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

3.3 普通变量

3.3.1 定义本地变量

本地变量在用户当前Shell生存期的脚本中使用。例如,本地变量oldboy的取值为bingbing,这个值只在用户当前Shell生存期中有意义。如果在Shell中启动另一个进程或退出,那么变量oldboy的值将会无效。

1.普通变量定义

为普通变量的定义赋值,一般有以下3种写法:

          变量名=value       #<==赋值时不加引号
          变量名='value'     #<==赋值时加单引号
          变量名="value"     #<==赋值时加双引号

2.在Shell中定义变量名及为变量内容赋值的要求

变量名一般是由字母、数字、下划线组成的,可以以字母或下划线开头,例如:oldboy、oldboy123、oldboy_training。

变量的内容可以用单引号或双引号引起来,也可不加引号,但是这三者的含义是不同的,具体参见后文说明。

3.普通变量的定义及输出的示例

范例3-4:采用不同的方式对普通变量进行定义,并一一打印输出。

          a=192.168.1.2
          b='192.168.1.2'
          c="192.168.1.2"
          echo "a=$a"
          echo "b=$b"
          echo "c=${c}"

提示:

1)$变量名表示输出变量,可以用$c和${c}两种用法。

2)请在命令行实践以上内容,然后看一看返回的结果有何不同。

思考:在命令行Shell下输入以上内容后会输出什么结果呢?请在看答案之前,先想一想上面a、b、c变量值的输出各是什么,最好自己实践一下。

答案:

        a=192.168.1.2
        b=192.168.1.2
        c=192.168.1.2

可见,将连续的普通字符串的内容赋值给变量,不管用不用引号,或者不管用什么引号,它的内容是什么,打印变量时就会输出什么。

范例3-5:接着上述范例的结果,再在Linux命令行下继续输入如下内容,想一想a、b、c的输出又各是什么结果?

        a=192.168.1.2-$a
        b='192.168.1.2-$a'
        c="192.168.1.2-$a"
        echo "a=$a"
        echo "b=$b"
        echo "c=${c}"

提示:建议先思考结果是什么,然后再在命令行实践以上内容,看看和思考的结果有何不同。

参考答案:

        a=192.168.1.2-192.168.1.2
        b=192.168.1.2-$a
        c=192.168.1.2-192.168.1.2-192.168.1.2

4.变量定义的基本技巧总结

这里以范例3-5为例:

        a=192.168.1.2-$a

第一种定义a变量的方式是不加任何引号直接定义变量的内容,当内容为简单连续的数字、字符串、路径名时,可以这样用,例如:a=1, b=oldboy等。不加引号时,值里有变量的会被解析后再输出,上述变量定义中因为$a的值被解析为192.168.1.2(范例3-3执行的影响),因此新的a值就是192.168.1.2-192.168.1.2。

        b='192.168.1.2-$a'

第二种定义b变量的方式是通过单引号定义。这种定义方式的特点是:输出变量内容时单引号里是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。这种方式比较适合于定义显示纯字符串的情况,即不希望解析变量、命令等的场景,因此,对于这里的b的值,定义时看到的是什么就输出什么,即192.168.1.2-$a。

        c="192.168.1.2-$a"

第三种定义c变量的方式是通过双引号定义变量。这种定义方式的特点是:输出变量内容时引号里的变量及命令会经过解析后再输出内容,而不是把双引号中的变量名及命令(命令需要反引起来)原样输出。这种方式比较适合于字符串中附带有变量及命令且想将其解析后再输出的变量定义。

老男孩经验:

数字内容的变量定义可以不加引号,其他没有特别要求的字符串等定义最好都加上双引号,如果真的需要原样输出就加单引号,定义变量加双引号是最常见的使用场景。

5.把一个命令的结果作为变量的内容赋值的方法

对需要获取命令结果的变量内容赋值的常见方法有两种:

        变量名=`ls`         #<==把命令用反引号引起来,不推荐使用这种方法,因为容易和单引号混淆
        变量名=$(ls)       #<==把命令用$()括起来,推荐使用这种方法

范例3-6:用两种方法把命令的结果赋值给变量。

        [oldboy@oldboy ~]$ ls
        test.sh
        [oldboy@oldboy ~]$ CMD=`ls`      #<==其中``为键盘上Tab键上面的那个键输出的字符
        [oldboy@oldboy ~]$ echo $CMD
        test.sh
        [oldboy@oldboy ~]$ CMD1=$(pwd)
        [oldboy@oldboy ~]$ echo $CMD1
        /home/oldboy                      #<==打印当前用户所在的目录

提示:生产场景中把命令的结果作为变量的内容进行赋值的方法在脚本开发时很常见。

