bash基础

作者:jicanmeng

时间:2016年05月05日


  1. 两个命令写在一行执行
  2. shell脚本第一行
  3. echo -n
  4. bash shell中的变量
  5. 输入输出重定向基本知识
  6. 管道基本知识
  7. bash shell中的数学运算的两种方法
  8. bash shell中的浮点运算
  9. bash shell中的退出状态码
  10. bash shell中命令的执行过程是怎样的?
  11. 5个问题

1. 两个命令写在一行执行

两个命令可以用分号;或逻辑与符号&&隔开,写在一行,在运行时按顺序运行。区别在于符号;前后的两条命令都会执行,而符号&&前面的命令执行成功,后面的命令才会执行。(P200)

[jicanmeng@andy tmp]$ ls
                    rpm.txt
                [jicanmeng@andy tmp]$ date
                    Thu May  5 22:36:59 CST 2016
                [jicanmeng@andy tmp]$ ls; date
                    rpm.txt
                    Thu May  5 22:37:03 CST 2016
                [jicanmeng@andy tmp]$ lsaaa; date
                    bash: lsaaa: command not found
                    Thu May  5 22:37:07 CST 2016
                [jicanmeng@andy tmp]$ lsaaa && date
                    bash: lsaaa: command not found
                [jicanmeng@andy tmp]$ 

2. shell脚本第一行

创建shell脚本时,必须在文件的第一行指定要使用的shell。其格式为:#!/bin/bash。(P201)
符号#表示注释。但是第一行是个特例,它不表示注释。(P201)
bash脚本的第一行指定了默认情况下使用哪个shell来执行本脚本。假设有两个脚本a.sh和b.sh,内容如下:

[jicanmeng@andy tmp]$ cat a.sh
                        #!/bin/bash
                        sleep 20
                    [jicanmeng@andy tmp]$ cat b.sh
                        #!/bin/sh
                        sleep 20
                    [jicanmeng@andy tmp]$ 
我们打开三个shell,分别对应pts/1,pts/2,pts/3。在pts/1中执行命令: ./a.sh,在pts/2中执行命令: ./b.sh,在pts/3中执行命令: ps -ef。命令输出如下:
[jicanmeng@andy tmp]$ ps -ef
                    ...
                    500       2870       1  0 May03 ?            00:00:13 /usr/bin/gnome-terminal -x /bin/
                    ...
                    500       5870  2870  0 22:29 pts/1    00:00:00 /bin/bash
                    500       5885  2870  0 22:29 pts/2    00:00:00 /bin/bash
                    500       5900  2870  0 22:29 pts/3    00:00:00 /bin/bash
                    500       5913  5870  0 22:29 pts/1    00:00:00 /bin/bash ./a.sh
                    500       5914  5913  0 22:29 pts/1    00:00:00 sleep 20
                    500       5915  5885  0 22:29 pts/2    00:00:00 /bin/sh ./b.sh
                    500       5916  5915  0 22:29 pts/2    00:00:00 sleep 20
                    500       5917  5900  0 22:29 pts/3    00:00:00 ps -ef
                [jicanmeng@andy tmp]$ 
可以看出,a.sh脚本是被bash执行的,b.sh是被sh执行的,这都是在脚本第一行指定的。但是如果在pts/1中执行命令: bash a.sh,在pts/2中执行命令: bash b.sh,在pts/3中执行命令: ps -ef,即显式指定bash来读取脚本并执行命令,那么在ps -ef的输出可以看到a.sh和b.sh都是被bash执行的。

3. echo -n

echo命令使用 -n 参数时,不会换行。(P203)

[jicanmeng@andy tmp]$ cat a.sh
                    #!/bin/bash
                    echo "Today is: "
                    date
                [jicanmeng@andy tmp]$ cat b.sh
                    #!/bin/bash
                    echo -n "Today is: "
                    date
                [jicanmeng@andy tmp]$ ./a.sh
                    Today is:
                    Thu May  5 23:00:38 CST 2016
                [jicanmeng@andy tmp]$ ./b.sh
                    Today is: Thu May  5 23:00:40 CST 2016
                [jicanmeng@andy tmp]$ 

