0 前言

最近没啥可以写博客的好点子,为此一直咕咕咕了。端午节没啥事,左右苦思冥想,发现网上针对ROS的bash启动脚本几乎还是处于0的状态。为此针对性的给ROS开一个bash脚本的教程是非常有必要的。也希望各位大佬能提点意见,如果后续合适,我会继续根据各位的意见来继续开坑的。
在这里插入图片描述

1 bash 脚本简介

1.1 bash 脚本基础介绍

首先我们知道常见的bash脚本是基于shell文件的。因为bash是免费的并且很容易使用。所以很多开源作者所提供的脚本都是使用bash。为此我们非常有必要去对该脚本语言结构进行详细阐述

  1. 程序必须以下面的行开始(必须方在文件的第一行)。符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行程序:

     #!/bin/sh
    

    bash提供一些可选项帮助你调试程序,-x option它在执行时打印命令和参数。它被称为打印调试,跟踪或x跟踪。我们可以通过修改第一行来使用它;-e option它代表“出错”。如果命令以非零退出状态退出,这将导致脚本立即退出。-v option它在读取时打印shell命令/输入行。这些选项可以组合使用,一次可以使用多个选项!

     #!/bin/bash-xe
     #!/bin/bash-ex
     #!/bin/bash-x-e
     #!/bin/bash-e-x
    
  2. 在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。bash脚本的注释非常有必要,也为了未来我们去分析我们所写脚本的作用及工作原理。

  3. 脚本执行,我们可以通过两种方式,来运行filename文件:

      chmod +x filename
      ./filename
    
    1.2 bash 脚本基础语句介绍
  4. bash脚本也可以对变量赋值,但是注意,等号两边不应有空格。同时bash中的语句结尾不需要分号(";")

     num="hello world"
     NUM=$(num)#该命令执行后的输入结果赋值给一个变量
    
     read -p "Please Enter You Name: " NAME #read 命令接收键盘的输入
     echo "Your Name Is: $NAME"
    
  5. 如果我们需要打印变量a的内容,则可以用echo来实现文本的打印

     #变量分开打印
     num=2
     echo "NUM is:"
     echo $num
     #一行打印
     num=2
     echo "this is the "$num
     echo "this is the ${num}nd"
    
  6. $符号在bash文件中起到非常重要的作用。当我们在执行脚本时有时很想传个参数进去,如:#sh mysh.sh abc那这时候需要用到$符号

     $# #传入脚本的命令行参数个数
    
     $* #所有命令行参数值,在各个参数值之间留有空格
    
     $0 #命令本身(shell文件名)
    
     $1 #第一个命令行参数
    
     $2 #第二个命令行参数
    

    我们可以用以下脚本进行测试

     sh my.sh a b c d e
    

    my.sh

     #!/bin/sh
     echo "number of vars:"$#
     echo "values of vars:"$*
     echo "value of var1:"$1
     echo "value of var2:"$2
     echo "value of var3:"$3
     echo "value of var4:"$4
     echo "value of var4:"$100 #执行时并没有输入100个参数,那取得的值为 NULL
    

    另外还有其他的 $符号指令

     $$
     #Shell本身的PID(ProcessID,即脚本运行的当前 进程ID号)
     $!
     #Shell最后运行的后台Process的PID(后台运行的最后一个进程的 进程ID号)
     $?
     #最后运行的命令的结束代码(返回值)即执行上一个指令的返回值 (显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误)
     $*
     #所有参数列表。如"$*"用「"」括起来的情况、以"$1 $2 … $n"的形式输出所有参数,此选项参数可超过9个。
     $@
     #所有参数列表。如"$@"用「"」括起来的情况、以"$1" "$2" … "$n" 的形式输出所有参数。
     #$@ 跟$*类似,但是可以当作数组用
    
  7. 文件&字符串&算术等测试操作,主要方便调试判断的使用

     #文件测试操作
     -d FILE_NAM  # True if FILE_NAM is a directory
     -e FILE_NAM  # True if FILE_NAM exists
     -f FILE_NAM  # True if FILE_NAM exists and is a regular file
     -r FILE_NAM  # True if FILE_NAM is readable
     -s FILE_NAM  # True if FILE_NAM exists and is not empty
     -w FILE_NAM  # True if FILE_NAM has write permission
     -x FILE_NAM  # True if FILE_NAM is executable
    
     #字符串测试操作
     -z STRING  # True if STRING is empty
     -n STRING  # True if STRING is not empty
     STRING1 = STRIN2 # True if strings are equal
     STRING1 != STRIN2 # True if strings are not equal
    
     #算术测试操作
     var1 -eq var2  # True if var1 is equal to var2
     var1 -ne var2  # True if var1 not equal to var2
     var1 -lt var2  # True if var1 is less than var2
     var1 -le var2  # True if var1 is less than or equal to var2
     var1 -gt var2  # True if var1 is greater than var2
     var1 -ge var2  # True if var1 is greater than or equal to var2
    
  8. 条件判断,和其他编程语言一样,shell脚本也能基于条件进行判断,我们可以使用if-elseif-elif-else来实现简单的条件判断

     if [ condition-is-true ]
     then
       command 1
     elif [ condition-is-true ];then
       command 2
     elif [ condition-is-true ]
     then
       command 3
     else
       command 4
     fi
    
  9. case语句case可以实现和if一样的功能,但是当条件判断很多的时候,使用if不太方便,比如使用if进行值的比较

     #!/bin/bash
     read -p "Enter the answer in Y/N: " ANSWER
     case "$ANSWER" in
       [yY] | [yY][eE][sS])#大小写
         echo "The Answer is Yes :)"
         ;;
       [nN] | [nN][oO])
         echo "The Answer is No :("
         ;;
       *)
         echo "Invalid Answer :/"
         ;;
     esac
    
  10. 迭代语句,可以通过循环执行同一个代码块很多次

     COLORS="red green blue"
     for COLOR in $COLORS
     do
       echo "The Color is: ${COLOR}"
     done
    
  11. while 循环,当所给的条件为true时,循环执行while里面的代码块,下面的例子是一行一行读取文件内容

     #!/bin/bash
     LINE=1
     while read CURRENT_LINE
     do
       echo "${LINE}: $CURRENT_LINE"
       ((LINE++))
     done < /etc/passwd
     # This script loops through the file /etc/passwd line by line
    
  12. 退出状态码,任何一个命令执行完成后都会产生一个退出状态码,范围0-255,状态码可以用来检查错误 0 表示正确执行并正常退出,如果是非0,表示执行过程中出错,没有正常退出。例如我们可以通过错误码来检查主机和服务器之间是否可以抵达。

     HOST="google.com"
     ping -c 1 $HOST     # -c is used for count, it will send the request, number of times mentioned
     RETURN_CODE=$?  # 最后运行的命令的结束代码(返回值)即执行上一个指令的返回值 
     if [ "$RETURN_CODE" -eq "0" ]
     then
       echo "$HOST reachable"
     else
       echo "$HOST unreachable"
     fi
    

    自定义退出状态码默认的状态码是上一条命令执行的结果,我们可以通过exit来自定义状态码

     exit 0
     exit 1
     exit 2
       ...
       ...
     exit 255
    
  13. bash脚本支持逻辑与&&和逻辑或||
  14. bash脚本中可以使用函数来把一些列的命令或语句定义在一个函数内,从程序的其他地方调用。
    如果需要调用函数,我们只需要简单的给出函数名字即可调用

    #!/bin/bash
    function myFunc () {
        echo "Shell Scripting Is Fun!"
    }
    myFunc # call
    

    同时脚本一样,也可以给函数传递参数完成特殊的任务,第一个参数存储在变量$1中,第二个参数存储在变量$2中,$@存储所有的参数,参数之间使用空格分割 myFunc param1 param2 param3...

  15. 在上面第六点case的例子中,我们就是使用到了通配符这一概念。bash脚本使用通配符可以完成特定的匹配。

    #通配符* 可以通配一个或多个任意字符
    *.txt
    hello.*
    great*.md
    
    #通配符?匹配一个字符
    ?.md
    Hello?
    
    #通配符[]匹配括号内部的任意一个字符
    He[loym], [AIEOU]
    
    #通配符[!]不匹配括号内的任何字符
    `[!aeiou]`
    
    #匹配通配符。有些情况下我们想匹配*或?等特殊字符,可以使用转义字符\*\?
    
  16. set命令是 ash 脚本的重要环节,却常常被忽视,导致脚本的安全性和可维护性出问题。和1.1处介绍类似。

    set -u # 脚本在头部加上它,遇到不存在的变量就会报错,并停止执行。
    set -o nounset #与set -u等价
    
    foo # 如果是不存在的命令,执行时会报错。但是,Bash 会忽略这个错误,继续往下执行。
    set +e # +e表示关闭-e选项,set -e表示重新打开-e选项
    command1
    command2
    set -e# 脚本只要发生错误,就终止执行
    

    在这里插入图片描述

    set -x #用来在运行结果之前,先输出执行的那一行命令
    set -o xtrace#与set -x等价
    

    在这里插入图片描述

  17. Unix命令同样可以在bash脚本中使用。这些命令通常是用来进行文件和文字操作的。

    ls #文件列表
    wc –l filewc -w filewc -c sourcefile #计算文件行数计算文件中的单词数计算文件中的字符数
    cp sourcefile destfile #文件拷贝
    mv oldname newname #重命名文件或移动文件
    rm file #删除文件
    grep 'pattern' sourcefile #在文件内搜索字符串比如:grep 'searchstring' file.txt
    cut -b colnum sourcefile  #指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出每行第5个到第9个字符cut -b5-9 file.txt千万不要和cat命令混淆,这是两个完全不同的命令
    cat file.txt #输出文件内容到标准输出设备(屏幕)上
    file somefile #得到文件类型
    read var #提示用户输入,并将输入赋值给变量
    sort file.txt #对file.txt文件中的行进行排序
    uniq #删除文本文件中出现的行列比如: sort file.txt | uniq
    expr #进行数学运算Example: add 2 and 3expr 2 "+" 3
    find #搜索文件比如:根据文件名搜索find . -name filename -print
    tee #将数据输出到标准输出设备(屏幕) 和文件比如:somecommand | tee outfile
    basename sourcefile #返回不包含路径的文件名比如: basename /bin/tux将返回 tux
    dirname sourcefile #返回文件所在路径比如:dirname /bin/tux将返回 /bin
    head sourcefile #打印文本文件开头几行
    tail sourcefile #打印文本文件末尾几行
    grep "hello" file.txt | wc -l #管道 (|) 将一个命令的输出作为另外一个命令的输入。在file.txt中搜索包含有”hello”的行并计算其行数。
    export MYENV=7 #定义环境变量并赋值,可以在bash文件之间互传
    export -p #列出当前的环境变量
    uname #可显示电脑以及操作系统的相关信息;-a或--all 显示全部的信息。-m或--machine 显示电脑类型。-n或-nodename 显示在网络上的主机名称。-r或--release 显示操作系统的发行编号。-s或--sysname 显示操作系统名称。
    >  #写入文件并覆盖旧文件
    >> #加到文件的尾部,保留旧文件内容。
    

    15.Bash脚本常见用法扩展,这位作者充分的去举了很多经典的bash脚本开发例子。需要进阶的读者可以看一下这一篇文章。