范例3-7:按天打包网站的站点目录程序,生成不同的文件名(此为企业实战案例)。

        [root@oldboy scripts]# CMD=$(date +%F)  #<==将当前日期(格式为2016-09-10)赋值
                                                        给CMD变量
        [root@oldboy scripts]# echo $CMD                          #<==输出变量的值
        2016-09-10
        [root@oldboy scripts]# echo $(date +%F).tar.gz #<==直接输出时间命令的结果
        2016-09-10.tar.gz
        [root@oldboy scripts]# echo `date +%F`.tar.gz
        2016-09-10.tar.gz
        [root@oldboy scripts]# tar zcf etc_$(date +%F).tar.gz /etc
                                                          #<==将时间作为压缩包名打包
        tar: 从成员名中删除开头的“/”
        tar: 从硬连接目标中删除开头的“/”
        [root@oldboy  scripts]#  ls  -l  etc_2016-09-10.tar.gz    #<==打包结果,包名中包含
                                                                      有当前日期
        -rw-r--r-- 1 root root 9700163 9月  10 18:39 etc_2016-09-10.tar.gz
        [root@oldboy scripts]# H=$(uname -n)    #<==获取主机名并赋值给H变量
        [root@oldboy scripts]# echo $H
        oldboy
        [root@oldboy scripts]# tar zcf $H.tar.gz /etc/services    #<==将主机名作为压缩包名
                                                                      打包文件
        tar: 从成员名中删除开头的“/”
        [root@oldboy scripts]# ls -l oldboy.tar.gz        #<==打包结果,包名中包含有主机名
        -rw-r--r-- 1 root root 127303 9月  10 18:40 oldboy.tar.gz

局部(普通)变量定义及赋值的经验小结

常规普通变量定义:

□若变量内容为连续的数字或字符串,赋值时,变量内容两边可以不加引号,例如a=123。

□变量的内容很多时,如果有空格且希望解析内容中的变量,就加双引号,例如a="/etc/rc.local $USER" ,此时输出变量会对内容中的$USER进行解析然后再输出。

□希望原样输出变量中的内容时就用单引号引起内容进行赋值,例如:a='$USER'。希望变量的内容是命令的解析结果的定义及赋值如下:

□要使用反引号将赋值的命令括起来,例如:a=`ls`;或者用$()括起来,例如:a=$(ls)。

变量的输出方法如下:

□使用“$变量名”即可输出变量的内容,常用“echo $变量名”的方式,也可用printf代替echo输出更复杂的格式内容。

变量定义的技巧及注意事项:

□注意命令变量内容前后的字符``(此字符为键盘Tab键上面的那个反引号,不是单引号),例如:“CMD=`ls`”。

□在变量名前加$可以取得该变量的值,使用echo或printf命令可以显示变量的值,$A和${A}的写法不同,但效果是一样的。

□用echo等命令输出变量的时候,也可用单引号、双引号、反引号,例如:echo$A、echo "$A"、echo '$A',它们的用法和前面变量内容定义的总结是一致的。

□$dbname_tname,当变量后面连接有其他字符的时候,必须给变量加上大括号{},例如:$dbname_tname就要改成${dbname}_tname。

有关上述变量问题输出的小故事

故事1:老男孩正在给面授班讲课,发了一段内容,结果引起群里网络班学员的强烈反应,下面是对话内容。

老男孩(31333741) 12:42:54

金庸新著

XXX-学员 12:43:39

老师,金庸又写啥小说了? #<==看到了吧,这引起了误解

老男孩(31333741) 12:42:54

这是一本小说,作者为金庸新,而非金庸,但是给读者造成的感觉是{金庸}新著。

$dbname_tname变量就类似于这个金庸新著,会引起歧义,因此要改成${dbname}_tname, Shell就会认为只有dbname是变量了。

老男孩(31333741) 12:44:45

如果真的是金庸新著,就要像这样用大括号分隔开,${金庸}新著。

故事2:老男孩运维班20期的李同学在他媳妇看电视剧时发现了这个金庸新著,于是他将电视剧停下来,还截图发给了我。

可见形象的比喻学习对学生的影响非常深远!养成将所有字符串变量用大括号括起来的习惯,在编程时将会减少很多问题。不过老男孩也并不是一直都这么做,因为多输入内容会造成效率不高,但是金庸新著的问题确实要多注意。

3.3.2 变量定义及变量输出说明

有关Shell变量定义、赋值及变量输出加单引号、双引号、反引号与不加引号的简要说明见表3-2。

表3-2 单引号、双引号、反引号与不加引号的知识说明

提示:这里仅为Linux Shell下的结论,对于awk语言会有点特别,有关情况下文会有测试讲解。

老男孩的建议:

