跟老男孩学Linux运维:Shell编程实战
上QQ阅读APP看书,第一时间看更新

4.1 Shell中特殊且重要的变量

4.1.1 Shell中的特殊位置参数变量

在Shell中存在一些特殊且重要的变量,例如:$0、$1、$#,我们称之为特殊位置参数变量。要从命令行、函数或脚本执行等处传递参数时,就需要在Shell脚本中使用位置参数变量。表4-1为常用的特殊位置参数变量的说明。

表4-1 常用的特殊位置参数变量说明

1. $1 $2…$9 ${10} ${11}..特殊变量实践

范例4-1:测试$n(n为1..15)的实践。

编写如下的p.sh脚本,输入内容为“echo $1”,并执行测试:

        [root@oldboy scripts]# cat p.sh
        echo $1    #<==脚本功能是打印脚本传递的第一个参数的值。
        [root@oldboy  scripts]#  sh  p.sh  oldboy   #<==传入一个oldboy字符串参数,赋值给脚
                                                        本中的$1。
        oldboy     #<==把传入的oldboy参数赋值给脚本中的$1并输出,因此输出结果为oldboy。
        [root@oldboy scripts]# sh p.sh oldboy oldgirl  #<==传入两个字符串参数,但脚本不
    会接收第二个参数,参数默认是以空格分隔。
        oldboy     #<==只输出了oldboy,因为脚本里没有加入$2,因此,无法接收第二个参数oldgirl
                        字符串。
        [root@oldboy  scripts]#  sh  p.sh  "oldboy  oldgirl"   #<==加引号扩起来的内容传参,
                                                                    会作为一个字符串参数。
        oldboy oldgirl     #<==虽然都打印了,但是这些内容是作为一个参数传递给$1的。

范例4-2:在脚本中同时加入$1和$2,并进行测试。

        [root@oldboy scripts]# cat p.sh
        echo $1 $2
        [root@oldboy scripts]# sh p.sh longge bingbing     #<==同时传入两个字符串参数。
        longge bingbing
        [root@oldboy scripts]# sh p.sh "longge bingbing" oldgirl #<==传入两个字符串参
    数,对第一个有空格的多个字符串用双引号引起来。
        longge bingbing oldgirl

范例4-3:设置15个位置参数($1~$15),用于接收命令行传递的15个参数。

        [root@oldboy ~]# echo \${1..15}          #<==利用大括号输出15个位置参数,学会了
                                                        该命令就不用手敲代码了。
        $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15
        [root@oldboy ~]# echo \${1..15} >n.sh   #<==利用大括号输出15个位置参数并定向到
                                                        文件n.sh里。
        [root@oldboy ~]# cat n.sh
        $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15
        [root@oldboy scripts]# cat n.sh
        echo $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 #<==增加echo命令打
    印所有参数,这是最终的测试代码,前面的都是为了写代码,读者也可以用vim编辑录入。
        [root@oldboy scripts]# echo {a..z}        #<==测试打印26个字母a~z并以空格分隔。
        a b c d e f g h i j k l m n o p q r s t u v w x y z
        [root@oldboy scripts]# sh n.sh  {a..z}    #<==传入26个字母a~z,以空格分隔,作为
                                                        26个参数。
        a b c d e f g h i a0 a1 a2 a3 a4 a5 #<==位置参数的数字大于9后,输出的内容就不对了。

其实,当我们使用vim编辑脚本时,利用vim的高亮功能就会看到脚本呈现异常的颜色显示,如图4-1所示。

图4-1 vim高亮功能呈现脚本的异常

当位置参数数字大于9时,需要用大括号将数字括起来,如下:

        [root@oldboy scripts]# cat n.sh
        echo $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11} ${12} ${13} ${14} ${15}
        #<==数字大于9,必须给数字加大括号才能输出正确内容。

图4-2是加上括号后的高亮颜色,可以看到,颜色已经是正常的了,vim的语法高亮显示对编程很有帮助,有关vim的开发环境配置,见第16章。

图4-2 长方形线内为正常的颜色显示

以下是有关“$1, $2, $3…”这些位置参数的系统生产场景案例。对此,读者可以多参考rpcbind、NFS两个软件启动的脚本,这两个服务的启动脚本简单、规范。若是最小化安装的系统,则表示没有安装rpcbind、NFS,可以通过执行yum instlla nfs-utils rpcbind -y来安装。