4. bash shell中的变量(P205)

  1. 组成变量的字符可以是字母、数字和下划线,并且变量不能以数字开头。
  2. 有两种方法来引用变量的值:
    1. 在变量前加$。例如$PATH
    2. 将变量用{}括起来,然后前面添加$。例如${PATH}
    另外要注意的是,当一个变数名称尚未被设定时,预设的内容是‘空’的。
  3. 给变量赋值时,变量、等号和值之间不能包含空格。格式为variable=value
  4. shell脚本会自动决定变量值的数据类型????
  5. 变数内容若有空白字元,可使用双引号或单引号将变数内容结合起来。不过,双引号内的特殊字元如 $ 等,可以保有原本的特性;而单引号内的特殊字元则仅为一般字元 (纯文字)。如下所示:
    [jicanmeng@andy tmp]$ var="lang is $LANG"
                            [jicanmeng@andy tmp]$ echo $var
                                lang is en_US.UTF-8
                            [jicanmeng@andy tmp]$ var='lang is $LANG'
                            [jicanmeng@andy tmp]$ echo $var
                                lang is $LANG
                            [jicanmeng@andy tmp]$ 
  6. 可用跳脱字元‘ \ ’将特殊符号(如 $, \, 空白字元, ', "等)变成一般字元。例如:
    [jicanmeng@andy tmp]$ name=canmeng\"\$
                            [jicanmeng@andy tmp]$ echo $name
                                canmeng"$
                            [jicanmeng@andy tmp]$ name=can\ meng
                            [jicanmeng@andy tmp]$ echo $var
                                can meng
                            [jicanmeng@andy tmp]$ 
  7. 为变数扩增变数内容时,则可用 "$变数名称"${变数} 累加内容。如下所示:
    [jicanmeng@andy tmp]$ name=canmeng
                            [jicanmeng@andy tmp]$ name="$name"One
                            [jicanmeng@andy tmp]$ echo $name
                                canmengOne
                            [jicanmeng@andy tmp]$ name=${name}Two
                            [jicanmeng@andy tmp]$ echo $name
                                canmengOneTwo
                            [jicanmeng@andy tmp]$ 
    在不引起混淆的情况下,也可以直接用 $变数内容 来累加内容。例如:
    [jicanmeng@andy tmp]$ name=canmeng
                            [jicanmeng@andy tmp]$ name=$nameOne
                            [jicanmeng@andy tmp]$ echo $name
    
                            [jicanmeng@andy tmp]$ name=$name:Two
                            [jicanmeng@andy tmp]$ echo $name
                                :Two
                            [jicanmeng@andy tmp]$ name=$name" Three"
                            [jicanmeng@andy tmp]$ echo $name
                                :Two Three
                            [jicanmeng@andy tmp]$ 
    可以看到,第一个例子引起了混淆,因为根本就没有为名称为nameOne的变量,所以name变量值为空。第二个例子和第三个例子都没有引起混淆,因为变量名的组成字符只能是字母、数字和下划线。不过,最好我们还是用标准的方法,使用 "$变数名称"${变数} 的形式。
  8. 在一串指令中,还需要藉由其他的指令提供的资讯,常用的有两种方法:可以使用反单引号 `指令` 或小括号 $(指令) 。特别注意,那个 ` 是键盘上方的数字键 1 左边那个按键,而不是单引号! 例如想要取得核心版本的设定:
    [jicanmeng@andy tmp]$ version=$(uname -r)
                            [jicanmeng@andy tmp]$ echo $version
                                2.6.32-431.el6.x86_64
                            [jicanmeng@andy tmp]$ version=`uname -r`
                            [jicanmeng@andy tmp]$ echo $version
                                2.6.32-431.el6.x86_64
                            [jicanmeng@andy tmp]$ cd /lib/modules/`uname -r`/kernel
                            [jicanmeng@andy tmp]$ pwd
                                /lib/modules/2.6.32-431.el6.x86_64/kernel
                            [jicanmeng@andy tmp]$ 
  9. 取消变量的方法是使用unset命令:unset 变量名称

5. 输入输出重定向基本知识

使用符号>进行输出重定向。使用符号<进行输入重定向。(P208)

使用符号>>进行输出重定向,和>差异如下:>直接覆盖重定向的文件的原有内容,而>>则在重定向的文件的尾部追加数据。

还有另外一种输入重定向的方法,称为内联输入重定向(inline input redirection)。这种方法允许你在命令行而不是从文件指定输入被重定向的数据。内联输入重定向的符号是<<。除了这个符号,你必须指定一个文本标记来划分要输入数据的开始和结尾。你可以用任何字符串的值来作为文本标记,但在数据的开始和结尾必须一致。例如:

[jicanmeng@andy tmp]$ cat > version-check.sh << "EOF"
                    > gcc -v
                    > gdb -v
                    > EOF
                [jicanmeng@andy tmp]$ ls
                    version-check.sh
                [jicanmeng@andy tmp]$ cat version-check.sh
                    gcc -v
                    gdb -v
                [jicanmeng@andy tmp]$ echo $PS2
                    >
                [jicanmeng@andy tmp]$ 

在命令行上使用内联输入重定向时,shell会用PS2环境变量中定义的次提示符来提示输入数据。由上面最后一条命令也可以看出来,次提示符是>。

准确的说,<是对文件描述符STDIN重定向,>是对文件描述符STDOUT重定向。

6. 管道基本知识

管道的符号是|,放在命令之间,用于将一个命令的输出重定向到另一个命令的输入上。(P210)

不要以为管道链接会一个一个地运行。Linux系统实际上会同时运行这两个命令,在系统内部将它们连接起来???(第一个命令还没有运行完,第二个命令即使运行也没有输入呀?)

7. bash shell中的数学运算的两种方法

shell中有两种途径来进行数学运算操作。

  1. expr命令。缺点是有的符号需要进行转义。例如:
    [jicanmeng@andy tmp]$ var1=`expr 5 * 2`
                            expr: syntax error
                        [jicanmeng@andy tmp]$ var1=`expr 5 \* 2`
                        [jicanmeng@andy tmp]$ echo $var1
                            10
                        [jicanmeng@andy tmp]$ 
    在实际使用中,这种方法比较少见,我们要重点关注第二种方法。
  2. bash shell为了保持跟Bourne shell的兼容而包含了expr命令,但它同时也提供了一个执行数学表达式的简单方法。在bash中,将一个数学运算结果赋值给某个变量时,你可以用美元符和方括号($[ operation ])将数学表达式圈起来。例如:
    [jicanmeng@andy tmp]$ var1=$[5*2]
                        [jicanmeng@andy tmp]$ echo $var1
                            10
                        [jicanmeng@andy tmp]$ var1=$[ 5 * 3 ]
                        [jicanmeng@andy tmp]$ echo $var1
                            15
                        [jicanmeng@andy tmp]$ 
    注意,在使用方括号时,不用担心shell会误解乘号*或其它符号。shell知道它不是通配符,因为它在方括号内。

8. bash shell中的浮点运算

bash shell数学运算符只支持整数运算。如果要在bash shell脚本中进行浮点数运算,我们可以使用bash计算器,称作bc。用法如下:

  1. bash计算器可以识别出:数字(整数和浮点数)、变量(简单变量和数组)、注释(以#开头的行或c语言中的/**/对)、表达式、编程语句(例如if-then语句)、函数。例如:
    例1:
                        [jicanmeng@andy tmp]$ echo "3*4" | bc
                            12
                        例2:
                        [jicanmeng@andy tmp]$ echo "scale=3;12.44/5" | bc
                            2.488
                        [jicanmeng@andy tmp]$
                        例3:
                        [jicanmeng@andy tmp]$ bc -q
                            12.44/5
                            2
                            scale=4
                            12.44/5
                            2.4880
                            var1=10
                            var1 * 3
                            30
                            var2 = 20
                            var3 = var2 * 5
                            print var3
                            100
                            quit
                        [jicanmeng@andy tmp]$ 
    几个地方需要注意:1. bc可以工作在交互模式,也可以工作在非交互模式; 2. 和bash shell不同,bc中变量的赋值表达式中,等号前后是可以有空格的; 3. 和bash shell不同,bc中的变量在引用的时候,是不需要加$符号的。4. scale内建变量表示浮点数的精度; 5. -q选项表示将bash计算器输出的长长的欢迎信息屏蔽掉。
  2. bash可以工作在非交互模式,而我们又常常用反引号来运行某个命令,利用这两个特性,我们在shell脚本中就可以处理浮点数了。具体做法是用反引号``来运行bc命令并赋值给一个变量。例如:
    例4:
                        [jicanmeng@andy tmp]$ var=`echo "scale = 4; 12.44 / 5" | bc`
                        [jicanmeng@andy tmp]$ echo $var
                            2.4880
                        [jicanmeng@andy tmp]$
                        例5:
                        [jicanmeng@andy tmp]$ cat test10
                            #!/bin/bash
                            var1=100
                            var2=45
                            var3=`echo "scale = 4; $var1 / $var2" | bc`
                            echo The answer is $var3
                        [jicanmeng@andy tmp]$ bash test10
                            The answer is 2.2222
                        [jicanmeng@andy tmp]$
                        例6:
                        [jicanmeng@andy tmp]$ cat test11
                            #!/bin/bash
                            var3=`echo "var1 = 100; var2 = 45; scale = 4; var1 / var2" | bc`
                            echo The answer is $var3
                        [jicanmeng@andy tmp]$ bash test11
                            The answer is 2.2222
                        [jicanmeng@andy tmp]$ 
    需要注意的是:在上面的例3中,引用变量时前面不需要加$符号,而在例5中,给var3变量赋值语句中,引用var2和var1变量时都用了$符号,因为这两个变量都不是bc定义的变量,而是bash定义的变量。从例5和例6相比较可以看出差异。
  3. 例6中给var3变量赋值的那一行太长,如果命令更长,那么可读性就比较差。可以使用内联输入重定向来进行简化。内联输入重定向时,每条命令占一行,命令后不需要添加分号;。如下:
    例7:
                        [jicanmeng@andy tmp]$ cat test12
                            #!/bin/bash
                            var1=100
                            var3=`bc << EOF
                            var2 = 45
                            scale = 4
                            $var1 / var2
                            EOF`
                            echo The answer if $var3
                        [jicanmeng@andy tmp]$ bash test12
                            The answer is 2.2222
                        [jicanmeng@andy tmp]$ 

9. bash shell中的退出状态码

shell中运行的每个命令都使用退出状态码(exit status)来告诉shell它完成了处理。可以通过 echo $? 命令来查看上个执行的命令的退出状态码。《鸟哥的linux私房菜》中有一个非常好的例子:

[jicanmeng@andy tmp]$ 12name=VBird
                    bash: 12name=VBird: command not found
                [jicanmeng@andy tmp]$ echo $?
                    127
                [jicanmeng@andy tmp]$ echo $?
                    0
                [jicanmeng@andy tmp]$ 

默认情况下,shell脚本的退出状态码就是脚本中最后一个命令的退出状态码。不过,我们可以通过exit命令在脚本结束运行时指定一个退出状态码。例如:

例1:
                [jicanmeng@andy tmp]$ cat test13
                    echo "hello,world"
                    exit 5
                [jicanmeng@andy tmp]$ bash test13
                    hello,world
                [jicanmeng@andy tmp]$ echo $?
                    5
                [jicanmeng@andy tmp]$
                例2:
                [jicanmeng@andy tmp]$ cat test14
                    echo "hello,world"
                    var=3
                    exit $var
                [jicanmeng@andy tmp]$ bash test14
                    hello,world
                [jicanmeng@andy tmp]$ echo $?
                    3
                [jicanmeng@andy tmp]$ 
需要注意的是,退出状态码最大只能是255。

10. bash shell中命令的执行过程是怎样的?

在bash shell中下达的命令,会启动一个新的shell,在这个新的shell中执行命令,执行完毕后会回到原来的shell。当下达的命令是shell的 built-in指令时,才不会新创建一个子shell。子shell会继承父shell的环境变量(environment variables)。我们可以在父shell中执行export variableName来将一个变量变为环境变量,这样在子shell中也可以使用这个变量的值了。

11. 5个问题

  1. 上面提到,shell脚本会自动决定变量值的数据类型???? 是不是bash shell变量的类型就两种:整型和字符串型???
  2. 上面也提到,管道符前后的两条命令会同时运行。不明白...
  3. name=canmeng ls这条命令为什么可以执行??? 先定义一个变量,再执行一条命令,书里面并没有提到这种格式啊???
  4. cat a.txtcat < a.txt两条命令都可以显示a.txt文件的内容,那么这两条命令有什么区别呢? 答:如果cat命令没有输入参数,那么它会从标准输入STDIN读取数据,如果有,就从输入参数对应的文件读取数据。所以区别是cat < a.txt命令从标准输入读取的数据,但标准输入被重定向到了a.txt文件中。
  5. 上面的示例程序test12中,在给var3变量赋值的那一行,如果给字符EOF添加双引号,那么脚本运行的时候就会提示出错。但是在如果直接在命令行上输入,就没有问题,如下:
    例1:
                    [jicanmeng@andy tmp]$ var6=`bc << "EOF"
                        > var1 = 100
                        > var2 = 45
                        > scale = 3
                        > var1 / var2
                        > EOF`
                    [jicanmeng@andy tmp]$ echo $var6
                        2.222
                    [jicanmeng@andy tmp]$ 
    于是问题来了,为什么在命令上直接输入没有问题,而写到脚本中就出错呢?

参考资料

  1. Lnux命令行与shell脚本编程大全
  2. 鸟哥的linux私房菜:
    http://linux.vbird.org/linux_basic/0320bash.php