□在脚本中定义普通字符串变量时,应尽量把变量的内容用双引号括起来。

□单纯数字的变量内容可以不加引号。

□希望变量的内容原样输出时需要加单引号。

□希望变量值引用命令并获取命令的结果时就用反引号或$()。

以下是单引号、双引号与不加引号的实战演示。

范例3-8:对由反引号引起来的`date`命令或$(date)进行测试。

        [root@oldboy ~]# echo 'today is date'
        today is date     #<==单引号引起内容时,你看到什么就会显示什么
        [root@oldboy ~]# echo 'today is `date`'
        today is `date`   #<==单引号引起内容时,你看到什么就会显示什么,内容中有命令时即使通过
                                反引号引起来也没有用
        [root@oldboy ~]# echo "today is date"
        today is date
        [root@oldboy ~]# echo "today is `date`"
        today is Sun Sep 11 15:12:30 CST 2016
        #<== 对输出内容加双引号时,如果里面是变量或用反引号引起来的命令,则会先把变量或命令解析成
            具体内容再显示
        [root@oldboy ~]# echo "today is $(date)"    #<==$()的功能和反引号``相同
        today is Sun Sep 11 15:12:30 CST 2016
        [root@oldboy ~]# echo today is $(date)      #<==带空格的内容不加引号,同样可以正确
                                                          地输出,但不建议这么做
        today is Sun Sep 11 15:12:30 CST 2016
        #<==对于连续的字符串等内容输出一般可以不加引号,但加双引号比较保险,所以推荐使用。

范例3-9:变量定义后,在调用变量输出打印时加引号测试。

        [root@oldboy ~]# OLDBOY=testchars            #<==创建一个不带引号的变量并赋值。
        [root@oldboy ~]# echo $OLDBOY                #<==不加引号,显示变量解析后的内容。
        testchars
        [root@oldboy ~]# echo '$OLDBOY'              #<==加单引号,显示变量本身。
        $OLDBOY
        [root@oldboy ~]# echo "$OLDBOY"              #<==加双引号,显示变量内容,引号内可以
                                                          是变量、字符串等。
        testchars

范例3-10:使用三剑客命令中的grep过滤字符串时给过滤的内容加引号。

        [root@oldboy ~]# cat grep.log                #<==待测试的内容。
        testchars
        oldboy
        [root@oldboy ~]# grep "$OLDBOY" grep.log     #<==将$OLDBOY解析为结果后进行过滤。
        testchars
        [root@oldboy ~]# grep '$OLDBOY' grep.log     #<==将$OLDBOY本身作为结果进行过滤。
        [root@oldboy ~]# grep $OLDBOY grep.log       #<==将$OLDBOY解析为结果后进行过滤,同
    双引号的情况,但不建议这样使用,没有特殊需要时应一律加双引号。
        testchars

范例3-11:使用awk调用Shell中的变量,分别针对加引号、不加引号等情况进行测试。

首先在给Shell中的变量赋值时不加任何引号,这里使用awk输出测试结果。

        [root@oldboy ~]# ETT=123                      #<==定义变量ETT并赋值123,没加引号。
        [root@oldboy ~]# awk 'BEGIN {print "$ETT"}'
        #<==加双引号引用$ETT,却只输出了本身,这个就不符合前文的结论了。
        $ETT
        [root@oldboy ~]# awk 'BEGIN {print $ETT}'
                  #<==不加引号的$ETT,又输出了空的结果,这个就不符合前文的结论了。
        [root@oldboy ~]# awk 'BEGIN {print '$ETT'}'
        #<==加单引号引用$ETT,又输出了解析后的结果,这个就不符合前文的结论了。
        123
          [root@oldboy ~]# awk 'BEGIN {print "'$ETT'"}'
        123

以上的结果正好与前面的结论相反,这是awk调用Shell变量的特殊用法。

然后在给Shell中的变量赋值时加单引号,同样使用awk输出测试结果。

        [root@oldboy ~]# ETT='oldgirl'       #<==定义变量ETT并赋值oldgirl,加单引号。
        [root@oldboy ~]# awk 'BEGIN {print "$ETT"}'
        $ETT                                  #<==加双引号引用$ETT,则输出本身。
        [root@oldboy ~]# awk 'BEGIN {print $ETT}'
        #<==对$ETT不加引号,则输出空的结果。
        [root@oldboy ~]# awk 'BEGIN {print '$ETT'}'
        #<==加单引号引用$ETT,也是输出空的结果,这个和前文的不加引号定义、赋值的结果又不一样。
        [root@oldboy ~]# awk 'BEGIN {print "'$ETT'"}'
        oldgirl    #<==在单引号外再加一层双引号引用$ETT,则输出解析后的结果。