在生产场景中,执行/etc/init.d/rpcbind start之后,rpcbind脚本后携带的start参数会传给脚本里的“$1”进行判断,脚本中传递参数的关键case语句节选如下:

        case "$1" in #<==这里的$1用于接收执行此脚本命令行的第一个参数,规范用法是用双引号引起来。
            start)   #<==如果$1接收的值匹配start,则执行下文的start函数及内部的指令。
                start  #<==调用脚本中的start函数。
                RETVAL=$? #<==这里是记录start函数执行的返回值,$?也是重要的变量,暂时可
                                以忽略,后面有介绍。
                ;;
            stop) #<==如果$1接收的值匹配stop,则执行下文的stop函数及内部的指令。
                stop
                RETVAL=$?
                ;;
            status) #<==如果$1接收的值匹配status,则执行下文的status函数及内部的指令。
                status $prog
                RETVAL=$?
                ;;
        ……省略部分内容

说明:读者只需要关注特殊变量($1)的内容,case等其他语句后文会细讲。

2. $0特殊变量的作用及变量实践

$0的作用为取出执行脚本的名称(包括路径),下面是该功能的实践。

范例4-4:获取脚本的名称及路径。

        [root@oldboy scripts]# cat n.sh
        echo $0

若不带路径执行脚本,那么输出结果就是脚本的名字,如下:

        [root@oldboy scripts]# sh n.sh
        n.sh #<==$0获取的值就是脚本的名字,因此这里输出了n.sh。

若使用全路径执行脚本,那么输出结果就是全路径加上脚本的名字,如下:

        [root@oldboy scripts]# sh /server/scripts/n.sh
        /server/scripts/n.sh  #<==如果执行的脚本中带有路径,那么$0获取的值就是脚本的名字加路径。

当要执行的脚本为全路径时,$0也会带着路径。此时如果希望单独获取名称或路径,则可用范例4-5的方法。

范例4-5:dirname及basename命令自身的功能和用法。

        [root@oldboy scripts]# dirname /server/scripts/n.sh
        /server/scripts              #<==dirname命令的作用是获取脚本的路径。
        [root@oldboy scripts]# basename /server/scripts/n.sh
        n.sh                         #<==basename命令的作用是获取脚本的名字。

说明:以后读者可以根据需求,用不同的命令获取对应的结果。

范例4-6:利用$0和上述命令(dirname、basename)分别取出脚本名称和脚本路径。

        [root@oldboy scripts]# cat n.sh
        dirname $0
        basename $0
        [root@oldboy scripts]# sh /server/scripts/n.sh
        /server/scripts              #<==这就是dirname $0的输出结果。
        n.sh                         #<==这就是basename $0的输出结果。

有关“$0”这个位置参数的系统生产场景案例如下,其中采用rpcbind系统脚本。

        [root@oldboy scripts]# tail -6 /etc/init.d/rpcbind  #<==查看结尾6行。
                echo  $"Usage:  $0  {start|stop|status|restart|reload|force-
    reload|condrestart|try-restart}"
                #<==$0的基本生产场景就是,当用户的输入不符合脚本的要求时,就打印脚本的名字及使用帮助。
                RETVAL=2
                ;;
        esac
        exit $RETVAL
        [root@oldboy scripts]# /etc/init.d/rpcbind #<==不带任何参数执行rpcbind脚本。
        Usage:  /etc/init.d/rpcbind  {start|stop|status|restart|reload|force-
    reload|condrestart|try-restart}
        #<==上文/etc/init.d/rpcbind就是$0从脚本命令行获取的值,当用户输入不符合脚本设定的
    要求时,打印脚本名字及预期的使用帮助。

3. $#特殊变量获取脚本传参个数的实践

范例4-7:通过$#获取脚本传参的个数。

        [root@oldboy scripts]# cat q.sh
        echo $1 $2 $3 $4 $5 $6 $7 $8 $9
        echo $# #<==此行是打印脚本命令行传参的个数。
        [root@oldboy scripts]# sh q.sh {a..z} #<==传入26个字符作为26个参数。
        a b c d e f g h i #<==只接收了9个变量,所以打印9个字符。
        26  #<==传入26个字符作为26个参数,因此这里的数字为26,说明传入了26个参数。