2 bash在ros的使用

在介绍完bash的基础用法后,我们对bash有了一定基础的认识。下面我来给各位读者提供一些经典的和ros开发相关的bash代码例子。

  1. 安装ros melodic环境(需要管理员启动)

     #!/bin/bash
     # ROS Melodic Installation Script
     sudo -s
    
     # Setup your sources.list
     sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
    
     # Set up your keys
     apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
    
     # Update packages
     apt-get update
    
     # Install ROS
     sudo apt install ros-melodic-desktop-full
     sudo apt install ros-melodic-desktop-full
     sudo apt install ros-melodic-desktop-full
     sudo apt install ros-melodic-desktop-full
    
     # Environment setup
     echo "# Source ROS installation setup"  >> ~/.bashrc
     echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc
     source ~/.bashrc
    
     # Dependencies for building packages
     apt install python-rosdep python-rosinstall python-rosinstall-generator python-wstool build-essential
    
     # Install rosdep
     apt install python-rosdep
    
     # Initialize rosdep
     rosdep init
     rosdep update
    
     # Print final message
     echo "Script execution finalized"
    
  2. 创建ros开发环境

     #!/bin/bash
     set -e
     SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
     # get UBUNTU_CODENAME, ROS_DISTRO, REPO_DIR, CATKIN_DIR
    
     export UBUNTU_CODENAME=$(lsb_release -s -c)
     case $UBUNTU_CODENAME in
       trusty)
         export ROS_DISTRO=indigo;;
       xenial)
         export ROS_DISTRO=kinetic;;
       bionic)
         export ROS_DISTRO=melodic;;
       *)
         echo "Unsupported version of Ubuntu detected. Only trusty (14.04.*), xenial (16.04.*), and bionic (18.04.*) are currently supported."
         exit 1
     esac
     export REPO_DIR=$(dirname "$SCRIPT_DIR")
     export CATKIN_DIR="$HOME/catkin_ws"
    
     source /opt/ros/$ROS_DISTRO/setup.bash
    
     main()
     {
         install_catkin_tools
         create_catkin_ws
     }
    
     install_catkin_tools()
     {
         # Check if already installed
         if type catkin > /dev/null 2>&1; then
             echo "Catkin tools is already installed"
         else
             echo "Installing catkin tools ..."
             sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu `lsb_release -sc` main" > /etc/apt/sources.list.d/ros-latest.list'
             wget -qO - http://packages.ros.org/ros.key | sudo apt-key add -
             sudo apt-get -qq update
             sudo apt-get -qq install python-catkin-tools > /dev/null
             echo "Catkin tools installed successfully."
         fi
     }
    
     create_catkin_ws()
     {
         # Check if workspace exists
         if [ -e "$CATKIN_DIR/.catkin_workspace" ] || [ -d "$CATKIN_DIR/.catkin_tools" ]; then
             echo "Catkin workspace detected at ~/catkin_ws"
         else
             echo "Creating catkin workspace in $HOME/catkin_ws ..."
             source /opt/ros/$ROS_DISTRO/setup.bash
             mkdir -p "$HOME/catkin_ws/src"
             cd "$HOME/catkin_ws"
             catkin init > /dev/null
             catkin_make
             echo "Catkin workspace created successfully."
         fi
     }
    
     main
    
  3. 自动拉文件并编译,这部分我们展示下多文件的相互调用(主要展示下export的调用)

    首先创建一个 ubuntu_test.bash的文件

     #!/bin/bash
     set -e
     export UBUNTU_CODENAME=$(lsb_release -s -c)
     case $UBUNTU_CODENAME in
       trusty)
         export ROS_DISTRO=indigo;;
       xenial)
         export ROS_DISTRO=kinetic;;
       bionic)
         export ROS_DISTRO=melodic;;
       *)
         echo "Unsupported version of Ubuntu detected. Only trusty (14.04.*), xenial (16.04.*), and bionic (18.04.*) are currently supported."
         exit 1
     esac
     export REPO_DIR=$(dirname "$SCRIPT_DIR")
     export CATKIN_DIR="$HOME/catkin_ws"
    

    再创建一个pull_package.bash的bash文件

     #!/bin/bash
     set -e
     SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
     # get UBUNTU_CODENAME, ROS_DISTRO, REPO_DIR, CATKIN_DIR
     source $SCRIPT_DIR/identify_environment.bash
    
     cd "$HOME/catkin_ws/src"
     rm CMakeLists.txt
     git clone https://github.com/xxxx/xxxxxxxx.git .
    
     # Build the whole workspace
     echo "building all packages"
     cd $HOME/catkin_ws
     catkin_make
     echo "built all packages"
    
     # Source the workspace
     source $HOME/catkin_ws/devel/setup.bash
    
  4. 安装第三方库,以ceres为例

     #!/bin/bash
     set -e
     BUILD_TYPE=Release
     INSTALL_PREFIX=${PWD}
    
     # Clone
     if [ ! -d src/ceres-solver ]; then
       cd src
       git clone https://github.com/ceres-solver/ceres-solver
       cd ..
     fi
    
     # Build
     cd src/ceres-solver
     git checkout 2.0.0
     rm -rf build
     mkdir -p build
     cd build
     cmake .. \
       -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
       -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX \
       -DCMAKE_PREFIX_PATH=$INSTALL_PREFIX
     make
     make install
    
  5. ros卸载
     #!/bin/bash
     apt-get purge ros-*
     rm -rf /etc/ros
     gedit ~/.bashrc
    
    在写这篇博客的同时,本人发现网络上有一个对机器人函数进行安装的开源项目。如果各位有需求可以访问该项目直接根据本文的知识对所需要的函数库进行安装:Ubuntu Setup Scripts for Robotics & Machine Learning
    在这里插入图片描述
    在这里插入图片描述