以下在给Shell中的变量赋值时加双引号,也使用awk输出测试结果。

        [root@oldboy ~]# ETT="tingting" #<==定义变量ETT并赋值tingting,加双引号,这个
                                                测试结果同单引号的情况。
        [root@oldboy ~]# awk 'BEGIN {print "$ETT"}'     #<==加双引号引用$ETT,会输出本身。
        $ETT
        [root@oldboy ~]# awk 'BEGIN {print $ETT}'       #<==不加引号的$ETT,会输出空的结果。
        [root@oldboy ~]# awk 'BEGIN {print '$ETT'}'     #<==加单引号的$ETT,会输出空的结果。
        [root@oldboy ~]# awk 'BEGIN {print "'$ETT'"}'   #<==在单引号外部再加双引号引用$ETT,
                                                              会输出正确结果。
        tingting

以下在给Shell中的变量赋值时加反引号引用命令,同样使用awk输出测试结果。

        [root@oldboy ~]# ETT=`pwd` #<==定义变量ETT并赋值pwd命令,加反引号,这个测试结果更特殊。
        [root@oldboy ~]# echo $ETT
        /root
        [root@oldboy ~]# awk 'BEGIN {print "$ETT"}'     #<==加双引号引用$ETT,会输出本身。
        $ETT
        [root@oldboy ~]# awk 'BEGIN {print $ETT}'       #<==不加引号的$ETT,会输出空的结果。
        [root@oldboy ~]# awk 'BEGIN {print '$ETT'}'     #<==单引号引用$ETT,看起来输出了结
                                                                果,却是报错,和外层单引号冲突了。
        awk: BEGIN {print /root}
        awk:                    ^ unterminated regexp
        awk: cmd. line:1: BEGIN {print /root}
        awk: cmd. line:1:                          ^ unexpected newline or end of string
        [root@oldboy ~]# awk 'BEGIN {print "'$ETT'"}'   #<==在单引号外部再加双引号引用$ETT,
                                                                会输出正确结果。
        /root

根据上述范例整理的测试结果见表3-3,供读者参考。

表3-3 测试结果

结论:不管变量如何定义、赋值,除了加单引号以外,利用awk直接获取变量的输出,结果都是一样的,因此,在awk取用Shell变量时,我们更多地还是喜欢先用echo加符号输出变量,然后通过管道给awk,进而控制变量的输出结果。举例如下:

        [root@oldboy ~]# ETT="oldgirl"                     #<==最常规的赋值语法
        [root@oldboy ~]# echo "$ETT"|awk '{print $0}'      #<==用双引号引用$ETT
        oldgirl
        [root@oldboy ~]# echo '$ETT'|awk '{print $0}'      #<==用单引号引用$ETT
        $ETT
        [root@oldboy ~]# echo $ETT|awk '{print $0}'        #<==不加引号引用$ETT
        oldgirl
        [root@oldboy ~]# ETT=`pwd`                         #<==命令赋值的语法
        [root@oldboy ~]# echo "$ETT"|awk '{print $0}'
        /root
        [root@oldboy ~]# echo '$ETT'|awk '{print $0}'
        $ETT
        [root@oldboy ~]# echo $ETT|awk '{print $0}'
        /root

这就符合前面给出的普通情况的结论了。不过,这个例子特殊了一点,有关awk调用Shell变量的详情,还可以参考老男孩的博客“一道实用Linux运维问题的9种Shell解答http://oldboy.blog.51cto.com/2561410/760192”。

范例3-12:通过sed指定变量关键字过滤。

        [root@oldboy ~]# cat sed.log
        testchars
        oldboy
        [root@oldboy ~]# sed -n /"$OLDBOY"/p sed.log   #<==加双引号测试
        testchars
        [root@oldboy ~]# sed -n /$OLDBOY/p sed.log     #<==不加引号测试
        testchars
        [root@oldboy ~]# sed -n /'$OLDBOY'/p sed.log   #<==加单引号测试
          #<==输出本身,但是文件里没有本身匹配的字符串,因此输出为空

注意:sed和grep的测试和前面结论是相符的,唯有awk有些特殊。

提示:上述内容不需要特意去记,在使用时测试一下就会明白。

关于自定义普通字符串变量的建议

1)内容是纯数字、简单的连续字符(内容中不带任何空格)时,定义时可以不加任何引号,例如:

        a.OldboyAge=33
        b.NETWORKING=yes

2)没有特殊情况时,字符串一律用双引号定义赋值,特别是多个字符串中间有空格时,例如:

        a.NFSD_MODULE="no load"
        b.MyName="Oldboy is a handsome boy."

3)当变量里的内容需要原样输出时,要用单引号(' '),这样的需求极少,例如:

        a.OLDBOY_NAME='OLDBOY'