范例4-8:根据用户在命令行的传参个数判断用户的输入,不合要求的给予提示并退出。

这是一个针对$0、$1、$#等多位置参数的综合型企业案例,脚本中可能包含了部分读者没有掌握的技术,这里只需要理解这几个位置参数就可以了,对于其他知识后面会有详细讲解。

首先来看条件表达式判断语句的写法,如下:

        [root@oldboy scripts]# cat t1.sh
        [ $# -ne 2 ] && {         #<==如果执行脚本传参的个数不等于2,
          echo "muse two args"    #<==则给用户提示正确的用法。
          exit 1 #<==由于不满足要求,因此退出脚本,返回值为1。
        }
        echo oldgirl   #<==满足了参数个数的传参要求后,就执行判断后的程序脚本,即打印oldgirl。
        [root@oldboy scripts]# sh t1.sh
        muse two args #<==如果不加参数执行脚本,即不符合脚本要求,则直接给出提示。
        [root@oldboy scripts]# sh t1.sh   arg1 arg2
        oldgirl        #<==当参数满足要求后,打印oldgirl字符串。

然后是if判断语句的写法,如下:

        [root@oldboy scripts]# cat t2.sh
        if [ $# -ne 2 ] #<==如果执行脚本传参的个数不等于2,
          then
            echo "USAGE:/bin/sh $0 arg1 arg2" #<==则给用户提示正确用法,注意此处的$0,打印
                                                      脚本名字及路径。
            exit 1 #<==若不满足要求,则退出脚本,返回值为1。
        fi
        echo $1 $2 #<==若参数满足要求,则打印$1和$2获取到的传参的字符串。
        [root@oldboy scripts]# sh t2.sh         #<==若不加参数执行脚本,则直接给出提示。
        USAGE:/bin/sh t2.sh arg1 arg2           #<==t2.sh就是脚本中$0获取的值。
        [root@oldboy scripts]# sh t2.sh oldboy oldgirl
        oldboy oldgirl #<==若参数满足要求,则打印$1和$2获取的字符串,即oldboy和oldgirl。

4. $*和$@特殊变量功能及区别说明

首先,请翻到本章的开头再重新温习一下$*和$@的作用,然后再来看范例。

范例4-9:利用set设置位置参数(同命令行脚本的传参)。

        [root@oldboy scripts]# set -- "I am" handsome oldboy. #<==通过set设置三个
    字符串参数,“--”表示清除所有的参数变量,重新设置后面的参数变量。
        [root@oldboy scripts]# echo $#    #<==输出参数的个数。
        3  #<==共三个参数。
        [root@oldboy scripts]# echo $1    #<==打印第一个参数值。
        I am
        [root@oldboy scripts]# echo $2    #<==打印第二个参数值。
        handsome
        [root@oldboy scripts]# echo $3    #<==打印第三个参数值。
        oldboy.

测试$*和$@,注意,此时不带双引号:

        [root@oldboy scripts]# echo $*    #<==打印$*。
        I am handsome oldboy.
        [root@oldboy scripts]# echo $@     #<==打印$@。
        I am handsome oldboy.
        [root@oldboy scripts]# for i in $*; do echo $i; done #<==使用for循环输出$*测试。
        I #<==($*)不加双引号,因此会输出所有参数,然后第一个参数"I am"也拆开输出了。
        am
        handsome
        oldboy.
        [root@oldboy scripts]# for i in $@; do echo $i; done #<==使用for循环输出$@测试。
        I #<==($@)不加双引号,因此会输出所有参数,然后第一个参数"I am"也拆开输出了。
        am
        handsome
        oldboy.

测试"$*"和"$@",注意,此时带有双引号:

        [root@oldboy scripts]# echo "$*"
        I am handsome oldboy.
        [root@oldboy scripts]# echo "$@"
        I am handsome oldboy.
        [root@oldboy scripts]# for i in "$*"; do echo $i; done
        #<==在有双引号的情况下"$*",参数里引号中的内容当作一个参数输出了!
        I am handsome oldboy.
        [root@oldboy scripts]# for i in "$@"; do echo $i; done
        #<==在有双引号的情况下,每个参数均以独立的内容输出。
        I am #<==有双引号算一个参数。
        handsome
        oldboy.
        #<==这才真正符合我们传入的参数需求,set -- "I am" handsome oldboy.
        [root@oldboy  scripts]#  for  i; do  echo  $i; done  #<==去掉in  变量列表,相当于有引
                                                                号的in "$@"。
        I am
        handsome
        oldboy.
        #<==这才真正符合我们传入的参数需求,set -- "I am" handsome oldboy.
        [root@oldboy 02]# for i in $*; do echo $i; done   #<==($*)不加双引号,因此会输出
    所有参数,然后第一个参数"I am"也拆开输出了。
        I
        am
        handsome
        oldboy.
        [root@oldboy scripts]# shift         #<==用shift将位置参数移位(左移)。
        [root@oldboy scripts]# echo $#
        2
        [root@oldboy scripts]# echo $1       #<==这里就打印原来$2的值了。
        handsome
        [root@oldboy scripts]# echo $2       #<==这里就打印原来$3的值了。
        oldboy.

有关set和eval命令的使用案例(特殊位置变量用法)见http://oldboy.blog.51cto. com/2561410/1175971

4.1.2 Shell进程中的特殊状态变量

表4-2针对Shell进程的特殊状态变量进行了说明。

表4-2 Shell进程的特殊状态变量说明

提示:查找上述知识的方法为使用man bash命令,然后搜关键字“Special Parameters”。

1. $?特殊变量功能实践

范例4-10:执行命令后获取返回值(切换到oldboy用户下进行测试)。

        [oldboy@oldboy ~]$ pwd #<==执行pwd命令,然后用“echo $? ”查看执行命令的状态返回值。
        /home/oldboy
        [oldboy@oldboy ~]$ echo $?
        0  #<==返回0,表示上一个命令的执行是成功的。
        [oldboy@oldboy ~]$ ls /root #<==列表root目录的内容,
        ls: cannot open directory /root: Permission denied  #<==提示权限不够。
        [oldboy@oldboy ~]$ echo $?
        2  #<==返回值为非0,表示上一个命令(ls  /root)执行错误。注意:对于不同的错误,返回值是
                不同的。
        [oldboy@oldboy ~]$ rm -fr /root #<==删除/root目录及其子目录。
        rm: cannot remove `/root': Permission denied        #<==提示权限不够。
        [oldboy@oldboy ~]$ echo $?
        1 #<==返回值为1(非0)。
        [oldboy@oldboy ~]$ oldboy                          #<==执行一个不存在的命令。
        -bash: oldboy: command not found                    #<==提示命令找不到。
        [oldboy@oldboy ~]$ echo $?
        127 #<==返回值为127(非0)。

不同命令的执行结果中,“$? ”的返回值不尽相同,但在工作场景中,常用的就是0和非0两种状态,0表示成功运行,非0表示运行失败。

范例4-11:根据返回值来判断软件的安装步骤是否成功。

若使用源码编译安装软件,可以在每个步骤的结尾获取“$? ”来判断命令执行成功与否。例如:编译Nginx Web服务软件,执行make命令之后,新手不太容易确定编译是否正确,这时就可以使用“echo $? ”命令查看其返回值是否为0。下面是Nginx Web的基本安装过程,其中就是通过获取命令的返回值来确定命令的执行状况的。

        [root@oldboy tools]# yum install pcre-devel openssl-devel -y
        [root@oldboy tools]# wget -q http://nginx.org/download/nginx-1.10.1.tar.gz
        [root@oldboy tools]# tar xf nginx-1.10.1.tar.gz
        [root@oldboy tools]# cd nginx-1.10.1
        [root@oldboy  nginx-1.10.1]#  ./configure  --prefix=/application/nginx-1.10.1
    --user=nginx  --group=nginx  --with-http_ssl_module    --with-http_stub_status_
    module
        ……省略部分配置过程
          nginx http proxy temporary files: "proxy_temp"
          nginx http fastcgi temporary files: "fastcgi_temp"
          nginx http uwsgi temporary files: "uwsgi_temp"
          nginx http scgi temporary files: "scgi_temp"
        [root@oldboy nginx-1.10.1]# echo $?
        0
        [root@oldboy nginx-1.10.1]# make
        ……省略部分编译过程
        sed -e "s|%%PREFIX%%|/application/nginx-1.10.1|" \
                          -e "s|%%PID_PATH%%|/application/nginx-1.10.1/logs/nginx.pid|" \
                          -e "s|%%CONF_PATH%%|/application/nginx-1.10.1/conf/nginx.conf|" \
                          -e "s|%%ERROR_LOG_PATH%%|/application/nginx-1.10.1/logs/error.log|" \
                          < man/nginx.8 > objs/nginx.8
        make[1]: Leaving directory `/home/oldboy/tools/nginx-1.10.1'
        [root@oldboy nginx-1.10.1]# echo $?
        0
        test -d '/application/nginx-1.10.1/logs' \
                          || mkdir -p '/application/nginx-1.10.1/logs'
        make[1]: Leaving directory `/home/oldboy/tools/nginx-1.10.1'
        [root@oldboy nginx-1.10.1]# echo $?
        0

对于新手来说,在安装服务时,可以通过获取执行命令的返回值来确定命令的执行状态,从而快速确定命令是否执行成功。不过,有经验的技术人员不需要获取返回值,通过命令的最后过程输出就可以快速判断是否成功。

范例4-12:通过获取“$? ”的返回值确定网站备份是否正确。

提示:当对服务器的数据进行备份时,我们会在执行完关键命令,例如tar或cp后,通过获取返回值来判断命令是否成功,备份数据是否完整。

        [root@oldboy ~]# cd /etc/
        [root@oldboy etc]# tar zcf /opt/services.tar.gz ./services     #<==打包备份
        [root@oldboy  etc]#  echo  $?   #<==检查备份后的$?是否为0,如果为0则表示上一tar命
                                            令执行成功,工作中会写成Shell脚本。
        0

范例4-13:通过脚本控制命令及脚本执行后的返回值。

        [root@oldboy scripts]# cat test4.sh
        [ $# -ne 2 ] && {         #<==若参数个数不等于2,
        echo "must be two args." #<==则输出提示。
        exit 119                    #<==终止程序运行并以指定的119状态值退出程序,赋值给当前
                                        Shell的“$? ”变量。
        }
        echo oldgirl
        [root@oldboy scripts]# sh test4.sh #<==执行脚本。
        must be two args.
        [root@oldboy scripts]# echo $?
        119 #<==返回值为119,这个就是脚本中exit传过来的返回值。
        [root@oldboy scripts]# sh test4.sh a1 a2 #<==若满足参数要求,
        oldgirl #<==则跳过不合要求的提示及以119状态退出的两条命令。
        [root@oldboy scripts]# echo $?
        0 #<==返回值为0,这个就是echo oldgirl正确执行后,“$? ”的结果。

在企业场景下,“$? ”返回值的用法如下:

1)判断命令、脚本或函数等程序是否执行成功。

2)若在脚本中调用执行“exit 数字”,则会返回这个数字给“$? ”变量。

3)如果是在函数里,则通过“return 数字”把这个数字以函数返回值的形式传给“$? ”。

范例4-14:查看系统脚本的应用情况,脚本名为/etc/init.d/rpcbind。

这里利用sed打印/etc/init.d/rpcbind脚本的第50-73行,然后分析脚本里“$? ”的使用情况!

        [root@oldboy scripts]# sed -n '63,73p' /etc/init.d/rpcbind
        stop() {
                echo -n $"Stopping $prog: "
                killproc $prog  #<==这是停止rpcbind的命令。
                RETVAL=$? #<==将上述命令的返回值“$? ”赋值给RETVAL变量,用于后面的判断。
                echo
                [ $RETVAL -eq 0 ]&&{ #<==这里就是判断,如果返回值为0,则执行下面的指令。
                        rm -f /var/lock/subsys/$prog
                        rm -f /var/run/rpcbind*
                }
                return $RETVAL   #<==如果返回值不等于0,则跳过条件表达式的判断,在这里直接作
                                      为返回值传给执行stop函数的脚本。
        }

提示:有关特殊位置和进程的状态变量,可以多参考这个简单却功能强大的脚本。

2. $$特殊变量功能及实践

范例4-15:获取脚本执行的进程号(PID)。

        [root@oldboy scripts]# cat test_pid.sh        #<==编写一个简单的脚本。
        echo $$ >/tmp/a.pid #<==获取$$的值,并重定向到/tmp/a.pid里。
        sleep 300             #<==休息300秒,模拟守护进程不退出。
        [root@oldboy scripts]# ps -ef|grep test_pid|grep -v grep
        [root@oldboy scripts]# sh test_pid.sh & #<==在后台运行脚本,&符号表示在后台运行。
        [1] 10397                                     #<==这是脚本的进程号。
        [root@oldboy scripts]# ps -ef|grep test_pid|grep -v grep
        root      10397 10292   0 15:57 pts/0         00:00:00 sh test_pid.sh #<==这是脚本
    的进程号。
        [root@oldboy scripts]# cat /tmp/a.pid
        10397 #<==这是$$对应的值。

提示:到这里大家应该明白了吧,$$就是获取当前执行的Shell脚本的进程号。

范例4-16:实现系统中多次执行某一个脚本后的进程只有一个(此为$$的企业级应用)。

说明:有时执行定时任务脚本的频率比较快,并不知道上一个脚本是否真的执行完毕,但是,业务要求同一时刻只能有一个同样的脚本在运行,此时就可以利用$$获取上一次运行的脚本进程号,当程序重新运行时,根据获得的进程号,清理掉上一次的进程,运行新的脚本命令,脚本如下:

        [root@oldboy scripts]# cat pid.sh
        # ! /bin/sh
        pidpath=/tmp/a.pid      #<==定义pid文件。
        if [ -f "$pidpath" ]    #<==如果pid文件存在,则执行then后面的命令。
          then
            kill `cat $pidpath` >/dev/null 2>&1  #<==杀掉与前一个进程号对应的进程。
            rm -f $pidpath      #<==删除pid文件。
        fi
        echo $$ >$pidpath      #<==将当前Shell进程号记录到pid文件里。
        sleep 300

执行结果如下:

        [root@oldboy scripts]# ps -ef|grep pid.sh|grep -v grep
        [root@oldboy scripts]# sh pid.sh &  #<==后台运行脚本。
        [1] 10617
        [root@oldboy scripts]# ps -ef|grep pid.sh|grep -v grep  #<==查看启动的脚本进程。
        root      10617 10462   0 16:20 pts/1     00:00:00 sh pid.sh  #<==只有一个。
        [root@oldboy scripts]# sh pid.sh &  #<==多次运行脚本,每次都会将上一次运行的杀掉。
        [2] 10624
        [root@oldboy scripts]# sh pid.sh &  #<==多次运行脚本,每次都会将上一次运行的杀掉。
        [3] 10628
        [1]    Terminated                  sh pid.sh
        [root@oldboy scripts]# ps -ef|grep pid.sh|grep -v grep
        root      10628 10462   0 16:20 pts/1     00:00:00 sh pid.sh   #<==发现无论运行
    多少次脚本,都只有一个进程。
        [2]-   Terminated                  sh pid.sh
        [root@oldboy scripts]# ps -ef|grep pid.sh|grep -v grep
        root      10628 10462   0 16:20 pts/1     00:00:00 sh pid.sh #<==发现无论运行多
    少次脚本,都只有一个进程。

提示:这是一个生产案例的简单模拟,脚本用于执行启动或定时任务时,相同的脚本中只能有一个在运行,当新脚本运行时,必须关闭未运行完或未退出的上一次的同名脚本进程。

3. $_特殊变量功能说明及实践

$_的作用是获得上一条命令的最后一个参数值,此功能用得不多,了解即可。

范例4-17:$_参数的示例。

        [root@oldboy scripts]# /etc/init.d/rpcbind start oldboy
        [root@oldboy scripts]# echo $_ #<==打印上一条命令的最后一个参数值,即oldboy。
        oldboy
        [root@oldboy scripts]# /etc/init.d/rpcbind stop oldgirl
        Stopping rpcbind:                                                       [   OK   ]
        [root@oldboy scripts]# echo $_ #<==打印上一条命令的最后一个参数值,即oldgirl。
        oldgirl

4. $!特殊变量功能说明及实践

$!的功能类似于$$,只不过作用是获取上一次执行脚本的pid,对此,了解即可。

范例4-18:$!的功能示例。

        [root@oldboy scripts]# ps -ef|grep pid.sh|grep -v grep
        [root@oldboy scripts]# sh pid.sh & #<==后台运行pid.sh脚本。
        [1] 10760
        [root@oldboy scripts]# echo $!  #<==获取前一次执行脚本pid.sh的进程号。
        10760
        [root@oldboy scripts]# ps -ef|grep pid.sh|grep -v grep
        root       10760 10462   0 16:44 pts/1     00:00:00 sh pid.sh