高级Bash脚本编程指南(二)
时间:2007-03-20 来源:qyelite
4.1 变量替换
------------
$ 变量替换操作符
只有在变量被声明,赋值,unset或exported或者是在变量代表一个signal的时候,
变量才会是以本来的面目出现在脚本里.变量在被赋值的时候,可能需要使用"=",
read状态或者是在循环的头部.
在""中还是会发生变量替换,这被叫做部分引用,或叫弱引用.而在''中就不会发生变
量替换,这叫做全引用,也叫强引用.具体见第5章的讨论. 注意:$var与${var}的区别,不加{},在某些上下文将引起错误,为了安全,使用2.
具体见9.3节 参数替换. Example 4-1. 变量赋值和替换
################################Start Script#######################################
1 #!/bin/bash
2
3 # 变量赋值和替换
4
5 a=375
6 hello=$a
7
8 #-------------------------------------------------------------------------
9 # 强烈注意,在赋值的前后一定不要有空格.
10 # 如果有空格会发生什么?
11
12 # 如果"VARIABLE =value",
13 # ^
14 #+ 脚本将尝试运行一个"VARIABLE"的命令,带着一个"=value"参数.
15
16 # 如果"VARIABLE= value",
17 # ^
18 #+ script tries to run "value" command with
18 #+ 脚本将尝试运行一个"value"的命令,带着
19 #+ the environmental variable "VARIABLE" set to "".
19 #+ 一个被赋成""值的环境变量"VARIABLE".
20 #-------------------------------------------------------------------------
21
22
23 echo hello # 没有变量引用,不过是个hello字符串
24
25 echo $hello
26 echo ${hello} # 同上
27
28 echo "$hello"
29 echo "${hello}"
30
31 echo
32
33 hello="A B C D"
34 echo $hello # A B C D
35 echo "$hello" # A B C D
36 # 就象你看到的echo $hello 和 echo "$hello" 将给出不同的结果.
37 # ^ ^
38 # Quoting a variable preserves whitespace.
38 # 引用一个变量将保留其中的空白,当然,如果是变量替换就不会保留了.
39
40 echo
41
42 echo '$hello' # $hello
43 # ^ ^
44 # 全引用的作用
45 #+ 将导致"$"变成一个单独的字符.
46
47 # 注意两种引用不同的效果
48
49
50 hello= # 设置为空值
51 echo "\$hello (null value) = $hello"
52 # 注意设置一个变量为空,与unset它,不是一回事,虽然看起来一样
53 #
54
55 # --------------------------------------------------------------
56
57 # 可以在同一行上设置多个变量.
58 #+ 要以空白分隔
59 # 小心,这会降低可读性,和可移植性.
60
61 var1=21 var2=22 var3=$V3
62 echo
63 echo "var1=$var1 var2=$var2 var3=$var3"
64
65 # 在老版本的"sh"上,可能会有问题.
66
67 # --------------------------------------------------------------
68
69 echo; echo
70
71 numbers="one two three"
72 # ^ ^
73 other_numbers="1 2 3"
74 # ^ ^
75 # 如果变量中有空白,那么引用就必要了.
76 #
77 echo "numbers = $numbers"
78 echo "other_numbers = $other_numbers" # other_numbers = 1 2 3
79 echo
80
81 echo "uninitialized_variable = $uninitialized_variable"
82 # Uninitialized变量为空值(根本就没赋值).
83 uninitialized_variable= # 声明,但是没被初始化
84 #+ 其实和前边设置为空值得作用是一样的.
85 echo "uninitialized_variable = $uninitialized_variable"
86 # 还是一个空值
87
88 uninitialized_variable=23 # 赋值
89 unset uninitialized_variable # Unset it.
90 echo "uninitialized_variable = $uninitialized_variable"
91 # 还是空值
92 echo
93
94 exit 0
################################End Script#########################################
注意: 一个空值变量,或者是根本就没声明的变量,在赋值之前使用它可能会引起问题.
但是还是可以用来做算术运算
################################Start Script#######################################
1 echo "$uninitialized" # (blank line)
2 let "uninitialized += 5" # Add 5 to it.
3 echo "$uninitialized" # 5
4
5 # 结论:
6 # 对于一个空值变量在做算术操作的时候,就好像它的值为0一样.
8 # This is undocumented (and probably non-portable) behavior.
7 # 这并没被文档化(可能是不可移植)的行为.
################################End Script#########################################
具体参考 Example 11-21
4.2 变量赋值
------------
= 赋值操作符(前后都不能有空白)
不要与-eq混淆,那个是test,并不是赋值.
注意,=也可被用来做test操作,这依赖于上下文. Example 4-2. 一般的变量赋值
################################Start Script#######################################
1 #!/bin/bash
2 # "裸体"变量
3
4 echo
5
6 # 变量什么时候是"裸体"的,比如前边少了$的时候.
7 # 当它被赋值的时候,而不是被引用的时候.
8
9 # 赋值
10 a=879
11 echo "The value of \"a\" is $a."
12
13 # 使用let赋值
14 let a=16+5
15 echo "The value of \"a\" is now $a."
16
17 echo
18
19 # 在for循环中
20 echo -n "Values of \"a\" in the loop are: "
21 for a in 7 8 9 11
22 do
23 echo -n "$a "
24 done
25
26 echo
27 echo
28
29 # 在read命令状态中
30 echo -n "Enter \"a\" "
31 read a
32 echo "The value of \"a\" is now $a."
33
34 echo
35
36 exit 0
################################End Script######################################### Example 4-3. 变量赋值,一般的和比较特殊的
################################Start Script#######################################
1 #!/bin/bash
2
3 a=23 # Simple case
4 echo $a
5 b=$a
6 echo $b
7
8 # 现在让我们来点小变化
9
10 a=`echo Hello!` # 把echo命令的结果传给变量a
11 echo $a
12 # 注意,如果在命令扩展结构中使用一个(!)的话,在命令行中将不能工作
13 #+ 因为这触发了Bash的"历史机制".
14 # 但是,在校本里边使用的话,历史功能是被关闭的,所以就能够正常运行.
15
16
17 a=`ls -l` # 把ls -l的结果给a
18 echo $a # 别忘了,这么引用的话,ls的结果中的所有空白部分都没了(包括换行)
19 echo
20 echo "$a" # 这么引用就正常了,保留了空白
21 # (具体参阅章节"引用")
22
23 exit 0
################################End Script#########################################
使用$(...)机制进行的变量赋值(除去使用``来赋值的另外一种新方法).事实上这两种方法都是
命令替换的一种形式.
# 来自于/ect/rc.d/rc.local
R=$(cat /ect/redhat-release)
arch=$(uname -m)
4.3 Bash变量是不分类型的
------------------------
不像其他程序语言一样,Bash并不对变量区分"类型".本质上,Bash变量都是字符串.
但是依赖于上下文,Bash也允许比较操作和算术操作.决定这些的关键因素就是,变量中的值
是否只有数字. Example 4-4 整型还是string?
################################Start Script#######################################
1 #!/bin/bash
2 # int-or-string.sh: 整形还是string?
3
4 a=2334 # 整型
5 let "a += 1"
6 echo "a = $a " # a = 2335
7 echo # 还是整型
8
9
10 b=${a/23/BB} # 将23替换成BB
11 # 这将把b变量从整型变为string
12 echo "b = $b" # b = BB35
13 declare -i b # 即使使用declare命令也不会对此有任何帮助,9.4节有解释
14 echo "b = $b" # b = BB35
15
16 let "b += 1" # BB35 + 1 =
17 echo "b = $b" # b = 1
18 echo
19
20 c=BB34
21 echo "c = $c" # c = BB34
22 d=${c/BB/23} # S将BB替换成23
23 # 这使得$d变为一个整形
24 echo "d = $d" # d = 2334
25 let "d += 1" # 2334 + 1 =
26 echo "d = $d" # d = 2335
27 echo
28
29 # 关于空变量怎么样?
30 e=""
31 echo "e = $e" # e =
32 let "e += 1" # 算术操作允许一个空变量?
33 echo "e = $e" # e = 1
34 echo # 空变量将转换成一个整型变量
35
36 # 关于未声明的变量怎么样?
37 echo "f = $f" # f =
38 let "f += 1" # 算术操作允许么?
39 echo "f = $f" # f = 1
40 echo # 未声明的变量将转换成一个整型变量
41
42
43
44 # 所以说Bash中的变量都是无类型的.
45
46 exit 0
################################End Script#########################################
4.4 特殊的变量类型
------------------
local variables
这种变量只有在代码块或者是函数中才可见(具体见23.2和23章)
environmental variables
这种变量将改变用户接口和shell的行为. 在一般的上下文中,每个进程都有自己的环境,就是一组保持进程可能引用的信息的
变量.这种情况下,shell于一个一般进程是相同的. 每次当shell启动时,它都将创建自己的环境变量.更新或者添加新的环境变量,将导
致shell更新它的环境,同时也会影响所有继承自这个环境的所有子进程(由这个命令
导致的). 注意:分配给环境变量的空间是受限的.创建太多的环境变量将引起空间溢出,这会引
起问题.
关于eval命令,具体见第11章
bash$ eval "`seq 10000 | sed -e 's/.*/export var&=ZZZZZZZZZZZZZZ/'`"
bash$ du
bash: /usr/bin/du: Argument list too long 如果一个脚本设置了环境变量,需要export它,来通知本脚本的环境,这是export
命令的功能,关于export命令,具体见11章.
脚本只能对它产生的子进程export变量.一个从命令行被调用的脚本export的变量,将
不能影响调用这个脚本的那个命令行shell的环境. positional parameters
就是从命令行中传进来的参数,$0, $1, $2, $3... $0就是脚本文件的名字,$1是第一个参数,$2为第2个...,参见[1](有$0的说明),$9
以后就需要打括号了,如${10},${11},${12}...
两个值得注意的变量$*和$@(第9章有具体的描述),表示所有的位置参数. Example 4-5 位置参数
################################Start Script#######################################
1 #!/bin/bash
2
3 # 作为用例,调用这个脚本至少需要10个参数,如
4 # ./scriptname 1 2 3 4 5 6 7 8 9 10
5 MINPARAMS=10
6
7 echo
8
9 echo "The name of this script is \"$0\"."
10 # 添加./是为了当前目录
11 echo "The name of this script is \"`basename $0`\"."
12 # 去掉目录信息,具体见'basename'命令
13
14 echo
15
16 if [ -n "$1" ] # 测试变量被被引用
17 then
18 echo "Parameter #1 is $1" # "#"没被转义
19 fi
20
21 if [ -n "$2" ]
22 then
23 echo "Parameter #2 is $2"
24 fi
25
26 if [ -n "$3" ]
27 then
28 echo "Parameter #3 is $3"
29 fi
30
31 # ...
32
33
34 if [ -n "${10}" ] # 大于9的参数必须出现在{}中.
35 then
36 echo "Parameter #10 is ${10}"
37 fi
38
39 echo "-----------------------------------"
40 echo "All the command-line parameters are: "$*""
41
42 if [ $# -lt "$MINPARAMS" ] #$#是传到脚本里的位置参数的个数
43 then
44 echo
45 echo "This script needs at least $MINPARAMS command-line arguments!"
46 fi
47
48 echo
49
50 exit 0
################################End Script#########################################
{}标记法是一种很好的使用位置参数的方法.这也需要间接引用(见Example 34-2)
1 args=$# # 位置参数的个数
2 lastarg=${!args}
3 # 或: lastarg=${!#}
4 # 注意 lastarg=${!$#} 将报错 一些脚本可能会依赖于使用不同的调用名字,而表现出不同的行为,这样一般都需要
判断$0,而其他的名字都是通过ln命令产生的链接.(具体参见Example 12-2) 如果脚本需要一个命令行参数,而调用的时候,没用这个参数,这就有可能造成分配一个
空变量,这样估计就会引起问题.一种解决办法就是在这个位置参数,和相关的变量后
边,都添加一个额外的字符.具体见下边的例子.
################################Start Script#######################################
1 variable1_=$1_ # 而不是 variable1=$1
2 # 这将阻止一个错误,即使在调用时没使用这个位置参数.
3
4 critical_argument01=$variable1_
5
6 # 这个扩展的字符是可以被消除掉的,就像这样.
7 variable1=${variable1_/_/}
8 # 副作用就是$variable1_多了一个下划线
9 # 这里使用了一个参数替换模版(后边会有具体的讨论)
10 # (Leaving out the replacement pattern results in a deletion.)
10 # (在一个删除动作中,节省了一个替换模式)
11
12
13 # 一个解决这种问题的更简单的做法就是,判断一下这个位置参数是否传递下来了
14 if [ -z $1 ]
15 then
16 exit $E_MISSING_POS_PARAM
17 fi
18
19
20 # 但是上边的方法将可能产生一个意外的副作用
21 # 参数替换的更好的办法应该是:
22 # ${1:-$DefaultVal}
23 # 具体察看"Parameter Substition"节
24 #+ 在第9章
################################End Script#########################################
Example 4-6 wh,whois节点名字查询
################################Start Script#######################################
1 #!/bin/bash
2 # ex18.sh
3
4 # Does a 'whois domain-name' lookup on any of 3 alternate servers:
5 # ripe.net, cw.net, radb.net
6
7 # 把这个脚本重命名为'wh',然后放到/usr/local/bin下
8
9 # 需要3个符号链接
10 # ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
11 # ln -s /usr/local/bin/wh /usr/local/bin/wh-cw
12 # ln -s /usr/local/bin/wh /usr/local/bin/wh-radb
13
14 E_NOARGS=65
15
16
17 if [ -z "$1" ]
18 then
19 echo "Usage: `basename $0` [domain-name]"
20 exit $E_NOARGS
21 fi
22
23 # Check script name and call proper server.
23 # 检查脚本名字,然后调用合适的服务器
24 case `basename $0` in # Or: case ${0##*/} in
25 "wh" ) whois [email protected];;
26 "wh-ripe") whois [email protected];;
27 "wh-radb") whois [email protected];;
28 "wh-cw" ) whois [email protected];;
29 * ) echo "Usage: `basename $0` [domain-name]";;
30 esac
31
32 exit $?
################################End Script######################################### shift shift命令重新分配位置参数,其实就是向左移动一个位置.
$1 <--- $2, $2 <--- $3, $3 <--- $4, 等等.
老的$1将消失,但是$0(脚本名)是不会改变的.如果你使用了大量的位置参数,那么
shift命令允许你存取超过10个参数.虽然{}表示法也允许这样.
Example 4-7 使用shift
################################Start Script#######################################
1 #!/bin/bash
2 # 使用'shift'来穿过所有的位置参数.
3
4 # 把这个脚本命名为shft,
5 #+ 并且使用一些参数来调用它,如:
6 # ./shft a b c def 23 skidoo
7
8 until [ -z "$1" ] # 知道所有参数都用光
9 do
10 echo -n "$1 "
11 shift
12 done
13
14 echo # 额外的换行.
15
16 exit 0
################################End Script#########################################
在将参数传递到函数中时,shift的工作方式也基本差不多.具体见Example 33-15 注意事项:
[1] 进程调用设置$0参数的脚本.一般的,这个参数就是脚本名字.具体察看execv的man页. 第5章 引用(翻译的可能有问题,特指引号)
======================================
引号的特殊效果就是,保护字符串中的特殊字符不被shell或者是shell脚本重新解释或者扩展.
(我们这里所说的"特殊"指的是一些字符在shell中具有的特殊意义,比如*)
如:
bash$ ls -l [Vv]*
-rw-rw-r-- 1 bozo bozo 324 Apr 2 15:05 VIEWDATA.BAT
-rw-rw-r-- 1 bozo bozo 507 May 4 14:25 vartrace.sh
-rw-rw-r-- 1 bozo bozo 539 Apr 14 17:11 viewdata.sh
bash$ ls -l '[Vv]*'
ls: [Vv]*: No such file or directory 在我们一般的生活中,引号内的内容往往有特殊的含义,而在Bash中,当我们引用一个字符串,
我们是保护它的字面含义. 特定的程序和工具能够重新解释或扩展特殊的字符.引用的一个重要的作用就是保护命令行中
的参数,但还是允许正在调用的程序来扩展它.
bash$ grep '[Ff]irst' *.txt
file1.txt:This is the first line of file1.txt.
file2.txt:This is the First line of file2.txt. 注意 grep [Ff]irst *.txt在Bash下的行为(其实就是正则表达式么),[1] 引用还可以抑制echo命令的换行作用. bash$ echo $(ls -l)
total 8 -rw-rw-r-- 1 bozo bozo 130 Aug 21 12:57 t222.sh -rw-rw-r-- 1 bozo bozo 78 Aug 21 12:57 t71.sh bash$ echo "$(ls -l)"
total 8
-rw-rw-r-- 1 bozo bozo 130 Aug 21 12:57 t222.sh
-rw-rw-r-- 1 bozo bozo 78 Aug 21 12:57 t71.sh
5.1 引用变量
------------
在一个双引号中直接使用变量名,一般都是没有问题的.它阻止了所有在引号中的特殊字符的
重新解释--包括变量名[2]--但是$,`和\除外.[3]保留$,作为特殊字符的意义,是为了能够在双
引号中也能够正常地引用变量("$var").这样在""中可以使用变量所表达的值(Example 4-1). 使用""来防止单词分割.[4]如果在参数列表中使用双引号,将使得双引号中的参数作为一个参
数.即使双引号中的字符串包含多个单词(也就是包含空白部分),也不会变为多个参数,如:
1 variable1="a variable containing five words"
2 COMMAND This is $variable1 # COMMAND将以7个参数来执行
3 # "This" "is" "a" "variable" "containing" "five" "words"
4
5 COMMAND "This is $variable1" # COMMAND将以1个参数来执行
6 # "This is a variable containing five words"
7
8
9 variable2="" # 空值
10
11 COMMAND $variable2 $variable2 $variable2 # COMMAND将不带参数执行
12 COMMAND "$variable2" "$variable2" "$variable2" # COMMAND将以3个空参数来执行
13 COMMAND "$variable2 $variable2 $variable2" # COMMAND将以1个参数来执行(2空格)
用双引号把参数封到echo中是很有必要的,只有在单词分隔或时保留空白时的时候可能
有些问题. Example 5-1 echo一些诡异的变量
################################Start Script#######################################
1 #!/bin/bash
2 # weirdvars.sh: echo诡异的变量
3
4 var="'(]\\{}\$\""
5 echo $var # '(]\{}$"
6 echo "$var" # '(]\{}$" 并没有什么不同
7
8 echo
9
10 IFS='\'
11 echo $var # '(] {}$" \ 转换成空格了?明显和IFS有关系么!又不傻!
12 echo "$var" # '(]\{}$"
13
14 exit 0
################################End Script######################################### 单引号操作总体上和""很像,但不允许引用变量.因为$的特殊含义被关闭了.在''中除了',其他
字符都没有特殊的含义了.所以单引号比双引号严格.
因为即使是\,在''中都被关闭了,所以你想在''中显示'的含义,将得不到预期的效果.
1 echo "Why can't I write 's between single quotes"
2
3 echo
4
5 # 一种绕弯的方法
6 echo 'Why can'\''t I write '"'"'s between single quotes'
7 # |-------| |----------| |-----------------------|
8 # 包含了2个单引号字符,原书好像有错误 注意事项:
[1] 除非当前目录下,正好有个叫first的文件.
[2] 即使是变量的值也是有副作用的(见下边)
[3] 如果在""中包含"!"的话,在命令行中将会出现错误.因为这个"!"被当作历史命令来解释了.
在一个脚本中,这种情况是不会发生的,因为在脚本中,Bash历史记录被关闭了. 下边是一些关于"\"一些不协调的行为.
bash$ echo hello\!
hello! bash$ echo "hello\!"
hello\! bash$ echo -e x\ty
xty bash$ echo -e "x\ty"
x y [4] "单词分隔",在这个上下文中意味着,将一个字符串分隔为一些分离的参数.
5.2 转义(\)
-----------
转义是一种引用单个字符的方法.一个具有特殊含义的字符前边放上一个转义符(\)就告诉shell
这个字符失去了特殊的含义.
值得注意的是,在某些特定的命令和工具中,比如echo和sed,转义符往往会起到相反的效果,
它反倒有可能引发出这个字符特殊的含义. 对于特定的转义符的特殊的含义
在echo和sed中所使用的
\n 意味着新的一行
\r 回车
\t tab键
\v vertical tab(垂直tab),查前边的Ctl-K
\b backspace,查前边的Ctl-H
\a "alert"(如beep或flash)
\0xx 转换成8进制ASCII解码,等价于oxx Example 5-2 转义符
################################Start Script#######################################
1 #!/bin/bash
2 # escaped.sh: 转义符
3
4 echo; echo
5
6 echo "\v\v\v\v" # 逐字的打印\v\v\v\v .
7 # 使用-e选项的echo命令来打印转义符
8 echo "============="
9 echo "VERTICAL TABS"
10 echo -e "\v\v\v\v" # Prints 4 vertical tabs.
11 echo "=============="
12
13 echo "QUOTATION MARK"
14 echo -e "\042" # 打印" (引号, 8进制的ASCII 码就是42).
15 echo "=============="
16
17 # The $'\X' construct makes the -e option unnecessary.
17 # 如果使用$'\X'结构,那-e选项就不必要了
18 echo; echo "NEWLINE AND BEEP"
19 echo $'\n' # 新行.
20 echo $'\a' # Alert (beep).
21
22 echo "==============="
23 echo "QUOTATION MARKS"
24 # 版本2以后Bash允许使用$'\nnn'结构
25 # 注意这种情况,'\nnn\是8进制
26 echo $'\t \042 \t' # Quote (") framed by tabs.
27
28 # 当然,也可以使用16进制的值,使用$'\xhhh' 结构
29 echo $'\t \x22 \t' # Quote (") framed by tabs.
30
31 # 早一点的Bash版本允许'\x022'这种形式
32 echo "==============="
33 echo
34
35
36 # 分配ASCII字符到变量中
37 # ---------------------
38 quote=$'\042' # \042是",分配到变量中
39 echo "$quote This is a quoted string, $quote and this lies outside the quotes."
40
41 echo
42
43 # Concatenating ASCII chars in a variable.
43 # 变量中的连续的ASCII char.
44 triple_underline=$'\137\137\137' # 137 是8进制的ASCII 码'_'.
45 echo "$triple_underline UNDERLINE $triple_underline"
46
47 echo
48
49 ABC=$'\101\102\103\010' # 101, 102, 103 是8进制的码A, B, C.
50 echo $ABC
51
52 echo; echo
53
54 escape=$'\033' # 033 是8进制码for escape.
55 echo "\"escape\" echoes as $escape"
56 #"escape" echoes as 没有变量被输出
57
58 echo; echo
59
60 exit 0
################################End Script#########################################
另一个关于$''字符串扩展结果的例子见Example 34-1 \" 表达引号本身
1 echo "Hello" # Hello
2 echo "\"Hello\", he said." # "Hello", he said. \$ $号本身,跟在\$后的变量名,将不能扩展
1 echo "\$variable01" # 结果是$variable01 \\ \号本身.
1 echo "\\" # 结果是\
2
3 # 相反的 . . .
4
5 echo "\" # 这会出现第2个命令提示符,说白了就是提示你命令不全,你再补个"就
6 # 好了.如果是在脚本里,就会给出一个错误. 注意:\的行为依赖于它是否被转义,被"",或者是否在"命令替换"和"here document"中.
################################Start Script#######################################
1 # 简单的转义和""
2 echo \z # z
3 echo \\z # \z
4 echo '\z' # \z
5 echo '\\z' # \\z
6 echo "\z" # \z
7 echo "\\z" # \z
8
9 # 命令替换
10 echo `echo \z` # z
11 echo `echo \\z` # z
12 echo `echo \\\z` # \z
13 echo `echo \\\\z` # \z
14 echo `echo \\\\\\z` # \z
15 echo `echo \\\\\\\z` # \\z
16 echo `echo "\z"` # \z
17 echo `echo "\\z"` # \z
18
19 # Here document
20 cat <<EOF
21 \z
22 EOF # \z
23
24 cat <<EOF
25 \\z
26 EOF # \z
################################End Script######################################### 分配给变量的字符串的元素也会被转义,但是只把一个转义符分配给变量将会报错.
################################Start Script#######################################
1 variable=\
2 echo "$variable"
3 # Will not work - gives an error message:
3 # 将不能正常工作- 将给出一个错误消息:
4 # test.sh: : command not found
5 # 一个"裸体的" 转义符将不能够安全的分配给变量.
6 #
7 # What actually happens here is that the "\" escapes the newline and
7 # 这里其实真正发生的是variable=\,这句被shell认为是没有完成,\被认为是一个续行符
8 #+ 这样,下边的这句echo,也被认为是上一行的补充.所以,总的来说就是一个非法变量分配
9
10 variable=\
11 23skidoo
12 echo "$variable" # 23skidoo
13 # 这句就可以使用,因为这是一个合法的变量分配
14
15 variable=\
16 # \^ 转义一个空格
17 echo "$variable" # 显示空格
18
19 variable=\\
20 echo "$variable" # \
21
22 variable=\\\
23 echo "$variable"
24 # 不能正常工作,给出一个错误
25 # test.sh: \: command not found
26 #
27 # 第一个转义符把第2个\转义了,但是第3个又变成"裸体的"了,
28 #+ 与上边的例子的原因相同
29
30 variable=\\\\
31 echo "$variable" # \\
32 # 转了两个\
33 # 没问题
################################End Script######################################### 转义一个空格,在命令行参数列表中将会阻止单词分隔问题.
################################Start Script#######################################
1 file_list="/bin/cat /bin/gzip /bin/more /usr/bin/less /usr/bin/emacs-20.7"
2 # 列出的文件都作为命令的参数.
3
4 # Add two files to the list, and list all.
4 # 加2个文件到list中,并且列出全部.
5 ls -l /usr/X11R6/bin/xsetroot /sbin/dump $file_list
6
7 echo "-------------------------------------------------------------------------"
8
9 # 如果我们转义2个空格,会发生什么?
10 ls -l /usr/X11R6/bin/xsetroot\ /sbin/dump\ $file_list
11 # 错误: 因为前3个路径名被合并成一个参数传给了'ls -l'
12 # 因为2个转义符阻止了参数(单词)分离
################################End Script######################################### 转义符也提供续行功能.一般,每一行都包含一个不同的命令,但如果在行尾加上\,那就会接受
新行的输入,作为这一行的补充.
1 (cd /source/directory && tar cf - . ) | \
2 (cd /dest/directory && tar xpvf -)
3 # 重复了 Alan Cox的目录树拷贝命令
4 # 为了增加可读性分成2行.
5
6 # 也可以使用如下方式:
7 tar cf - -C /source/directory . |
8 tar xpvf - -C /dest/directory
9 # 察看下边的注意事项 注意:如果一个脚本以|(管道字符)结束.那么一个\(转义符),就不用非加上不可了.
但是一个好的shell脚本编写风格,还是应该在行尾加上\,以增加可读性.
################################Start Script#######################################
1 echo "foo
2 bar"
3 #foo
4 #bar
5
6 echo
7
8 echo 'foo
9 bar' # 没区别
10 #foo
11 #bar
12
13 echo
14
15 echo foo\
16 bar # 续行
17 #foobar
18
19 echo
20
21 echo "foo\
22 bar" # 与上边一样,\还是作为续行符
23 #foobar
24
25 echo
26
27 echo 'foo\
28 bar' # 由于是强引用,所以\没被解释成续行符
29 #foo\
30 #bar
################################End Script######################################### 第6章 退出和退出状态
====================
exit命令被用来结束脚本,就像C语言一样.他也会返回一个值来传给父进程,父进程会判断是否
可用. 每个命令都会返回一个exit状态(有时候也叫return状态).成功返回0,如果返回一个非0值,通
常情况下都会被认为是一个错误码.一个编写良好的UNIX命令,程序,和工具都会返回一个0作为
退出码来表示成功,虽然偶尔也会有例外. 同样的,脚本中的函数和脚本本身都会返回退出状态.在脚本或者是脚本函数中执行的最后的命
令会决定退出状态.在脚本中,exit nnn命令将会把nnn退出码传递给shell(nnn必须是10进制数
0-255). 当一个脚本以不带参数exit来结束时,脚本的退出状态就由脚本中最后执行命令来决定.
1 #!/bin/bash
2
3 COMMAND_1
4
5 . . .
6
7 # 将以最后的命令来决定退出状态
8 COMMAND_LAST
9
10 exit $? 1 #!/bin/bash
2
3 COMMAND1
4
5 . . .
6
7 # 将以最后的命令来决定退出状态
8 COMMAND_LAST $?读取最后执行命令的退出码.函数返回后,$?给出函数最后执行的那条命令的退出码.这种给
函数返回值的方法是Bash的方法.对于脚本来说也一样.总之,一般情况下,0为成功,非0失败W.
Example 6-1 exit/exit状态
################################Start Script#######################################
1 #!/bin/bash
2
3 echo hello
4 echo $? # 返回0,因为执行成功
5
6 lskdf # 不认识的命令.
7 echo $? # 返回非0值,因为失败了.
8
9 echo
10
11 exit 113 # 将返回113给shell.
12 # To verify this, type "echo $?" after script terminates.
12 # 为了验证这个,在脚本结束的地方使用"echo $?"
################################End Script######################################### $?对于测试脚本中的命令的结果特别有用(见Example 12-32和Example 12-17).
注意: !逻辑非操作,将会反转test命令的结果,并且这会影响exit状态.
Example 6-2 否定一个条件使用!
################################Start Script#######################################
1 true # true是shell内建命令,什么事都不做,就是shell返回0
2 echo "exit status of \"true\" = $?" # 0
3
4 ! true
5 echo "exit status of \"! true\" = $?" # 1
6 # 注意:"!"需要一个空格
7 # !true 将导致一个"command not found"错误
8 #
9 # 如果一个命令以'!'开头,那么将使用Bash的历史机制.就是显示这个命令被使用的历史.
10
11 true
12 !true
13 # 这次就没有错误了.
14 # 他不过是重复了之前的命令(true).
################################End Script######################################### 注意事项:
特定的退出码都有预定的含义(见附录D),用户不应该在自己的脚本中指定他. 第7章 Tests
===========
每个完整的合理的编程语言都具有条件判断的功能.Bash具有test命令,不同的[]和()操作,和
if/then结构.
7.1 Test结构
------------
一个if/then结构可以测试命令的返回值是否为0(因为0表示成功),如果是的话,执行更多命令. 有一个专用命令"["(左中括号,特殊字符).这个命令与test命令等价,但是出于效率上的考虑,
它是一个内建命令.这个命令把它的参数作为比较表达式或是文件测试,并且根据比较的结果,
返回一个退出码. 在版本2.02的Bash中,推出了一个新的[[...]]扩展test命令.因为这种表现形式可能对某些语
言的程序员来说更加熟悉.注意"[["是一个关键字,并不是一个命令. Bash把[[ $a -lt $b ]]看作一个单独的元素,并且返回一个退出码. ((...))和let...结果也能够返回一个退出码,当它们所测试的算术表达式的结果为非0的时候,
他们的退出码将返回0.这些算术扩展(见第15章)结构被用来做算术比较.
1 let "1<2" returns 0 (as "1<2" expands to "1")
2 (( 0 && 1 )) returns 1 (as "0 && 1" expands to "0") if命令可以测试任何命令,不仅仅是括号中的条件.
1 if cmp a b &> /dev/null # 阻止输出.
2 then echo "Files a and b are identical."
3 else echo "Files a and b differ."
4 fi
5
6 # 非常有用的"if-grep" 结构:
7 # ------------------------
8 if grep -q Bash file
9 then echo "File contains at least one occurrence of Bash."
10 fi
11
12 word=Linux
13 letter_sequence=inu
14 if echo "$word" | grep -q "$letter_sequence"
15 # "-q"选项是用来阻止输出
16 then
17 echo "$letter_sequence found in $word"
18 else
19 echo "$letter_sequence not found in $word"
20 fi
21
22
23 if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
24 then echo "Command succeeded."
25 else echo "Command failed."
26 fi 一个if/then结构可以包含多级比较和tests. 1 if echo "Next *if* is part of the comparison for the first *if*."
2
3 if [[ $comparison = "integer" ]]
4 then (( a < b ))
5 else
6 [[ $a < $b ]]
7 fi
8
9 then
10 echo '$a is less than $b'
11 fi Example 7-1 什么情况下为真?
################################Start Script#######################################
1 #!/bin/bash
2
3 # 技巧:
4 # 如果你不确定一个特定的条件如何判断.
5 #+ 在一个if-test结构中测试它.
6
7 echo
8
9 echo "Testing \"0\""
10 if [ 0 ] # zero
11 then
12 echo "0 is true."
13 else
14 echo "0 is false."
15 fi # 0 is true.
16
17 echo
18
19 echo "Testing \"1\""
20 if [ 1 ] # one
21 then
22 echo "1 is true."
23 else
24 echo "1 is false."
25 fi # 1 is true.
26
27 echo
28
29 echo "Testing \"-1\""
30 if [ -1 ] # -1
31 then
32 echo "-1 is true."
33 else
34 echo "-1 is false."
35 fi # -1 is true.
36
37 echo
38
39 echo "Testing \"NULL\""
40 if [ ] # NULL (控状态)
41 then
42 echo "NULL is true."
43 else
44 echo "NULL is false."
45 fi # NULL is false.
46
47 echo
48
49 echo "Testing \"xyz\""
50 if [ xyz ] # 字符串
51 then
52 echo "Random string is true."
53 else
54 echo "Random string is false."
55 fi # Random string is true.
56
57 echo
58
59 echo "Testing \"\$xyz\""
60 if [ $xyz ] # 测试$xyz是否为null,但是...(明显没人定义么!)
61 # 只不过是一个未定义的变量
62 then
63 echo "Uninitialized variable is true."
64 else
65 echo "Uninitialized variable is false."
66 fi # Uninitialized variable is false.
67
68 echo
69
70 echo "Testing \"-n \$xyz\""
71 if [ -n "$xyz" ] # 更学究的的检查
72 then
73 echo "Uninitialized variable is true."
74 else
75 echo "Uninitialized variable is false."
76 fi # Uninitialized variable is false.
77
78 echo
79
80
81 xyz= # 初始化了,但是将其设为空值
82
83 echo "Testing \"-n \$xyz\""
84 if [ -n "$xyz" ]
85 then
86 echo "Null variable is true."
87 else
88 echo "Null variable is false."
89 fi # Null variable is false.
90
91
92 echo
93
94
95 # 什么时候"flase"为true?
96
97 echo "Testing \"false\""
98 if [ "false" ] # 看起来"false"只不过是个字符串而已.
99 then
100 echo "\"false\" is true." #+ 并且它test的结果就是true.
101 else
102 echo "\"false\" is false."
103 fi # "false" is true.
104
105 echo
106
107 echo "Testing \"\$false\"" # 再来一个,未声明的变量
108 if [ "$false" ]
109 then
110 echo "\"\$false\" is true."
111 else
112 echo "\"\$false\" is false."
113 fi # "$false" is false.
114 # 现在我们终于得到了期望的结果
115
116 # 如果我们test这个变量"$true"会发生什么结果?答案是和"$flase"一样,都为空,因为我
117 #+ 们并没有定义它.
118 echo
119
120 exit 0
################################End Script#########################################
练习.解释上边例子的行为(我想我解释的已经够清楚了) 1 if [ condition-true ]
2 then
3 command 1
4 command 2
5 ...
6 else
7 # 可选的(如果不需要可以省去)
8 # 如果原始的条件测试结果是false,那么添加默认的代码来执行.
9 command 3
10 command 4
11 ...
12 fi 注意:当if和then在一个条件测试的同一行中的话,必须使用";"来终止if表达式.if和then都是
关键字.关键字(或者命令)作为一个表达式的开头,并且在一个新的表达式开始之前,必须
结束上一个表达式.
1 if [ -x "$filename" ]; then Else if和elif elif
elif是else if的缩减形式.
1 if [ condition1 ]
2 then
3 command1
4 command2
5 command3
6 elif [ condition2 ]
7 # Same as else if
8 then
9 command4
10 command5
11 else
12 default-command
13 fi 使用if test condition-true这种形式和if[condition-true]这种形式是等价的.向我们前边
所说的"["是test的标记.并且以"]"结束.在if/test中并不应该这么严厉,但是新版本的Bash
需要它. 注意:test命令是Bash的内建命令,用来测试文件类型和比较字符串.因此,在Bash脚本中,test
并不调用/usr/bin/test的二进制版本(这是sh-utils工具包的一部分).同样的,[并不调用
/usr/bin/[,被连接到/usr/bin/test.
bash$ type test
test is a shell builtin
bash$ type '['
[ is a shell builtin
bash$ type '[['
[[ is a shell keyword
bash$ type ']]'
]] is a shell keyword
bash$ type ']'
bash: type: ]: not found Example 7-2 几个等效命令test,/usr/bin/test,[],和/usr/bin/[
################################Start Script#######################################
1 #!/bin/bash
2
3 echo
4
5 if test -z "$1"
6 then
7 echo "No command-line arguments."
8 else
9 echo "First command-line argument is $1."
10 fi
11
12 echo
13
14 if /usr/bin/test -z "$1" # 与内建的test结果相同
15 then
16 echo "No command-line arguments."
17 else
18 echo "First command-line argument is $1."
19 fi
20
21 echo
22
23 if [ -z "$1" ] # 与上边代码的作用相同
24 # if [ -z "$1" 应该工作,但是...
25 #+ Bash相应一个缺少关闭中括号的错误消息.
26 then
27 echo "No command-line arguments."
28 else
29 echo "First command-line argument is $1."
30 fi
31
32 echo
33
34
35 if /usr/bin/[ -z "$1" ] # 再来一个,与上边代码的作用相同
36 # if /usr/bin/[ -z "$1" # 工作,但是给个错误消息
37 # # 注意:
38 # This has been fixed in Bash, version 3.x.
38 # 在ver 3.x上,这个bug已经被Bash修正了.
39 then
40 echo "No command-line arguments."
41 else
42 echo "First command-line argument is $1."
43 fi
44
45 echo
46
47 exit 0
###############################End Script######################################### [[]]结构比Bash的[]更加灵活,这是一个扩展的test命令,从ksh88继承过来的.
注意:在[[]]结构中,将没有文件扩展或者是单词分离,但是会发生参数扩展和命令替换.
1 file=/etc/passwd
2
3 if [[ -e $file ]]
4 then
5 echo "Password file exists."
6 fi
注意:使用[[]],而不是[],能够阻止脚本中的许多逻辑错误.比如,尽管在[]中将给出一个错误,
但是&&,||,<>操作还是能够工作在一个[[]]test之中.
注意:在if后边,test命令和[]或[[]]都不是必须的.如下:
1 dir=/home/bozo
2
3 if cd "$dir" 2>/dev/null; then # "2>/dev/null" hides error message.
4 echo "Now in $dir."
5 else
6 echo "Can't change to $dir."
7 fi
if命令将返回if后边的命令的退出码. 与此相似,当在一个在使用与或列表结构的时候,test或中括号的使用,也并不一定非的有if不可
1 var1=20
2 var2=22
3 [ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"
4
5 home=/home/bozo
6 [ -d "$home" ] || echo "$home directory does not exist." (())结构扩展并计算一个算术表达式的结果.如果表达式的结果为0,它将返回1作为退出码,或
者是"false".而一个非0表达式的结果将返回0作为退出码,或者是"true". Example 7-3 算数测试使用(( ))
################################Start Script#######################################
1 #!/bin/bash
2 # 算数测试
3
4 # The (( ... )) construct evaluates and tests numerical expressions.
4 # (( ... ))结构计算并测试算数表达式的结果.
5 # 退出码将与[ ... ]结构相反!
6
7 (( 0 ))
8 echo "Exit status of \"(( 0 ))\" is $?." # 1
9
10 (( 1 ))
11 echo "Exit status of \"(( 1 ))\" is $?." # 0
12
13 (( 5 > 4 )) # true
14 echo "Exit status of \"(( 5 > 4 ))\" is $?." # 0
15
16 (( 5 > 9 )) # false
17 echo "Exit status of \"(( 5 > 9 ))\" is $?." # 1
18
19 (( 5 - 5 )) # 0
20 echo "Exit status of \"(( 5 - 5 ))\" is $?." # 1
21
22 (( 5 / 4 )) # 除法也行
23 echo "Exit status of \"(( 5 / 4 ))\" is $?." # 0
24
25 (( 1 / 2 )) # 出发结果<1
26 echo "Exit status of \"(( 1 / 2 ))\" is $?." # 结果将为0
27 # 1
28
29 (( 1 / 0 )) 2>/dev/null # 除数为0的错误
30 # ^^^^^^^^^^^
31 echo "Exit status of \"(( 1 / 0 ))\" is $?." # 1
32
33 # What effect does the "2>/dev/null" have?
33 # "2>/dev/null"的作用是什么?
34 # 如果删除"2>dev/null"将会发生什么?
35 # Try removing it, then rerunning the script.
35 # 尝试删除它,然后再运行脚本.
36
37 exit 0
################################End Script#########################################
7.2 文件测试操作
----------------
返回true如果... -e 文件存在
-a 文件存在
这个选项的效果与-e相同.但是它已经被弃用了,并且不鼓励使用
-f file是一个regular文件(不是目录或者设备文件)
-s 文件长度不为0
-d 文件是个目录
-b 文件是个块设备(软盘,cdrom等等)
-c 文件是个字符设备(键盘,modem,声卡等等)
-p 文件是个管道
-h 文件是个符号链接
-L 文件是个符号链接
-S 文件是个socket
-t 关联到一个终端设备的文件描述符
这个选项一般都用来检测是否在一个给定脚本中的stdin[-t0]或[-t1]是一个终端
-r 文件具有读权限(对于用户运行这个test)
-w 文件具有写权限(对于用户运行这个test)
-x 文件具有执行权限(对于用户运行这个test)
-g set-group-id(sgid)标志到文件或目录上
如果一个目录具有sgid标志,那么一个被创建在这个目录里的文件,这个目录属于创建
这个目录的用户组,并不一定与创建这个文件的用户的组相同.对于workgroup的目录
共享来说,这非常有用.见<<UNIX环境高级编程中文版>>第58页.
-u set-user-id(suid)标志到文件上
如果运行一个具有root权限的文件,那么运行进程将取得root权限,即使你是一个普通
用户.[1]这对于需要存取系统硬件的执行操作(比如pppd和cdrecord)非常有用.如果
没有suid标志的话,那么普通用户(没有root权限)将无法运行这种程序.
见<<UNIX环境高级编程中文版>>第58页.
-rwsr-xr-t 1 root 178236 Oct 2 2000 /usr/sbin/pppd
对于设置了suid的文件,在它的权限标志中有"s".
-k 设置粘贴位,见<<UNIX环境高级编程中文版>>第65页.
对于"sticky bit",save-text-mode标志是一个文件权限的特殊类型.如果设置了这
个标志,那么这个文件将被保存在交换区,为了达到快速存取的目的.如果设置在目录
中,它将限制写权限.对于设置了sticky bit位的文件或目录,权限标志中有"t".
drwxrwxrwt 7 root 1024 May 19 21:26 tmp/
如果一个用户并不时具有stick bit位的目录的拥有者,但是具有写权限,那么用户只
能在这个目录下删除自己所拥有的文件.这将防止用户在一个公开的目录中不慎覆盖
或者删除别人的文件,比如/tmp(当然root或者是目录的所有者可以随便删除或重命名
其中的文件).
-O 你是文件的所有者.
-G 文件的group-id和你的相同.
-N 从文件最后被阅读到现在,是否被修改. f1 -nt f2
文件f1比f2新
f1 -ot f2
f1比f2老
f1 -ef f2
f1和f2都硬连接到同一个文件. ! 非--反转上边测试的结果(如果条件缺席,将返回true) Example 7-4 test死的链接文件
################################Start Script#######################################
1 #!/bin/bash
2 # broken-link.sh
3 # Written by Lee bigelow <[email protected]>
4 # Used with permission.
5
6 #一个真正有用的shell脚本来找出死链接文件并且输出它们的引用
7 #以便于它们可以被输入到xargs命令中进行处理 :)
8 #比如: broken-link.sh /somedir /someotherdir|xargs rm
9 #
10 #这里,不管怎么说,是一种更好的方法
11 #
12 #find "somedir" -type l -print0|\
13 #xargs -r0 file|\
14 #grep "broken symbolic"|
15 #sed -e 's/^\|: *broken symbolic.*$/"/g'
16 #
17 #但这不是一个纯粹的bash,最起码现在不是.
18 #小心:小心/proc文件系统和任何的循环链接文件.
19 ##############################################################
20
21
22 #如果没对这个脚本传递参数,那么就使用当前目录.
23 #否则就使用传递进来的参数作为目录来搜索.
24 #
25 ####################
26 [ $# -eq 0 ] && directorys=`pwd` || directorys=$@
27
28 #建立函数linkchk来检查传进来的目录或文件是否是链接和是否存在,
29 #并且打印出它们的引用
30 #如果传进来的目录有子目录,
31 #那么把子目录也发送到linkchk函数中处理,就是递归目录.
32 ##########
33 linkchk () {
34 for element in $1/*; do
35 [ -h "$element" -a ! -e "$element" ] && echo \"$element\"
36 [ -d "$element" ] && linkchk $element
37 # Of course, '-h' tests for symbolic link, '-d' for directory.
37 # 当然'-h'是测试链接,'-d'是测试目录.
38 done
39 }
40
41 #如果是个可用目录,那就把每个从脚本传递进来的参数都送到linkche函数中.
42 #如果不是,那就打印出错误消息和使用信息.
43 #
44 ################
45 for directory in $directorys; do
46 if [ -d $directory ]
47 then linkchk $directory
48 else
49 echo "$directory is not a directory"
50 echo "Usage: $0 dir1 dir2 ..."
51 fi
52 done
53
54 exit 0
################################End Script#########################################
Example 28-1, Example 10-7, Example 10-3, Example 28-3, 和Example A-1 也会说明文件
测试操作的使用过程. 注意事项:
[1] 小心suid,可能引起安全漏洞,但是不会影响shell脚本.
[2] 在当代UNIX系统中,已经不使用sticky bit了,只在目录中使用.
7.3 其他比较操作
----------------
二元比较操作符,比较变量或者比较数字.注意数字与字符串的区别. 整数比较 -eq 等于,如:if [ "$a" -eq "$b" ]
-ne 不等于,如:if [ "$a" -ne "$b" ]
-gt 大于,如:if [ "$a" -gt "$b" ]
-ge 大于等于,如:if [ "$a" -ge "$b" ]
-lt 小于,如:if [ "$a" -lt "$b" ]
-le 小于等于,如:if [ "$a" -le "$b" ]
< 小于(需要双括号),如:(("$a" < "$b"))
<= 小于等于(需要双括号),如:(("$a" <= "$b"))
> 大于(需要双括号),如:(("$a" > "$b"))
>= 大于等于(需要双括号),如:(("$a" >= "$b")) 字符串比较
= 等于,如:if [ "$a" = "$b" ]
== 等于,如:if [ "$a" == "$b" ],与=等价
注意:==的功能在[[]]和[]中的行为是不同的,如下:
1 [[ $a == z* ]] # 如果$a以"z"开头(模式匹配)那么将为true
2 [[ $a == "z*" ]] # 如果$a等于z*(字符匹配),那么结果为true
3
4 [ $a == z* ] # File globbing 和word splitting将会发生
5 [ "$a" == "z*" ] # 如果$a等于z*(字符匹配),那么结果为true
一点解释,关于File globbing是一种关于文件的速记法,比如"*.c"就是,再如~也是.
但是file globbing并不是严格的正则表达式,虽然绝大多数情况下结构比较像.
!= 不等于,如:if [ "$a" != "$b" ]
这个操作符将在[[]]结构中使用模式匹配.
< 小于,在ASCII字母顺序下.如:
if [[ "$a" < "$b" ]]
if [ "$a" \< "$b" ]
注意:在[]结构中"<"需要被转义.
> 大于,在ASCII字母顺序下.如:
if [[ "$a" > "$b" ]]
if [ "$a" \> "$b" ]
注意:在[]结构中">"需要被转义.
具体参考Example 26-11来查看这个操作符应用的例子.
-z 字符串为"null".就是长度为0.
-n 字符串不为"null"
注意:
使用-n在[]结构中测试必须要用""把变量引起来.使用一个未被""的字符串来使用! -z
或者就是未用""引用的字符串本身,放到[]结构中(见Example 7-6)虽然一般情况下可
以工作,但这是不安全的.习惯于使用""来测试字符串是一种好习惯.[1] Example 7-5 数字和字符串比较
################################Start Script#######################################
1 #!/bin/bash
2
3 a=4
4 b=5
5
6 # 这里的变量a和b既可以当作整型也可以当作是字符串.
7 # 这里在算术比较和字符串比较之间有些混淆,
8 #+ 因为Bash变量并不是强类型的.
9
10 # Bash允许对整型变量操作和比较
11 #+ 当然变量中只包含数字字符.
12 # 但是还是要考虑清楚再做.
13
14 echo
15
16 if [ "$a" -ne "$b" ]
17 then
18 echo "$a is not equal to $b"
19 echo "(arithmetic comparison)"
20 fi
21
22 echo
23
24 if [ "$a" != "$b" ]
25 then
26 echo "$a is not equal to $b."
27 echo "(string comparison)"
28 # "4" != "5"
29 # ASCII 52 != ASCII 53
30 fi
31
32 # 在这个特定的例子中,"-ne"和"!="都可以.
33
34 echo
35
36 exit 0
################################End Script######################################### Example 7-6 测试字符串是否为null
################################Start Script#######################################
1 #!/bin/bash
2 # str-test.sh: 测试null字符串和非引用字符串,
3 #+ but not strings and sealing wax, not to mention cabbages and kings . . .
4 #+ 上边这句没看懂
5 # Using if [ ... ]
6
7
8 # 如果一个字符串没被初始化,那么它就没有定义的值(像这种话,总感觉像屁话)
9 # 这种状态叫做"null"(与zero不同)
10
11 if [ -n $string1 ] # $string1 没被声明和初始化
12 then
13 echo "String \"string1\" is not null."
14 else
15 echo "String \"string1\" is null."
16 fi
17 # 错误的结果.
18 # 显示$string1为非空,虽然他没被初始化.
19
20
21 echo
22
23
24 # 让我们再试一下.
25
26 if [ -n "$string1" ] # 这次$string1被引用了.
27 then
28 echo "String \"string1\" is not null."
29 else
30 echo "String \"string1\" is null."
31 fi # ""的字符串在[]结构中
32
33
34 echo
35
36
37 if [ $string1 ] # 这次$string1变成"裸体"的了
38 then
39 echo "String \"string1\" is not null."
40 else
41 echo "String \"string1\" is null."
42 fi
43 # 这工作得很好.
44 # 这个[]test操作检测string是否为null.
45 # 然而,使用("$string1")是一种很好的习惯
46 #
47 # As Stephane Chazelas points out,
48 # if [ $string1 ] 有1个参数 "]"
49 # if [ "$string1" ] 有2个参数,空的"$string1"和"]"
50
51
52
53 echo
54
55
56
57 string1=initialized
58
59 if [ $string1 ] # 再来,$string1"裸体了"
60 then
61 echo "String \"string1\" is not null."
62 else
63 echo "String \"string1\" is null."
64 fi
65 # 再来,给出了正确的结果.
66 # 不过怎么说("$string1")还是好很多,因为. . .
67
68
69 string1="a = b"
70
71 if [ $string1 ] # 再来,$string1 再次裸体了.
72 then
73 echo "String \"string1\" is not null."
74 else
75 echo "String \"string1\" is null."
76 fi
77 # 非引用的"$string1"现在给出了一个错误的结果!
78
79 exit 0
80 # Thank you, also, Florian Wisser, for the "heads-up".
################################End Script######################################### Example 7-7 zmore
################################Start Script#######################################
1 #!/bin/bash
2 # zmore
3
4 #使用'more'来查看gzip文件
5
6 NOARGS=65
7 NOTFOUND=66
8 NOTGZIP=67
9
10 if [ $# -eq 0 ] # 与 if [ -z "$1" ]同样的效果
11 # 应该是说前边的那句注释有问题,$1是可以存在的,比如:zmore "" arg2 arg3
12 then
13 echo "Usage: `basename $0` filename" >&2
14 # 错误消息到stderr
15 exit $NOARGS
16 # 脚本返回65作为退出码.
17 fi
18
19 filename=$1
20
21 if [ ! -f "$filename" ] # 将$filename ""起来,来允许可能的空白
22 then
23 echo "File $filename not found!" >&2
24 # 错误消息到stderr
25 exit $NOTFOUND
26 fi
27
28 if [ ${filename##*.} != "gz" ]
29 # 在变量替换中使用中括号
30 then
31 echo "File $1 is not a gzipped file!"
32 exit $NOTGZIP
33 fi
34
35 zcat $1 | more
36
37 # 使用过滤命令'more'
38 # 如果你想的话也可使用'less'
39
40
41 exit $? # 脚本将返回pipe的结果作为退出码
42 # 事实上,不用非的有"exit $?",但是不管怎么说,有了这句,能正规一些
43 # 将最后一句命令的执行状态作为退出码返回
################################End Script######################################### 混合比较 -a 逻辑与
exp1 -a exp2 如果exp1和exp2都为true的话,这个表达式将返回true -o 逻辑或
exp1 -o exp2 如果exp1和exp2中有一个为true的话,那么这个表达式就返回true 这与Bash的比较操作符&&和||很相像.在[[]]中使用它.
1 [[ condition1 && condition2 ]]
-o和-a一般都是和test命令或者是[]一起工作.
1 if [ "$exp1" -a "$exp2" ] 请参考Example 8-3,Example 26-16和Example A-28来查看混合比较操作的行为. 注意事项:
[1] S.C.(这家伙是个人名)指出,在使用混合比较的时候即使"$var"也可能会产生问题.
如果$string为空的话,[ -n "$string" -o "$a" = "$b" ]可能在某些版本的Bash中
会有问题.为了附加一个额外的字符到可能的空变量中的一种安全的办法是,
[ "x$string" != x -o "x$a" = "x$b" ](the "x's" cancel out)(没看懂).
cancel out是抵消的意思.
7.4 嵌套的if/then条件test
-------------------------
可以使用if/then来进行嵌套的条件test.最终的结果和上边的使用&&混合比较操作是相同的.
1 if [ condition1 ]
2 then
3 if [ condition2 ]
4 then
5 do-something # 这里只有在condition1和condition2都可用的时候才行.
6 fi
7 fi
具体请查看Example 34-4.
7.5 检查你的test知识
--------------------
系统范围的xinitrc文件可以用来启动X server.这个文件中包含了相当多的if/then test,
就像下边的节选一样:
1 if [ -f $HOME/.Xclients ]; then
2 exec $HOME/.Xclients
3 elif [ -f /etc/X11/xinit/Xclients ]; then
4 exec /etc/X11/xinit/Xclients
5 else
6 # 故障保险设置,虽然我们永远都不会走到这来.
7 # (我们在Xclients中也提供了相同的机制)它不会受伤的.
8 xclock -geometry 100x100-5+5 &
9 xterm -geometry 80x50-50+150 &
10 if [ -f /usr/bin/netscape -a -f /usr/share/doc/HTML/index.html ]; then
11 netscape /usr/share/doc/HTML/index.html &
12 fi
13 fi 对上边的"test"结构进行解释,然后检查整个文件,/etc/X11/xinit/xinitrc,并分析if/then
test结构.你可能需要查看一下后边才能讲解到的grep,sed和正则表达式的知识. 第8章 操作符和相关的主题
======================== 8.1 操作符
---------- 等号操作符 变量赋值
初始化或者修改变量的值
=
无论在算术运算还是字符串运算中,都是赋值语句.
1 var=27
2 category=minerals # No spaces allowed after the "=". 注意:不要和"="test操作符混淆.
1 # = as a test operator
2
3 if [ "$string1" = "$string2" ]
4 # if [ "X$string1" = "X$string2" ] is safer,
5 # to prevent an error message should one of the variables be empty.
6 # (The prepended "X" characters cancel out.)
7 then
8 command
9 fi 算术操作符 + 加法
- 减法
* 乘法
/ 除法
** 幂运算
1 # Bash, version 2.02, introduced the "**" exponentiation operator.
2
3 let "z=5**3"
4 echo "z = $z" # z = 125
% 取模
bash$ expr 5 % 3
2 5/3=1余2
模运算经常用在其它的事情中,比如产生特定的范围的数字(Example 9-24,
Example 9-27)和格式化程序的输出(Example 26-15,Example A-6).它甚至可以用来
产生质数,(Example A-16).事实上取模运算在算术运算中使用的频率惊人的高. Example 8-1 最大公约数
################################Start Script#######################################
1 #!/bin/bash
2 # gcd.sh: 最大公约数
3 # 使用Euclid's 算法
4
5 # 最大公约数,就是2个数能够同时整除的最大的数.
6 #
7
8 # Euclid's算法采用连续除法.
9 # 在每个循环中
10 #+ 被除数 <--- 除数
11 #+ 除数 <--- 余数
12 #+ 直到余数= 0.
13 #+ 在最后的循环中The gcd = 被除数
14 #
15 # 关于这个算法更精彩的讨论
16 # 见Jim Loy's site, http://www.jimloy.com/number/euclids.htm.
17
18
19 # ------------------------------------------------------
20 # 参数检查
21 ARGS=2
22 E_BADARGS=65
23
24 if [ $# -ne "$ARGS" ]
25 then
26 echo "Usage: `basename $0` first-number second-number"
27 exit $E_BADARGS
28 fi
29 # ------------------------------------------------------
30
31
32 gcd ()
33 {
34
35 dividend=$1 # 随便给值
36 divisor=$2 #+ 即使$2大,也没关系.
37 # Why not?
38
39 remainder=1 # 如果再循环中使用为初始化的变量.
40 #+ 那将在第一次循环中产生一个错误消息.
41
42
43 until [ "$remainder" -eq 0 ]
44 do
45 let "remainder = $dividend % $divisor"
46 dividend=$divisor # 现在使用2个最小的数重复.
47 divisor=$remainder
48 done # Euclid's algorithm
49
50 } # Last $dividend is the gcd.
50 } # 最后的$dividend就是gcd.
51
52
53 gcd $1 $2
54
55 echo; echo "GCD of $1 and $2 = $dividend"; echo
56
57
58 # 练习:
59 # --------
60 # 检查命令行参数来确定它们都是整数,
61 #+ and exit the script with an appropriate error message if not.
61 #+ 否则就选择合适的错误消息退出.
62
63 exit 0
################################End Script######################################### += 加等于(通过常量增加变量)
let "var += 5" #var将在本身值的基础上增加5
-= 减等于
*= 乘等于
let "var *= 4"
/= 除等于
%= 取模赋值,算术操作经常使用expr或者let表达式. Example 8-2 使用算术操作符
################################Start Script#######################################
1 #!/bin/bash
2 # Counting to 11 in 10 different ways.
3
4 n=1; echo -n "$n "
5
6 let "n = $n + 1" # let "n = n + 1" 这么写也行
7 echo -n "$n "
8
9
10 : $((n = $n + 1))
11 # ":" 是必须的,这是因为,如果没有":"的话,Bash将
12 #+ 尝试把"$((n = $n + 1))"解释成一个命令
13 echo -n "$n "
14
15 (( n = n + 1 ))
16 # 对于上边的方法的一个更简单的选则.
17 # Thanks, David Lombard, for pointing this out.
18 echo -n "$n "
19
20 n=$(($n + 1))
21 echo -n "$n "
22
23 : $[ n = $n + 1 ]
24 # ":" 是必须的,这是因为,如果没有":"的话,Bash将
25 #+ 尝试把"$[ n = $n + 1 ]" 解释成一个命令
26 # 即使"n"被初始化成为一个字符串,这句也能工作.
27 echo -n "$n "
28
29 n=$[ $n + 1 ]
30 # 即使"n"被初始化成为一个字符串,这句也能工作.
31 #* Avoid this type of construct, since it is obsolete and nonportable.
31 #* 尽量避免这种类型的结果,因为这已经被废弃了,并且不具可移植性.
32 # Thanks, Stephane Chazelas.
33 echo -n "$n "
34
35 # 现在来个C风格的增量操作.
36 # Thanks, Frank Wang, for pointing this out.
37
38 let "n++" # let "++n" also works.
39 echo -n "$n "
40
41 (( n++ )) # (( ++n ) also works.
42 echo -n "$n "
43
44 : $(( n++ )) # : $(( ++n )) also works.
45 echo -n "$n "
46
47 : $[ n++ ] # : $[ ++n ]] also works
48 echo -n "$n "
49
50 echo
51
52 exit 0
################################End Script######################################### 注意:在Bash中的整型变量事实上是32位的,范围是 -2147483648 到2147483647.如果超过这个
范围进行算术操作,将不会得到你期望的结果(就是溢出么).
1 a=2147483646
2 echo "a = $a" # a = 2147483646
3 let "a+=1" # 加1 "a".
4 echo "a = $a" # a = 2147483647
5 let "a+=1" # 再加1 "a" ,将超过上限了.
6 echo "a = $a" # a = -2147483648
7 # 错误 (溢出了)
在Bash 2.05b版本中,Bash支持64位整型了. 注意:Bash并不能理解浮点运算.它把包含的小数点看作字符串.
1 a=1.5
2
3 let "b = $a + 1.3" # 错误.
4 # t2.sh: let: b = 1.5 + 1.3: 表达式的语义错误(错误标志为".5 + 1.3")
5
6 echo "b = $b" # b=1
如果真想做浮点运算的话,使用bc(见12.8节),bc可以进行浮点运算或调用数学库函数. 位操作符.
(晕,有点强大过分了吧,位级操作都支持.)
位操作符在shell脚本中极少使用.它们最主要的用途看起来就是操作和test从sockets中
读出的变量."Bit flipping"与编译语言的联系很紧密,比如c/c++,在这种语言中它可以
运行得足够快.(原文有处on the fly,我查了一下,好像是没事干的意思,没理解) << 左移1位(每次左移都将乘2) <<= 左移几位,=号后边将给出左移几位
let "var <<= 2"就是左移2位(就是乘4) >> 右移1位(每次右移都将除2) >>= 右移几位 & 按位与 &= 按位与赋值 | 按位或 |= 按位或赋值 ~ 按位非 ! 按位否?(没理解和上边的~有什么区别?),感觉是应该放到下边的逻辑操作中 ^ 按位异或XOR ^= 异或赋值
逻辑操作: && 逻辑与
1 if [ $condition1 ] && [ $condition2 ]
2 # 与: if [ $condition1 -a $condition2 ] 相同
3 # 如果condition1和condition2都为true,那结果就为true.
4
5 if [[ $condition1 && $condition2 ]] # 也可以.
6 # 注意&&不允许出现在[ ... ]中.
注意:&&也可以用在and list中(见25章),但是使用的时候需要依赖上下文. || 逻辑或
1 if [ $condition1 ] || [ $condition2 ]
2 # 与: if [ $condition1 -o $condition2 ] 相同
3 # 如果condition1或condition2为true,那结果就为true.
4
5 if [[ $condition1 || $condition2 ]] # 也可以
6 # 注意||不允许出现在[ ... ]中.
注意:Bash将test每个连接到逻辑操作的状态的退出状态(见第6章).
Example 8-3 使用&&和||进行混合状态的test
################################Start Script#######################################
1 #!/bin/bash
2
3 a=24
4 b=47
5
6 if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]
7 then
8 echo "Test #1 succeeds."
9 else
10 echo "Test #1 fails."
11 fi
12
13 # 错误: if [ "$a" -eq 24 && "$b" -eq 47 ]
14 #+ 尝试执行' [ "$a" -eq 24 '
15 #+ 因为没找到']'所以失败了.
16 #
17 # 注意: 如果 [[ $a -eq 24 && $b -eq 24 ]] 能够工作.
18 # 那这个[[]]的test结构就比[]结构更灵活了.
19 #
20 # (在17行的"&&"与第6行的"&&"意义不同)
21 # Thanks, Stephane Chazelas, for pointing this out.
22
23
24 if [ "$a" -eq 98 ] || [ "$b" -eq 47 ]
25 then
26 echo "Test #2 succeeds."
27 else
28 echo "Test #2 fails."
29 fi
30
31
32 # -a和-o选项提供了
33 #+ 一种可选的混合test方法.
34 # Thanks to Patrick Callahan for pointing this out.
35
36
37 if [ "$a" -eq 24 -a "$b" -eq 47 ]
38 then
39 echo "Test #3 succeeds."
40 else
41 echo "Test #3 fails."
42 fi
43
44
45 if [ "$a" -eq 98 -o "$b" -eq 47 ]
46 then
47 echo "Test #4 succeeds."
48 else
49 echo "Test #4 fails."
50 fi
51
52
53 a=rhino
54 b=crocodile
55 if [ "$a" = rhino ] && [ "$b" = crocodile ]
56 then
57 echo "Test #5 succeeds."
58 else
59 echo "Test #5 fails."
60 fi
61
62 exit 0
################################End Script#########################################
&&和||操作也能在算术运算的上下文中找到.
bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0))
1 0 1 0 混杂操作:
, 逗号操作符
逗号操作符可以连接2个或多个算术运算.所有的操作都会被执行,但是只有最后一个
操作作为结果.
1 let "t1 = ((5 + 3, 7 - 1, 15 - 4))"
2 echo "t1 = $t1" # t1 = 11
3
4 let "t2 = ((a = 9, 15 / 3))" # Set "a" and calculate "t2".
5 echo "t2 = $t2 a = $a" # t2 = 5 a = 9
","主要用在for循环中,具体见Example 10-12.
8.2 数字常量
------------
shell脚本默认都是将数字作为10进制数处理,除非这个数字某种特殊的标记法或前缀开头.
以0开头就是8进制.以0x开头就是16进制数.使用BASE#NUMBER这种形式可以表示其它进制
表示法 Example 8-4 数字常量的处理
################################Start Script#######################################
1 #!/bin/bash
2 # numbers.sh: 数字常量的几种不同的表示法
3
4 # 10进制: 默认
5 let "dec = 32"
6 echo "decimal number = $dec" # 32
7 # 一切都很正常
8
9
10 # 8进制: 以'0'(零)开头
11 let "oct = 032"
12 echo "octal number = $oct" # 26
13 # 表达式的结果用10进制表示.
14 #
15
16 # 16进制表示:数字以'0x'或者'0X'开头
17 let "hex = 0x32"
18 echo "hexadecimal number = $hex" # 50
19 # 表达式的结果用10进制表示.
20
21 # 其它进制: BASE#NUMBER
22 # BASE between 2 and 64.
22 # 2到64进制都可以.
23 # NUMBER必须在BASE的范围内,具体见下边.
24
25
26 let "bin = 2#111100111001101"
27 echo "binary number = $bin" # 31181
28
29 let "b32 = 32#77"
30 echo "base-32 number = $b32" # 231
31
32 let "b64 = 64#@_"
33 echo "base-64 number = $b64" # 4031
34 # 这种64进制的表示法中的每位数字都必须在64进制表示法的限制字符内.
35 # 10 个数字+ 26 个小写字母+ 26 个大写字母+ @ + _
36
37
38 echo
39
40 echo $((36#zz)) $((2#10101010)) $((16#AF16)) $((53#1aA))
41 # 1295 170 44822 3375
42
43
44 # 重要的注意事项:
45 # ---------------
46 # 如果使用的每位数字超出了这个进制表示法规定字符的范围的话,
47 #+ 将给出一个错误消息.
48
49 let "bad_oct = 081"
50 # (部分的) 错误消息输出:
51 # bad_oct = 081: too great for base (error token is "081")
52 # Octal numbers use only digits in the range 0 - 7.
53
54 exit 0 # Thanks, Rich Bartell and Stephane Chazelas, for clarification.
################################End Script#########################################
------------
$ 变量替换操作符
只有在变量被声明,赋值,unset或exported或者是在变量代表一个signal的时候,
变量才会是以本来的面目出现在脚本里.变量在被赋值的时候,可能需要使用"=",
read状态或者是在循环的头部.
在""中还是会发生变量替换,这被叫做部分引用,或叫弱引用.而在''中就不会发生变
量替换,这叫做全引用,也叫强引用.具体见第5章的讨论. 注意:$var与${var}的区别,不加{},在某些上下文将引起错误,为了安全,使用2.
具体见9.3节 参数替换. Example 4-1. 变量赋值和替换
################################Start Script#######################################
1 #!/bin/bash
2
3 # 变量赋值和替换
4
5 a=375
6 hello=$a
7
8 #-------------------------------------------------------------------------
9 # 强烈注意,在赋值的前后一定不要有空格.
10 # 如果有空格会发生什么?
11
12 # 如果"VARIABLE =value",
13 # ^
14 #+ 脚本将尝试运行一个"VARIABLE"的命令,带着一个"=value"参数.
15
16 # 如果"VARIABLE= value",
17 # ^
18 #+ script tries to run "value" command with
18 #+ 脚本将尝试运行一个"value"的命令,带着
19 #+ the environmental variable "VARIABLE" set to "".
19 #+ 一个被赋成""值的环境变量"VARIABLE".
20 #-------------------------------------------------------------------------
21
22
23 echo hello # 没有变量引用,不过是个hello字符串
24
25 echo $hello
26 echo ${hello} # 同上
27
28 echo "$hello"
29 echo "${hello}"
30
31 echo
32
33 hello="A B C D"
34 echo $hello # A B C D
35 echo "$hello" # A B C D
36 # 就象你看到的echo $hello 和 echo "$hello" 将给出不同的结果.
37 # ^ ^
38 # Quoting a variable preserves whitespace.
38 # 引用一个变量将保留其中的空白,当然,如果是变量替换就不会保留了.
39
40 echo
41
42 echo '$hello' # $hello
43 # ^ ^
44 # 全引用的作用
45 #+ 将导致"$"变成一个单独的字符.
46
47 # 注意两种引用不同的效果
48
49
50 hello= # 设置为空值
51 echo "\$hello (null value) = $hello"
52 # 注意设置一个变量为空,与unset它,不是一回事,虽然看起来一样
53 #
54
55 # --------------------------------------------------------------
56
57 # 可以在同一行上设置多个变量.
58 #+ 要以空白分隔
59 # 小心,这会降低可读性,和可移植性.
60
61 var1=21 var2=22 var3=$V3
62 echo
63 echo "var1=$var1 var2=$var2 var3=$var3"
64
65 # 在老版本的"sh"上,可能会有问题.
66
67 # --------------------------------------------------------------
68
69 echo; echo
70
71 numbers="one two three"
72 # ^ ^
73 other_numbers="1 2 3"
74 # ^ ^
75 # 如果变量中有空白,那么引用就必要了.
76 #
77 echo "numbers = $numbers"
78 echo "other_numbers = $other_numbers" # other_numbers = 1 2 3
79 echo
80
81 echo "uninitialized_variable = $uninitialized_variable"
82 # Uninitialized变量为空值(根本就没赋值).
83 uninitialized_variable= # 声明,但是没被初始化
84 #+ 其实和前边设置为空值得作用是一样的.
85 echo "uninitialized_variable = $uninitialized_variable"
86 # 还是一个空值
87
88 uninitialized_variable=23 # 赋值
89 unset uninitialized_variable # Unset it.
90 echo "uninitialized_variable = $uninitialized_variable"
91 # 还是空值
92 echo
93
94 exit 0
################################End Script#########################################
注意: 一个空值变量,或者是根本就没声明的变量,在赋值之前使用它可能会引起问题.
但是还是可以用来做算术运算
################################Start Script#######################################
1 echo "$uninitialized" # (blank line)
2 let "uninitialized += 5" # Add 5 to it.
3 echo "$uninitialized" # 5
4
5 # 结论:
6 # 对于一个空值变量在做算术操作的时候,就好像它的值为0一样.
8 # This is undocumented (and probably non-portable) behavior.
7 # 这并没被文档化(可能是不可移植)的行为.
################################End Script#########################################
具体参考 Example 11-21
4.2 变量赋值
------------
= 赋值操作符(前后都不能有空白)
不要与-eq混淆,那个是test,并不是赋值.
注意,=也可被用来做test操作,这依赖于上下文. Example 4-2. 一般的变量赋值
################################Start Script#######################################
1 #!/bin/bash
2 # "裸体"变量
3
4 echo
5
6 # 变量什么时候是"裸体"的,比如前边少了$的时候.
7 # 当它被赋值的时候,而不是被引用的时候.
8
9 # 赋值
10 a=879
11 echo "The value of \"a\" is $a."
12
13 # 使用let赋值
14 let a=16+5
15 echo "The value of \"a\" is now $a."
16
17 echo
18
19 # 在for循环中
20 echo -n "Values of \"a\" in the loop are: "
21 for a in 7 8 9 11
22 do
23 echo -n "$a "
24 done
25
26 echo
27 echo
28
29 # 在read命令状态中
30 echo -n "Enter \"a\" "
31 read a
32 echo "The value of \"a\" is now $a."
33
34 echo
35
36 exit 0
################################End Script######################################### Example 4-3. 变量赋值,一般的和比较特殊的
################################Start Script#######################################
1 #!/bin/bash
2
3 a=23 # Simple case
4 echo $a
5 b=$a
6 echo $b
7
8 # 现在让我们来点小变化
9
10 a=`echo Hello!` # 把echo命令的结果传给变量a
11 echo $a
12 # 注意,如果在命令扩展结构中使用一个(!)的话,在命令行中将不能工作
13 #+ 因为这触发了Bash的"历史机制".
14 # 但是,在校本里边使用的话,历史功能是被关闭的,所以就能够正常运行.
15
16
17 a=`ls -l` # 把ls -l的结果给a
18 echo $a # 别忘了,这么引用的话,ls的结果中的所有空白部分都没了(包括换行)
19 echo
20 echo "$a" # 这么引用就正常了,保留了空白
21 # (具体参阅章节"引用")
22
23 exit 0
################################End Script#########################################
使用$(...)机制进行的变量赋值(除去使用``来赋值的另外一种新方法).事实上这两种方法都是
命令替换的一种形式.
# 来自于/ect/rc.d/rc.local
R=$(cat /ect/redhat-release)
arch=$(uname -m)
4.3 Bash变量是不分类型的
------------------------
不像其他程序语言一样,Bash并不对变量区分"类型".本质上,Bash变量都是字符串.
但是依赖于上下文,Bash也允许比较操作和算术操作.决定这些的关键因素就是,变量中的值
是否只有数字. Example 4-4 整型还是string?
################################Start Script#######################################
1 #!/bin/bash
2 # int-or-string.sh: 整形还是string?
3
4 a=2334 # 整型
5 let "a += 1"
6 echo "a = $a " # a = 2335
7 echo # 还是整型
8
9
10 b=${a/23/BB} # 将23替换成BB
11 # 这将把b变量从整型变为string
12 echo "b = $b" # b = BB35
13 declare -i b # 即使使用declare命令也不会对此有任何帮助,9.4节有解释
14 echo "b = $b" # b = BB35
15
16 let "b += 1" # BB35 + 1 =
17 echo "b = $b" # b = 1
18 echo
19
20 c=BB34
21 echo "c = $c" # c = BB34
22 d=${c/BB/23} # S将BB替换成23
23 # 这使得$d变为一个整形
24 echo "d = $d" # d = 2334
25 let "d += 1" # 2334 + 1 =
26 echo "d = $d" # d = 2335
27 echo
28
29 # 关于空变量怎么样?
30 e=""
31 echo "e = $e" # e =
32 let "e += 1" # 算术操作允许一个空变量?
33 echo "e = $e" # e = 1
34 echo # 空变量将转换成一个整型变量
35
36 # 关于未声明的变量怎么样?
37 echo "f = $f" # f =
38 let "f += 1" # 算术操作允许么?
39 echo "f = $f" # f = 1
40 echo # 未声明的变量将转换成一个整型变量
41
42
43
44 # 所以说Bash中的变量都是无类型的.
45
46 exit 0
################################End Script#########################################
4.4 特殊的变量类型
------------------
local variables
这种变量只有在代码块或者是函数中才可见(具体见23.2和23章)
environmental variables
这种变量将改变用户接口和shell的行为. 在一般的上下文中,每个进程都有自己的环境,就是一组保持进程可能引用的信息的
变量.这种情况下,shell于一个一般进程是相同的. 每次当shell启动时,它都将创建自己的环境变量.更新或者添加新的环境变量,将导
致shell更新它的环境,同时也会影响所有继承自这个环境的所有子进程(由这个命令
导致的). 注意:分配给环境变量的空间是受限的.创建太多的环境变量将引起空间溢出,这会引
起问题.
关于eval命令,具体见第11章
bash$ eval "`seq 10000 | sed -e 's/.*/export var&=ZZZZZZZZZZZZZZ/'`"
bash$ du
bash: /usr/bin/du: Argument list too long 如果一个脚本设置了环境变量,需要export它,来通知本脚本的环境,这是export
命令的功能,关于export命令,具体见11章.
脚本只能对它产生的子进程export变量.一个从命令行被调用的脚本export的变量,将
不能影响调用这个脚本的那个命令行shell的环境. positional parameters
就是从命令行中传进来的参数,$0, $1, $2, $3... $0就是脚本文件的名字,$1是第一个参数,$2为第2个...,参见[1](有$0的说明),$9
以后就需要打括号了,如${10},${11},${12}...
两个值得注意的变量$*和$@(第9章有具体的描述),表示所有的位置参数. Example 4-5 位置参数
################################Start Script#######################################
1 #!/bin/bash
2
3 # 作为用例,调用这个脚本至少需要10个参数,如
4 # ./scriptname 1 2 3 4 5 6 7 8 9 10
5 MINPARAMS=10
6
7 echo
8
9 echo "The name of this script is \"$0\"."
10 # 添加./是为了当前目录
11 echo "The name of this script is \"`basename $0`\"."
12 # 去掉目录信息,具体见'basename'命令
13
14 echo
15
16 if [ -n "$1" ] # 测试变量被被引用
17 then
18 echo "Parameter #1 is $1" # "#"没被转义
19 fi
20
21 if [ -n "$2" ]
22 then
23 echo "Parameter #2 is $2"
24 fi
25
26 if [ -n "$3" ]
27 then
28 echo "Parameter #3 is $3"
29 fi
30
31 # ...
32
33
34 if [ -n "${10}" ] # 大于9的参数必须出现在{}中.
35 then
36 echo "Parameter #10 is ${10}"
37 fi
38
39 echo "-----------------------------------"
40 echo "All the command-line parameters are: "$*""
41
42 if [ $# -lt "$MINPARAMS" ] #$#是传到脚本里的位置参数的个数
43 then
44 echo
45 echo "This script needs at least $MINPARAMS command-line arguments!"
46 fi
47
48 echo
49
50 exit 0
################################End Script#########################################
{}标记法是一种很好的使用位置参数的方法.这也需要间接引用(见Example 34-2)
1 args=$# # 位置参数的个数
2 lastarg=${!args}
3 # 或: lastarg=${!#}
4 # 注意 lastarg=${!$#} 将报错 一些脚本可能会依赖于使用不同的调用名字,而表现出不同的行为,这样一般都需要
判断$0,而其他的名字都是通过ln命令产生的链接.(具体参见Example 12-2) 如果脚本需要一个命令行参数,而调用的时候,没用这个参数,这就有可能造成分配一个
空变量,这样估计就会引起问题.一种解决办法就是在这个位置参数,和相关的变量后
边,都添加一个额外的字符.具体见下边的例子.
################################Start Script#######################################
1 variable1_=$1_ # 而不是 variable1=$1
2 # 这将阻止一个错误,即使在调用时没使用这个位置参数.
3
4 critical_argument01=$variable1_
5
6 # 这个扩展的字符是可以被消除掉的,就像这样.
7 variable1=${variable1_/_/}
8 # 副作用就是$variable1_多了一个下划线
9 # 这里使用了一个参数替换模版(后边会有具体的讨论)
10 # (Leaving out the replacement pattern results in a deletion.)
10 # (在一个删除动作中,节省了一个替换模式)
11
12
13 # 一个解决这种问题的更简单的做法就是,判断一下这个位置参数是否传递下来了
14 if [ -z $1 ]
15 then
16 exit $E_MISSING_POS_PARAM
17 fi
18
19
20 # 但是上边的方法将可能产生一个意外的副作用
21 # 参数替换的更好的办法应该是:
22 # ${1:-$DefaultVal}
23 # 具体察看"Parameter Substition"节
24 #+ 在第9章
################################End Script#########################################
Example 4-6 wh,whois节点名字查询
################################Start Script#######################################
1 #!/bin/bash
2 # ex18.sh
3
4 # Does a 'whois domain-name' lookup on any of 3 alternate servers:
5 # ripe.net, cw.net, radb.net
6
7 # 把这个脚本重命名为'wh',然后放到/usr/local/bin下
8
9 # 需要3个符号链接
10 # ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
11 # ln -s /usr/local/bin/wh /usr/local/bin/wh-cw
12 # ln -s /usr/local/bin/wh /usr/local/bin/wh-radb
13
14 E_NOARGS=65
15
16
17 if [ -z "$1" ]
18 then
19 echo "Usage: `basename $0` [domain-name]"
20 exit $E_NOARGS
21 fi
22
23 # Check script name and call proper server.
23 # 检查脚本名字,然后调用合适的服务器
24 case `basename $0` in # Or: case ${0##*/} in
25 "wh" ) whois [email protected];;
26 "wh-ripe") whois [email protected];;
27 "wh-radb") whois [email protected];;
28 "wh-cw" ) whois [email protected];;
29 * ) echo "Usage: `basename $0` [domain-name]";;
30 esac
31
32 exit $?
################################End Script######################################### shift shift命令重新分配位置参数,其实就是向左移动一个位置.
$1 <--- $2, $2 <--- $3, $3 <--- $4, 等等.
老的$1将消失,但是$0(脚本名)是不会改变的.如果你使用了大量的位置参数,那么
shift命令允许你存取超过10个参数.虽然{}表示法也允许这样.
Example 4-7 使用shift
################################Start Script#######################################
1 #!/bin/bash
2 # 使用'shift'来穿过所有的位置参数.
3
4 # 把这个脚本命名为shft,
5 #+ 并且使用一些参数来调用它,如:
6 # ./shft a b c def 23 skidoo
7
8 until [ -z "$1" ] # 知道所有参数都用光
9 do
10 echo -n "$1 "
11 shift
12 done
13
14 echo # 额外的换行.
15
16 exit 0
################################End Script#########################################
在将参数传递到函数中时,shift的工作方式也基本差不多.具体见Example 33-15 注意事项:
[1] 进程调用设置$0参数的脚本.一般的,这个参数就是脚本名字.具体察看execv的man页. 第5章 引用(翻译的可能有问题,特指引号)
======================================
引号的特殊效果就是,保护字符串中的特殊字符不被shell或者是shell脚本重新解释或者扩展.
(我们这里所说的"特殊"指的是一些字符在shell中具有的特殊意义,比如*)
如:
bash$ ls -l [Vv]*
-rw-rw-r-- 1 bozo bozo 324 Apr 2 15:05 VIEWDATA.BAT
-rw-rw-r-- 1 bozo bozo 507 May 4 14:25 vartrace.sh
-rw-rw-r-- 1 bozo bozo 539 Apr 14 17:11 viewdata.sh
bash$ ls -l '[Vv]*'
ls: [Vv]*: No such file or directory 在我们一般的生活中,引号内的内容往往有特殊的含义,而在Bash中,当我们引用一个字符串,
我们是保护它的字面含义. 特定的程序和工具能够重新解释或扩展特殊的字符.引用的一个重要的作用就是保护命令行中
的参数,但还是允许正在调用的程序来扩展它.
bash$ grep '[Ff]irst' *.txt
file1.txt:This is the first line of file1.txt.
file2.txt:This is the First line of file2.txt. 注意 grep [Ff]irst *.txt在Bash下的行为(其实就是正则表达式么),[1] 引用还可以抑制echo命令的换行作用. bash$ echo $(ls -l)
total 8 -rw-rw-r-- 1 bozo bozo 130 Aug 21 12:57 t222.sh -rw-rw-r-- 1 bozo bozo 78 Aug 21 12:57 t71.sh bash$ echo "$(ls -l)"
total 8
-rw-rw-r-- 1 bozo bozo 130 Aug 21 12:57 t222.sh
-rw-rw-r-- 1 bozo bozo 78 Aug 21 12:57 t71.sh
5.1 引用变量
------------
在一个双引号中直接使用变量名,一般都是没有问题的.它阻止了所有在引号中的特殊字符的
重新解释--包括变量名[2]--但是$,`和\除外.[3]保留$,作为特殊字符的意义,是为了能够在双
引号中也能够正常地引用变量("$var").这样在""中可以使用变量所表达的值(Example 4-1). 使用""来防止单词分割.[4]如果在参数列表中使用双引号,将使得双引号中的参数作为一个参
数.即使双引号中的字符串包含多个单词(也就是包含空白部分),也不会变为多个参数,如:
1 variable1="a variable containing five words"
2 COMMAND This is $variable1 # COMMAND将以7个参数来执行
3 # "This" "is" "a" "variable" "containing" "five" "words"
4
5 COMMAND "This is $variable1" # COMMAND将以1个参数来执行
6 # "This is a variable containing five words"
7
8
9 variable2="" # 空值
10
11 COMMAND $variable2 $variable2 $variable2 # COMMAND将不带参数执行
12 COMMAND "$variable2" "$variable2" "$variable2" # COMMAND将以3个空参数来执行
13 COMMAND "$variable2 $variable2 $variable2" # COMMAND将以1个参数来执行(2空格)
用双引号把参数封到echo中是很有必要的,只有在单词分隔或时保留空白时的时候可能
有些问题. Example 5-1 echo一些诡异的变量
################################Start Script#######################################
1 #!/bin/bash
2 # weirdvars.sh: echo诡异的变量
3
4 var="'(]\\{}\$\""
5 echo $var # '(]\{}$"
6 echo "$var" # '(]\{}$" 并没有什么不同
7
8 echo
9
10 IFS='\'
11 echo $var # '(] {}$" \ 转换成空格了?明显和IFS有关系么!又不傻!
12 echo "$var" # '(]\{}$"
13
14 exit 0
################################End Script######################################### 单引号操作总体上和""很像,但不允许引用变量.因为$的特殊含义被关闭了.在''中除了',其他
字符都没有特殊的含义了.所以单引号比双引号严格.
因为即使是\,在''中都被关闭了,所以你想在''中显示'的含义,将得不到预期的效果.
1 echo "Why can't I write 's between single quotes"
2
3 echo
4
5 # 一种绕弯的方法
6 echo 'Why can'\''t I write '"'"'s between single quotes'
7 # |-------| |----------| |-----------------------|
8 # 包含了2个单引号字符,原书好像有错误 注意事项:
[1] 除非当前目录下,正好有个叫first的文件.
[2] 即使是变量的值也是有副作用的(见下边)
[3] 如果在""中包含"!"的话,在命令行中将会出现错误.因为这个"!"被当作历史命令来解释了.
在一个脚本中,这种情况是不会发生的,因为在脚本中,Bash历史记录被关闭了. 下边是一些关于"\"一些不协调的行为.
bash$ echo hello\!
hello! bash$ echo "hello\!"
hello\! bash$ echo -e x\ty
xty bash$ echo -e "x\ty"
x y [4] "单词分隔",在这个上下文中意味着,将一个字符串分隔为一些分离的参数.
5.2 转义(\)
-----------
转义是一种引用单个字符的方法.一个具有特殊含义的字符前边放上一个转义符(\)就告诉shell
这个字符失去了特殊的含义.
值得注意的是,在某些特定的命令和工具中,比如echo和sed,转义符往往会起到相反的效果,
它反倒有可能引发出这个字符特殊的含义. 对于特定的转义符的特殊的含义
在echo和sed中所使用的
\n 意味着新的一行
\r 回车
\t tab键
\v vertical tab(垂直tab),查前边的Ctl-K
\b backspace,查前边的Ctl-H
\a "alert"(如beep或flash)
\0xx 转换成8进制ASCII解码,等价于oxx Example 5-2 转义符
################################Start Script#######################################
1 #!/bin/bash
2 # escaped.sh: 转义符
3
4 echo; echo
5
6 echo "\v\v\v\v" # 逐字的打印\v\v\v\v .
7 # 使用-e选项的echo命令来打印转义符
8 echo "============="
9 echo "VERTICAL TABS"
10 echo -e "\v\v\v\v" # Prints 4 vertical tabs.
11 echo "=============="
12
13 echo "QUOTATION MARK"
14 echo -e "\042" # 打印" (引号, 8进制的ASCII 码就是42).
15 echo "=============="
16
17 # The $'\X' construct makes the -e option unnecessary.
17 # 如果使用$'\X'结构,那-e选项就不必要了
18 echo; echo "NEWLINE AND BEEP"
19 echo $'\n' # 新行.
20 echo $'\a' # Alert (beep).
21
22 echo "==============="
23 echo "QUOTATION MARKS"
24 # 版本2以后Bash允许使用$'\nnn'结构
25 # 注意这种情况,'\nnn\是8进制
26 echo $'\t \042 \t' # Quote (") framed by tabs.
27
28 # 当然,也可以使用16进制的值,使用$'\xhhh' 结构
29 echo $'\t \x22 \t' # Quote (") framed by tabs.
30
31 # 早一点的Bash版本允许'\x022'这种形式
32 echo "==============="
33 echo
34
35
36 # 分配ASCII字符到变量中
37 # ---------------------
38 quote=$'\042' # \042是",分配到变量中
39 echo "$quote This is a quoted string, $quote and this lies outside the quotes."
40
41 echo
42
43 # Concatenating ASCII chars in a variable.
43 # 变量中的连续的ASCII char.
44 triple_underline=$'\137\137\137' # 137 是8进制的ASCII 码'_'.
45 echo "$triple_underline UNDERLINE $triple_underline"
46
47 echo
48
49 ABC=$'\101\102\103\010' # 101, 102, 103 是8进制的码A, B, C.
50 echo $ABC
51
52 echo; echo
53
54 escape=$'\033' # 033 是8进制码for escape.
55 echo "\"escape\" echoes as $escape"
56 #"escape" echoes as 没有变量被输出
57
58 echo; echo
59
60 exit 0
################################End Script#########################################
另一个关于$''字符串扩展结果的例子见Example 34-1 \" 表达引号本身
1 echo "Hello" # Hello
2 echo "\"Hello\", he said." # "Hello", he said. \$ $号本身,跟在\$后的变量名,将不能扩展
1 echo "\$variable01" # 结果是$variable01 \\ \号本身.
1 echo "\\" # 结果是\
2
3 # 相反的 . . .
4
5 echo "\" # 这会出现第2个命令提示符,说白了就是提示你命令不全,你再补个"就
6 # 好了.如果是在脚本里,就会给出一个错误. 注意:\的行为依赖于它是否被转义,被"",或者是否在"命令替换"和"here document"中.
################################Start Script#######################################
1 # 简单的转义和""
2 echo \z # z
3 echo \\z # \z
4 echo '\z' # \z
5 echo '\\z' # \\z
6 echo "\z" # \z
7 echo "\\z" # \z
8
9 # 命令替换
10 echo `echo \z` # z
11 echo `echo \\z` # z
12 echo `echo \\\z` # \z
13 echo `echo \\\\z` # \z
14 echo `echo \\\\\\z` # \z
15 echo `echo \\\\\\\z` # \\z
16 echo `echo "\z"` # \z
17 echo `echo "\\z"` # \z
18
19 # Here document
20 cat <<EOF
21 \z
22 EOF # \z
23
24 cat <<EOF
25 \\z
26 EOF # \z
################################End Script######################################### 分配给变量的字符串的元素也会被转义,但是只把一个转义符分配给变量将会报错.
################################Start Script#######################################
1 variable=\
2 echo "$variable"
3 # Will not work - gives an error message:
3 # 将不能正常工作- 将给出一个错误消息:
4 # test.sh: : command not found
5 # 一个"裸体的" 转义符将不能够安全的分配给变量.
6 #
7 # What actually happens here is that the "\" escapes the newline and
7 # 这里其实真正发生的是variable=\,这句被shell认为是没有完成,\被认为是一个续行符
8 #+ 这样,下边的这句echo,也被认为是上一行的补充.所以,总的来说就是一个非法变量分配
9
10 variable=\
11 23skidoo
12 echo "$variable" # 23skidoo
13 # 这句就可以使用,因为这是一个合法的变量分配
14
15 variable=\
16 # \^ 转义一个空格
17 echo "$variable" # 显示空格
18
19 variable=\\
20 echo "$variable" # \
21
22 variable=\\\
23 echo "$variable"
24 # 不能正常工作,给出一个错误
25 # test.sh: \: command not found
26 #
27 # 第一个转义符把第2个\转义了,但是第3个又变成"裸体的"了,
28 #+ 与上边的例子的原因相同
29
30 variable=\\\\
31 echo "$variable" # \\
32 # 转了两个\
33 # 没问题
################################End Script######################################### 转义一个空格,在命令行参数列表中将会阻止单词分隔问题.
################################Start Script#######################################
1 file_list="/bin/cat /bin/gzip /bin/more /usr/bin/less /usr/bin/emacs-20.7"
2 # 列出的文件都作为命令的参数.
3
4 # Add two files to the list, and list all.
4 # 加2个文件到list中,并且列出全部.
5 ls -l /usr/X11R6/bin/xsetroot /sbin/dump $file_list
6
7 echo "-------------------------------------------------------------------------"
8
9 # 如果我们转义2个空格,会发生什么?
10 ls -l /usr/X11R6/bin/xsetroot\ /sbin/dump\ $file_list
11 # 错误: 因为前3个路径名被合并成一个参数传给了'ls -l'
12 # 因为2个转义符阻止了参数(单词)分离
################################End Script######################################### 转义符也提供续行功能.一般,每一行都包含一个不同的命令,但如果在行尾加上\,那就会接受
新行的输入,作为这一行的补充.
1 (cd /source/directory && tar cf - . ) | \
2 (cd /dest/directory && tar xpvf -)
3 # 重复了 Alan Cox的目录树拷贝命令
4 # 为了增加可读性分成2行.
5
6 # 也可以使用如下方式:
7 tar cf - -C /source/directory . |
8 tar xpvf - -C /dest/directory
9 # 察看下边的注意事项 注意:如果一个脚本以|(管道字符)结束.那么一个\(转义符),就不用非加上不可了.
但是一个好的shell脚本编写风格,还是应该在行尾加上\,以增加可读性.
################################Start Script#######################################
1 echo "foo
2 bar"
3 #foo
4 #bar
5
6 echo
7
8 echo 'foo
9 bar' # 没区别
10 #foo
11 #bar
12
13 echo
14
15 echo foo\
16 bar # 续行
17 #foobar
18
19 echo
20
21 echo "foo\
22 bar" # 与上边一样,\还是作为续行符
23 #foobar
24
25 echo
26
27 echo 'foo\
28 bar' # 由于是强引用,所以\没被解释成续行符
29 #foo\
30 #bar
################################End Script######################################### 第6章 退出和退出状态
====================
exit命令被用来结束脚本,就像C语言一样.他也会返回一个值来传给父进程,父进程会判断是否
可用. 每个命令都会返回一个exit状态(有时候也叫return状态).成功返回0,如果返回一个非0值,通
常情况下都会被认为是一个错误码.一个编写良好的UNIX命令,程序,和工具都会返回一个0作为
退出码来表示成功,虽然偶尔也会有例外. 同样的,脚本中的函数和脚本本身都会返回退出状态.在脚本或者是脚本函数中执行的最后的命
令会决定退出状态.在脚本中,exit nnn命令将会把nnn退出码传递给shell(nnn必须是10进制数
0-255). 当一个脚本以不带参数exit来结束时,脚本的退出状态就由脚本中最后执行命令来决定.
1 #!/bin/bash
2
3 COMMAND_1
4
5 . . .
6
7 # 将以最后的命令来决定退出状态
8 COMMAND_LAST
9
10 exit $? 1 #!/bin/bash
2
3 COMMAND1
4
5 . . .
6
7 # 将以最后的命令来决定退出状态
8 COMMAND_LAST $?读取最后执行命令的退出码.函数返回后,$?给出函数最后执行的那条命令的退出码.这种给
函数返回值的方法是Bash的方法.对于脚本来说也一样.总之,一般情况下,0为成功,非0失败W.
Example 6-1 exit/exit状态
################################Start Script#######################################
1 #!/bin/bash
2
3 echo hello
4 echo $? # 返回0,因为执行成功
5
6 lskdf # 不认识的命令.
7 echo $? # 返回非0值,因为失败了.
8
9 echo
10
11 exit 113 # 将返回113给shell.
12 # To verify this, type "echo $?" after script terminates.
12 # 为了验证这个,在脚本结束的地方使用"echo $?"
################################End Script######################################### $?对于测试脚本中的命令的结果特别有用(见Example 12-32和Example 12-17).
注意: !逻辑非操作,将会反转test命令的结果,并且这会影响exit状态.
Example 6-2 否定一个条件使用!
################################Start Script#######################################
1 true # true是shell内建命令,什么事都不做,就是shell返回0
2 echo "exit status of \"true\" = $?" # 0
3
4 ! true
5 echo "exit status of \"! true\" = $?" # 1
6 # 注意:"!"需要一个空格
7 # !true 将导致一个"command not found"错误
8 #
9 # 如果一个命令以'!'开头,那么将使用Bash的历史机制.就是显示这个命令被使用的历史.
10
11 true
12 !true
13 # 这次就没有错误了.
14 # 他不过是重复了之前的命令(true).
################################End Script######################################### 注意事项:
特定的退出码都有预定的含义(见附录D),用户不应该在自己的脚本中指定他. 第7章 Tests
===========
每个完整的合理的编程语言都具有条件判断的功能.Bash具有test命令,不同的[]和()操作,和
if/then结构.
7.1 Test结构
------------
一个if/then结构可以测试命令的返回值是否为0(因为0表示成功),如果是的话,执行更多命令. 有一个专用命令"["(左中括号,特殊字符).这个命令与test命令等价,但是出于效率上的考虑,
它是一个内建命令.这个命令把它的参数作为比较表达式或是文件测试,并且根据比较的结果,
返回一个退出码. 在版本2.02的Bash中,推出了一个新的[[...]]扩展test命令.因为这种表现形式可能对某些语
言的程序员来说更加熟悉.注意"[["是一个关键字,并不是一个命令. Bash把[[ $a -lt $b ]]看作一个单独的元素,并且返回一个退出码. ((...))和let...结果也能够返回一个退出码,当它们所测试的算术表达式的结果为非0的时候,
他们的退出码将返回0.这些算术扩展(见第15章)结构被用来做算术比较.
1 let "1<2" returns 0 (as "1<2" expands to "1")
2 (( 0 && 1 )) returns 1 (as "0 && 1" expands to "0") if命令可以测试任何命令,不仅仅是括号中的条件.
1 if cmp a b &> /dev/null # 阻止输出.
2 then echo "Files a and b are identical."
3 else echo "Files a and b differ."
4 fi
5
6 # 非常有用的"if-grep" 结构:
7 # ------------------------
8 if grep -q Bash file
9 then echo "File contains at least one occurrence of Bash."
10 fi
11
12 word=Linux
13 letter_sequence=inu
14 if echo "$word" | grep -q "$letter_sequence"
15 # "-q"选项是用来阻止输出
16 then
17 echo "$letter_sequence found in $word"
18 else
19 echo "$letter_sequence not found in $word"
20 fi
21
22
23 if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
24 then echo "Command succeeded."
25 else echo "Command failed."
26 fi 一个if/then结构可以包含多级比较和tests. 1 if echo "Next *if* is part of the comparison for the first *if*."
2
3 if [[ $comparison = "integer" ]]
4 then (( a < b ))
5 else
6 [[ $a < $b ]]
7 fi
8
9 then
10 echo '$a is less than $b'
11 fi Example 7-1 什么情况下为真?
################################Start Script#######################################
1 #!/bin/bash
2
3 # 技巧:
4 # 如果你不确定一个特定的条件如何判断.
5 #+ 在一个if-test结构中测试它.
6
7 echo
8
9 echo "Testing \"0\""
10 if [ 0 ] # zero
11 then
12 echo "0 is true."
13 else
14 echo "0 is false."
15 fi # 0 is true.
16
17 echo
18
19 echo "Testing \"1\""
20 if [ 1 ] # one
21 then
22 echo "1 is true."
23 else
24 echo "1 is false."
25 fi # 1 is true.
26
27 echo
28
29 echo "Testing \"-1\""
30 if [ -1 ] # -1
31 then
32 echo "-1 is true."
33 else
34 echo "-1 is false."
35 fi # -1 is true.
36
37 echo
38
39 echo "Testing \"NULL\""
40 if [ ] # NULL (控状态)
41 then
42 echo "NULL is true."
43 else
44 echo "NULL is false."
45 fi # NULL is false.
46
47 echo
48
49 echo "Testing \"xyz\""
50 if [ xyz ] # 字符串
51 then
52 echo "Random string is true."
53 else
54 echo "Random string is false."
55 fi # Random string is true.
56
57 echo
58
59 echo "Testing \"\$xyz\""
60 if [ $xyz ] # 测试$xyz是否为null,但是...(明显没人定义么!)
61 # 只不过是一个未定义的变量
62 then
63 echo "Uninitialized variable is true."
64 else
65 echo "Uninitialized variable is false."
66 fi # Uninitialized variable is false.
67
68 echo
69
70 echo "Testing \"-n \$xyz\""
71 if [ -n "$xyz" ] # 更学究的的检查
72 then
73 echo "Uninitialized variable is true."
74 else
75 echo "Uninitialized variable is false."
76 fi # Uninitialized variable is false.
77
78 echo
79
80
81 xyz= # 初始化了,但是将其设为空值
82
83 echo "Testing \"-n \$xyz\""
84 if [ -n "$xyz" ]
85 then
86 echo "Null variable is true."
87 else
88 echo "Null variable is false."
89 fi # Null variable is false.
90
91
92 echo
93
94
95 # 什么时候"flase"为true?
96
97 echo "Testing \"false\""
98 if [ "false" ] # 看起来"false"只不过是个字符串而已.
99 then
100 echo "\"false\" is true." #+ 并且它test的结果就是true.
101 else
102 echo "\"false\" is false."
103 fi # "false" is true.
104
105 echo
106
107 echo "Testing \"\$false\"" # 再来一个,未声明的变量
108 if [ "$false" ]
109 then
110 echo "\"\$false\" is true."
111 else
112 echo "\"\$false\" is false."
113 fi # "$false" is false.
114 # 现在我们终于得到了期望的结果
115
116 # 如果我们test这个变量"$true"会发生什么结果?答案是和"$flase"一样,都为空,因为我
117 #+ 们并没有定义它.
118 echo
119
120 exit 0
################################End Script#########################################
练习.解释上边例子的行为(我想我解释的已经够清楚了) 1 if [ condition-true ]
2 then
3 command 1
4 command 2
5 ...
6 else
7 # 可选的(如果不需要可以省去)
8 # 如果原始的条件测试结果是false,那么添加默认的代码来执行.
9 command 3
10 command 4
11 ...
12 fi 注意:当if和then在一个条件测试的同一行中的话,必须使用";"来终止if表达式.if和then都是
关键字.关键字(或者命令)作为一个表达式的开头,并且在一个新的表达式开始之前,必须
结束上一个表达式.
1 if [ -x "$filename" ]; then Else if和elif elif
elif是else if的缩减形式.
1 if [ condition1 ]
2 then
3 command1
4 command2
5 command3
6 elif [ condition2 ]
7 # Same as else if
8 then
9 command4
10 command5
11 else
12 default-command
13 fi 使用if test condition-true这种形式和if[condition-true]这种形式是等价的.向我们前边
所说的"["是test的标记.并且以"]"结束.在if/test中并不应该这么严厉,但是新版本的Bash
需要它. 注意:test命令是Bash的内建命令,用来测试文件类型和比较字符串.因此,在Bash脚本中,test
并不调用/usr/bin/test的二进制版本(这是sh-utils工具包的一部分).同样的,[并不调用
/usr/bin/[,被连接到/usr/bin/test.
bash$ type test
test is a shell builtin
bash$ type '['
[ is a shell builtin
bash$ type '[['
[[ is a shell keyword
bash$ type ']]'
]] is a shell keyword
bash$ type ']'
bash: type: ]: not found Example 7-2 几个等效命令test,/usr/bin/test,[],和/usr/bin/[
################################Start Script#######################################
1 #!/bin/bash
2
3 echo
4
5 if test -z "$1"
6 then
7 echo "No command-line arguments."
8 else
9 echo "First command-line argument is $1."
10 fi
11
12 echo
13
14 if /usr/bin/test -z "$1" # 与内建的test结果相同
15 then
16 echo "No command-line arguments."
17 else
18 echo "First command-line argument is $1."
19 fi
20
21 echo
22
23 if [ -z "$1" ] # 与上边代码的作用相同
24 # if [ -z "$1" 应该工作,但是...
25 #+ Bash相应一个缺少关闭中括号的错误消息.
26 then
27 echo "No command-line arguments."
28 else
29 echo "First command-line argument is $1."
30 fi
31
32 echo
33
34
35 if /usr/bin/[ -z "$1" ] # 再来一个,与上边代码的作用相同
36 # if /usr/bin/[ -z "$1" # 工作,但是给个错误消息
37 # # 注意:
38 # This has been fixed in Bash, version 3.x.
38 # 在ver 3.x上,这个bug已经被Bash修正了.
39 then
40 echo "No command-line arguments."
41 else
42 echo "First command-line argument is $1."
43 fi
44
45 echo
46
47 exit 0
###############################End Script######################################### [[]]结构比Bash的[]更加灵活,这是一个扩展的test命令,从ksh88继承过来的.
注意:在[[]]结构中,将没有文件扩展或者是单词分离,但是会发生参数扩展和命令替换.
1 file=/etc/passwd
2
3 if [[ -e $file ]]
4 then
5 echo "Password file exists."
6 fi
注意:使用[[]],而不是[],能够阻止脚本中的许多逻辑错误.比如,尽管在[]中将给出一个错误,
但是&&,||,<>操作还是能够工作在一个[[]]test之中.
注意:在if后边,test命令和[]或[[]]都不是必须的.如下:
1 dir=/home/bozo
2
3 if cd "$dir" 2>/dev/null; then # "2>/dev/null" hides error message.
4 echo "Now in $dir."
5 else
6 echo "Can't change to $dir."
7 fi
if命令将返回if后边的命令的退出码. 与此相似,当在一个在使用与或列表结构的时候,test或中括号的使用,也并不一定非的有if不可
1 var1=20
2 var2=22
3 [ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"
4
5 home=/home/bozo
6 [ -d "$home" ] || echo "$home directory does not exist." (())结构扩展并计算一个算术表达式的结果.如果表达式的结果为0,它将返回1作为退出码,或
者是"false".而一个非0表达式的结果将返回0作为退出码,或者是"true". Example 7-3 算数测试使用(( ))
################################Start Script#######################################
1 #!/bin/bash
2 # 算数测试
3
4 # The (( ... )) construct evaluates and tests numerical expressions.
4 # (( ... ))结构计算并测试算数表达式的结果.
5 # 退出码将与[ ... ]结构相反!
6
7 (( 0 ))
8 echo "Exit status of \"(( 0 ))\" is $?." # 1
9
10 (( 1 ))
11 echo "Exit status of \"(( 1 ))\" is $?." # 0
12
13 (( 5 > 4 )) # true
14 echo "Exit status of \"(( 5 > 4 ))\" is $?." # 0
15
16 (( 5 > 9 )) # false
17 echo "Exit status of \"(( 5 > 9 ))\" is $?." # 1
18
19 (( 5 - 5 )) # 0
20 echo "Exit status of \"(( 5 - 5 ))\" is $?." # 1
21
22 (( 5 / 4 )) # 除法也行
23 echo "Exit status of \"(( 5 / 4 ))\" is $?." # 0
24
25 (( 1 / 2 )) # 出发结果<1
26 echo "Exit status of \"(( 1 / 2 ))\" is $?." # 结果将为0
27 # 1
28
29 (( 1 / 0 )) 2>/dev/null # 除数为0的错误
30 # ^^^^^^^^^^^
31 echo "Exit status of \"(( 1 / 0 ))\" is $?." # 1
32
33 # What effect does the "2>/dev/null" have?
33 # "2>/dev/null"的作用是什么?
34 # 如果删除"2>dev/null"将会发生什么?
35 # Try removing it, then rerunning the script.
35 # 尝试删除它,然后再运行脚本.
36
37 exit 0
################################End Script#########################################
7.2 文件测试操作
----------------
返回true如果... -e 文件存在
-a 文件存在
这个选项的效果与-e相同.但是它已经被弃用了,并且不鼓励使用
-f file是一个regular文件(不是目录或者设备文件)
-s 文件长度不为0
-d 文件是个目录
-b 文件是个块设备(软盘,cdrom等等)
-c 文件是个字符设备(键盘,modem,声卡等等)
-p 文件是个管道
-h 文件是个符号链接
-L 文件是个符号链接
-S 文件是个socket
-t 关联到一个终端设备的文件描述符
这个选项一般都用来检测是否在一个给定脚本中的stdin[-t0]或[-t1]是一个终端
-r 文件具有读权限(对于用户运行这个test)
-w 文件具有写权限(对于用户运行这个test)
-x 文件具有执行权限(对于用户运行这个test)
-g set-group-id(sgid)标志到文件或目录上
如果一个目录具有sgid标志,那么一个被创建在这个目录里的文件,这个目录属于创建
这个目录的用户组,并不一定与创建这个文件的用户的组相同.对于workgroup的目录
共享来说,这非常有用.见<<UNIX环境高级编程中文版>>第58页.
-u set-user-id(suid)标志到文件上
如果运行一个具有root权限的文件,那么运行进程将取得root权限,即使你是一个普通
用户.[1]这对于需要存取系统硬件的执行操作(比如pppd和cdrecord)非常有用.如果
没有suid标志的话,那么普通用户(没有root权限)将无法运行这种程序.
见<<UNIX环境高级编程中文版>>第58页.
-rwsr-xr-t 1 root 178236 Oct 2 2000 /usr/sbin/pppd
对于设置了suid的文件,在它的权限标志中有"s".
-k 设置粘贴位,见<<UNIX环境高级编程中文版>>第65页.
对于"sticky bit",save-text-mode标志是一个文件权限的特殊类型.如果设置了这
个标志,那么这个文件将被保存在交换区,为了达到快速存取的目的.如果设置在目录
中,它将限制写权限.对于设置了sticky bit位的文件或目录,权限标志中有"t".
drwxrwxrwt 7 root 1024 May 19 21:26 tmp/
如果一个用户并不时具有stick bit位的目录的拥有者,但是具有写权限,那么用户只
能在这个目录下删除自己所拥有的文件.这将防止用户在一个公开的目录中不慎覆盖
或者删除别人的文件,比如/tmp(当然root或者是目录的所有者可以随便删除或重命名
其中的文件).
-O 你是文件的所有者.
-G 文件的group-id和你的相同.
-N 从文件最后被阅读到现在,是否被修改. f1 -nt f2
文件f1比f2新
f1 -ot f2
f1比f2老
f1 -ef f2
f1和f2都硬连接到同一个文件. ! 非--反转上边测试的结果(如果条件缺席,将返回true) Example 7-4 test死的链接文件
################################Start Script#######################################
1 #!/bin/bash
2 # broken-link.sh
3 # Written by Lee bigelow <[email protected]>
4 # Used with permission.
5
6 #一个真正有用的shell脚本来找出死链接文件并且输出它们的引用
7 #以便于它们可以被输入到xargs命令中进行处理 :)
8 #比如: broken-link.sh /somedir /someotherdir|xargs rm
9 #
10 #这里,不管怎么说,是一种更好的方法
11 #
12 #find "somedir" -type l -print0|\
13 #xargs -r0 file|\
14 #grep "broken symbolic"|
15 #sed -e 's/^\|: *broken symbolic.*$/"/g'
16 #
17 #但这不是一个纯粹的bash,最起码现在不是.
18 #小心:小心/proc文件系统和任何的循环链接文件.
19 ##############################################################
20
21
22 #如果没对这个脚本传递参数,那么就使用当前目录.
23 #否则就使用传递进来的参数作为目录来搜索.
24 #
25 ####################
26 [ $# -eq 0 ] && directorys=`pwd` || directorys=$@
27
28 #建立函数linkchk来检查传进来的目录或文件是否是链接和是否存在,
29 #并且打印出它们的引用
30 #如果传进来的目录有子目录,
31 #那么把子目录也发送到linkchk函数中处理,就是递归目录.
32 ##########
33 linkchk () {
34 for element in $1/*; do
35 [ -h "$element" -a ! -e "$element" ] && echo \"$element\"
36 [ -d "$element" ] && linkchk $element
37 # Of course, '-h' tests for symbolic link, '-d' for directory.
37 # 当然'-h'是测试链接,'-d'是测试目录.
38 done
39 }
40
41 #如果是个可用目录,那就把每个从脚本传递进来的参数都送到linkche函数中.
42 #如果不是,那就打印出错误消息和使用信息.
43 #
44 ################
45 for directory in $directorys; do
46 if [ -d $directory ]
47 then linkchk $directory
48 else
49 echo "$directory is not a directory"
50 echo "Usage: $0 dir1 dir2 ..."
51 fi
52 done
53
54 exit 0
################################End Script#########################################
Example 28-1, Example 10-7, Example 10-3, Example 28-3, 和Example A-1 也会说明文件
测试操作的使用过程. 注意事项:
[1] 小心suid,可能引起安全漏洞,但是不会影响shell脚本.
[2] 在当代UNIX系统中,已经不使用sticky bit了,只在目录中使用.
7.3 其他比较操作
----------------
二元比较操作符,比较变量或者比较数字.注意数字与字符串的区别. 整数比较 -eq 等于,如:if [ "$a" -eq "$b" ]
-ne 不等于,如:if [ "$a" -ne "$b" ]
-gt 大于,如:if [ "$a" -gt "$b" ]
-ge 大于等于,如:if [ "$a" -ge "$b" ]
-lt 小于,如:if [ "$a" -lt "$b" ]
-le 小于等于,如:if [ "$a" -le "$b" ]
< 小于(需要双括号),如:(("$a" < "$b"))
<= 小于等于(需要双括号),如:(("$a" <= "$b"))
> 大于(需要双括号),如:(("$a" > "$b"))
>= 大于等于(需要双括号),如:(("$a" >= "$b")) 字符串比较
= 等于,如:if [ "$a" = "$b" ]
== 等于,如:if [ "$a" == "$b" ],与=等价
注意:==的功能在[[]]和[]中的行为是不同的,如下:
1 [[ $a == z* ]] # 如果$a以"z"开头(模式匹配)那么将为true
2 [[ $a == "z*" ]] # 如果$a等于z*(字符匹配),那么结果为true
3
4 [ $a == z* ] # File globbing 和word splitting将会发生
5 [ "$a" == "z*" ] # 如果$a等于z*(字符匹配),那么结果为true
一点解释,关于File globbing是一种关于文件的速记法,比如"*.c"就是,再如~也是.
但是file globbing并不是严格的正则表达式,虽然绝大多数情况下结构比较像.
!= 不等于,如:if [ "$a" != "$b" ]
这个操作符将在[[]]结构中使用模式匹配.
< 小于,在ASCII字母顺序下.如:
if [[ "$a" < "$b" ]]
if [ "$a" \< "$b" ]
注意:在[]结构中"<"需要被转义.
> 大于,在ASCII字母顺序下.如:
if [[ "$a" > "$b" ]]
if [ "$a" \> "$b" ]
注意:在[]结构中">"需要被转义.
具体参考Example 26-11来查看这个操作符应用的例子.
-z 字符串为"null".就是长度为0.
-n 字符串不为"null"
注意:
使用-n在[]结构中测试必须要用""把变量引起来.使用一个未被""的字符串来使用! -z
或者就是未用""引用的字符串本身,放到[]结构中(见Example 7-6)虽然一般情况下可
以工作,但这是不安全的.习惯于使用""来测试字符串是一种好习惯.[1] Example 7-5 数字和字符串比较
################################Start Script#######################################
1 #!/bin/bash
2
3 a=4
4 b=5
5
6 # 这里的变量a和b既可以当作整型也可以当作是字符串.
7 # 这里在算术比较和字符串比较之间有些混淆,
8 #+ 因为Bash变量并不是强类型的.
9
10 # Bash允许对整型变量操作和比较
11 #+ 当然变量中只包含数字字符.
12 # 但是还是要考虑清楚再做.
13
14 echo
15
16 if [ "$a" -ne "$b" ]
17 then
18 echo "$a is not equal to $b"
19 echo "(arithmetic comparison)"
20 fi
21
22 echo
23
24 if [ "$a" != "$b" ]
25 then
26 echo "$a is not equal to $b."
27 echo "(string comparison)"
28 # "4" != "5"
29 # ASCII 52 != ASCII 53
30 fi
31
32 # 在这个特定的例子中,"-ne"和"!="都可以.
33
34 echo
35
36 exit 0
################################End Script######################################### Example 7-6 测试字符串是否为null
################################Start Script#######################################
1 #!/bin/bash
2 # str-test.sh: 测试null字符串和非引用字符串,
3 #+ but not strings and sealing wax, not to mention cabbages and kings . . .
4 #+ 上边这句没看懂
5 # Using if [ ... ]
6
7
8 # 如果一个字符串没被初始化,那么它就没有定义的值(像这种话,总感觉像屁话)
9 # 这种状态叫做"null"(与zero不同)
10
11 if [ -n $string1 ] # $string1 没被声明和初始化
12 then
13 echo "String \"string1\" is not null."
14 else
15 echo "String \"string1\" is null."
16 fi
17 # 错误的结果.
18 # 显示$string1为非空,虽然他没被初始化.
19
20
21 echo
22
23
24 # 让我们再试一下.
25
26 if [ -n "$string1" ] # 这次$string1被引用了.
27 then
28 echo "String \"string1\" is not null."
29 else
30 echo "String \"string1\" is null."
31 fi # ""的字符串在[]结构中
32
33
34 echo
35
36
37 if [ $string1 ] # 这次$string1变成"裸体"的了
38 then
39 echo "String \"string1\" is not null."
40 else
41 echo "String \"string1\" is null."
42 fi
43 # 这工作得很好.
44 # 这个[]test操作检测string是否为null.
45 # 然而,使用("$string1")是一种很好的习惯
46 #
47 # As Stephane Chazelas points out,
48 # if [ $string1 ] 有1个参数 "]"
49 # if [ "$string1" ] 有2个参数,空的"$string1"和"]"
50
51
52
53 echo
54
55
56
57 string1=initialized
58
59 if [ $string1 ] # 再来,$string1"裸体了"
60 then
61 echo "String \"string1\" is not null."
62 else
63 echo "String \"string1\" is null."
64 fi
65 # 再来,给出了正确的结果.
66 # 不过怎么说("$string1")还是好很多,因为. . .
67
68
69 string1="a = b"
70
71 if [ $string1 ] # 再来,$string1 再次裸体了.
72 then
73 echo "String \"string1\" is not null."
74 else
75 echo "String \"string1\" is null."
76 fi
77 # 非引用的"$string1"现在给出了一个错误的结果!
78
79 exit 0
80 # Thank you, also, Florian Wisser, for the "heads-up".
################################End Script######################################### Example 7-7 zmore
################################Start Script#######################################
1 #!/bin/bash
2 # zmore
3
4 #使用'more'来查看gzip文件
5
6 NOARGS=65
7 NOTFOUND=66
8 NOTGZIP=67
9
10 if [ $# -eq 0 ] # 与 if [ -z "$1" ]同样的效果
11 # 应该是说前边的那句注释有问题,$1是可以存在的,比如:zmore "" arg2 arg3
12 then
13 echo "Usage: `basename $0` filename" >&2
14 # 错误消息到stderr
15 exit $NOARGS
16 # 脚本返回65作为退出码.
17 fi
18
19 filename=$1
20
21 if [ ! -f "$filename" ] # 将$filename ""起来,来允许可能的空白
22 then
23 echo "File $filename not found!" >&2
24 # 错误消息到stderr
25 exit $NOTFOUND
26 fi
27
28 if [ ${filename##*.} != "gz" ]
29 # 在变量替换中使用中括号
30 then
31 echo "File $1 is not a gzipped file!"
32 exit $NOTGZIP
33 fi
34
35 zcat $1 | more
36
37 # 使用过滤命令'more'
38 # 如果你想的话也可使用'less'
39
40
41 exit $? # 脚本将返回pipe的结果作为退出码
42 # 事实上,不用非的有"exit $?",但是不管怎么说,有了这句,能正规一些
43 # 将最后一句命令的执行状态作为退出码返回
################################End Script######################################### 混合比较 -a 逻辑与
exp1 -a exp2 如果exp1和exp2都为true的话,这个表达式将返回true -o 逻辑或
exp1 -o exp2 如果exp1和exp2中有一个为true的话,那么这个表达式就返回true 这与Bash的比较操作符&&和||很相像.在[[]]中使用它.
1 [[ condition1 && condition2 ]]
-o和-a一般都是和test命令或者是[]一起工作.
1 if [ "$exp1" -a "$exp2" ] 请参考Example 8-3,Example 26-16和Example A-28来查看混合比较操作的行为. 注意事项:
[1] S.C.(这家伙是个人名)指出,在使用混合比较的时候即使"$var"也可能会产生问题.
如果$string为空的话,[ -n "$string" -o "$a" = "$b" ]可能在某些版本的Bash中
会有问题.为了附加一个额外的字符到可能的空变量中的一种安全的办法是,
[ "x$string" != x -o "x$a" = "x$b" ](the "x's" cancel out)(没看懂).
cancel out是抵消的意思.
7.4 嵌套的if/then条件test
-------------------------
可以使用if/then来进行嵌套的条件test.最终的结果和上边的使用&&混合比较操作是相同的.
1 if [ condition1 ]
2 then
3 if [ condition2 ]
4 then
5 do-something # 这里只有在condition1和condition2都可用的时候才行.
6 fi
7 fi
具体请查看Example 34-4.
7.5 检查你的test知识
--------------------
系统范围的xinitrc文件可以用来启动X server.这个文件中包含了相当多的if/then test,
就像下边的节选一样:
1 if [ -f $HOME/.Xclients ]; then
2 exec $HOME/.Xclients
3 elif [ -f /etc/X11/xinit/Xclients ]; then
4 exec /etc/X11/xinit/Xclients
5 else
6 # 故障保险设置,虽然我们永远都不会走到这来.
7 # (我们在Xclients中也提供了相同的机制)它不会受伤的.
8 xclock -geometry 100x100-5+5 &
9 xterm -geometry 80x50-50+150 &
10 if [ -f /usr/bin/netscape -a -f /usr/share/doc/HTML/index.html ]; then
11 netscape /usr/share/doc/HTML/index.html &
12 fi
13 fi 对上边的"test"结构进行解释,然后检查整个文件,/etc/X11/xinit/xinitrc,并分析if/then
test结构.你可能需要查看一下后边才能讲解到的grep,sed和正则表达式的知识. 第8章 操作符和相关的主题
======================== 8.1 操作符
---------- 等号操作符 变量赋值
初始化或者修改变量的值
=
无论在算术运算还是字符串运算中,都是赋值语句.
1 var=27
2 category=minerals # No spaces allowed after the "=". 注意:不要和"="test操作符混淆.
1 # = as a test operator
2
3 if [ "$string1" = "$string2" ]
4 # if [ "X$string1" = "X$string2" ] is safer,
5 # to prevent an error message should one of the variables be empty.
6 # (The prepended "X" characters cancel out.)
7 then
8 command
9 fi 算术操作符 + 加法
- 减法
* 乘法
/ 除法
** 幂运算
1 # Bash, version 2.02, introduced the "**" exponentiation operator.
2
3 let "z=5**3"
4 echo "z = $z" # z = 125
% 取模
bash$ expr 5 % 3
2 5/3=1余2
模运算经常用在其它的事情中,比如产生特定的范围的数字(Example 9-24,
Example 9-27)和格式化程序的输出(Example 26-15,Example A-6).它甚至可以用来
产生质数,(Example A-16).事实上取模运算在算术运算中使用的频率惊人的高. Example 8-1 最大公约数
################################Start Script#######################################
1 #!/bin/bash
2 # gcd.sh: 最大公约数
3 # 使用Euclid's 算法
4
5 # 最大公约数,就是2个数能够同时整除的最大的数.
6 #
7
8 # Euclid's算法采用连续除法.
9 # 在每个循环中
10 #+ 被除数 <--- 除数
11 #+ 除数 <--- 余数
12 #+ 直到余数= 0.
13 #+ 在最后的循环中The gcd = 被除数
14 #
15 # 关于这个算法更精彩的讨论
16 # 见Jim Loy's site, http://www.jimloy.com/number/euclids.htm.
17
18
19 # ------------------------------------------------------
20 # 参数检查
21 ARGS=2
22 E_BADARGS=65
23
24 if [ $# -ne "$ARGS" ]
25 then
26 echo "Usage: `basename $0` first-number second-number"
27 exit $E_BADARGS
28 fi
29 # ------------------------------------------------------
30
31
32 gcd ()
33 {
34
35 dividend=$1 # 随便给值
36 divisor=$2 #+ 即使$2大,也没关系.
37 # Why not?
38
39 remainder=1 # 如果再循环中使用为初始化的变量.
40 #+ 那将在第一次循环中产生一个错误消息.
41
42
43 until [ "$remainder" -eq 0 ]
44 do
45 let "remainder = $dividend % $divisor"
46 dividend=$divisor # 现在使用2个最小的数重复.
47 divisor=$remainder
48 done # Euclid's algorithm
49
50 } # Last $dividend is the gcd.
50 } # 最后的$dividend就是gcd.
51
52
53 gcd $1 $2
54
55 echo; echo "GCD of $1 and $2 = $dividend"; echo
56
57
58 # 练习:
59 # --------
60 # 检查命令行参数来确定它们都是整数,
61 #+ and exit the script with an appropriate error message if not.
61 #+ 否则就选择合适的错误消息退出.
62
63 exit 0
################################End Script######################################### += 加等于(通过常量增加变量)
let "var += 5" #var将在本身值的基础上增加5
-= 减等于
*= 乘等于
let "var *= 4"
/= 除等于
%= 取模赋值,算术操作经常使用expr或者let表达式. Example 8-2 使用算术操作符
################################Start Script#######################################
1 #!/bin/bash
2 # Counting to 11 in 10 different ways.
3
4 n=1; echo -n "$n "
5
6 let "n = $n + 1" # let "n = n + 1" 这么写也行
7 echo -n "$n "
8
9
10 : $((n = $n + 1))
11 # ":" 是必须的,这是因为,如果没有":"的话,Bash将
12 #+ 尝试把"$((n = $n + 1))"解释成一个命令
13 echo -n "$n "
14
15 (( n = n + 1 ))
16 # 对于上边的方法的一个更简单的选则.
17 # Thanks, David Lombard, for pointing this out.
18 echo -n "$n "
19
20 n=$(($n + 1))
21 echo -n "$n "
22
23 : $[ n = $n + 1 ]
24 # ":" 是必须的,这是因为,如果没有":"的话,Bash将
25 #+ 尝试把"$[ n = $n + 1 ]" 解释成一个命令
26 # 即使"n"被初始化成为一个字符串,这句也能工作.
27 echo -n "$n "
28
29 n=$[ $n + 1 ]
30 # 即使"n"被初始化成为一个字符串,这句也能工作.
31 #* Avoid this type of construct, since it is obsolete and nonportable.
31 #* 尽量避免这种类型的结果,因为这已经被废弃了,并且不具可移植性.
32 # Thanks, Stephane Chazelas.
33 echo -n "$n "
34
35 # 现在来个C风格的增量操作.
36 # Thanks, Frank Wang, for pointing this out.
37
38 let "n++" # let "++n" also works.
39 echo -n "$n "
40
41 (( n++ )) # (( ++n ) also works.
42 echo -n "$n "
43
44 : $(( n++ )) # : $(( ++n )) also works.
45 echo -n "$n "
46
47 : $[ n++ ] # : $[ ++n ]] also works
48 echo -n "$n "
49
50 echo
51
52 exit 0
################################End Script######################################### 注意:在Bash中的整型变量事实上是32位的,范围是 -2147483648 到2147483647.如果超过这个
范围进行算术操作,将不会得到你期望的结果(就是溢出么).
1 a=2147483646
2 echo "a = $a" # a = 2147483646
3 let "a+=1" # 加1 "a".
4 echo "a = $a" # a = 2147483647
5 let "a+=1" # 再加1 "a" ,将超过上限了.
6 echo "a = $a" # a = -2147483648
7 # 错误 (溢出了)
在Bash 2.05b版本中,Bash支持64位整型了. 注意:Bash并不能理解浮点运算.它把包含的小数点看作字符串.
1 a=1.5
2
3 let "b = $a + 1.3" # 错误.
4 # t2.sh: let: b = 1.5 + 1.3: 表达式的语义错误(错误标志为".5 + 1.3")
5
6 echo "b = $b" # b=1
如果真想做浮点运算的话,使用bc(见12.8节),bc可以进行浮点运算或调用数学库函数. 位操作符.
(晕,有点强大过分了吧,位级操作都支持.)
位操作符在shell脚本中极少使用.它们最主要的用途看起来就是操作和test从sockets中
读出的变量."Bit flipping"与编译语言的联系很紧密,比如c/c++,在这种语言中它可以
运行得足够快.(原文有处on the fly,我查了一下,好像是没事干的意思,没理解) << 左移1位(每次左移都将乘2) <<= 左移几位,=号后边将给出左移几位
let "var <<= 2"就是左移2位(就是乘4) >> 右移1位(每次右移都将除2) >>= 右移几位 & 按位与 &= 按位与赋值 | 按位或 |= 按位或赋值 ~ 按位非 ! 按位否?(没理解和上边的~有什么区别?),感觉是应该放到下边的逻辑操作中 ^ 按位异或XOR ^= 异或赋值
逻辑操作: && 逻辑与
1 if [ $condition1 ] && [ $condition2 ]
2 # 与: if [ $condition1 -a $condition2 ] 相同
3 # 如果condition1和condition2都为true,那结果就为true.
4
5 if [[ $condition1 && $condition2 ]] # 也可以.
6 # 注意&&不允许出现在[ ... ]中.
注意:&&也可以用在and list中(见25章),但是使用的时候需要依赖上下文. || 逻辑或
1 if [ $condition1 ] || [ $condition2 ]
2 # 与: if [ $condition1 -o $condition2 ] 相同
3 # 如果condition1或condition2为true,那结果就为true.
4
5 if [[ $condition1 || $condition2 ]] # 也可以
6 # 注意||不允许出现在[ ... ]中.
注意:Bash将test每个连接到逻辑操作的状态的退出状态(见第6章).
Example 8-3 使用&&和||进行混合状态的test
################################Start Script#######################################
1 #!/bin/bash
2
3 a=24
4 b=47
5
6 if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]
7 then
8 echo "Test #1 succeeds."
9 else
10 echo "Test #1 fails."
11 fi
12
13 # 错误: if [ "$a" -eq 24 && "$b" -eq 47 ]
14 #+ 尝试执行' [ "$a" -eq 24 '
15 #+ 因为没找到']'所以失败了.
16 #
17 # 注意: 如果 [[ $a -eq 24 && $b -eq 24 ]] 能够工作.
18 # 那这个[[]]的test结构就比[]结构更灵活了.
19 #
20 # (在17行的"&&"与第6行的"&&"意义不同)
21 # Thanks, Stephane Chazelas, for pointing this out.
22
23
24 if [ "$a" -eq 98 ] || [ "$b" -eq 47 ]
25 then
26 echo "Test #2 succeeds."
27 else
28 echo "Test #2 fails."
29 fi
30
31
32 # -a和-o选项提供了
33 #+ 一种可选的混合test方法.
34 # Thanks to Patrick Callahan for pointing this out.
35
36
37 if [ "$a" -eq 24 -a "$b" -eq 47 ]
38 then
39 echo "Test #3 succeeds."
40 else
41 echo "Test #3 fails."
42 fi
43
44
45 if [ "$a" -eq 98 -o "$b" -eq 47 ]
46 then
47 echo "Test #4 succeeds."
48 else
49 echo "Test #4 fails."
50 fi
51
52
53 a=rhino
54 b=crocodile
55 if [ "$a" = rhino ] && [ "$b" = crocodile ]
56 then
57 echo "Test #5 succeeds."
58 else
59 echo "Test #5 fails."
60 fi
61
62 exit 0
################################End Script#########################################
&&和||操作也能在算术运算的上下文中找到.
bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0))
1 0 1 0 混杂操作:
, 逗号操作符
逗号操作符可以连接2个或多个算术运算.所有的操作都会被执行,但是只有最后一个
操作作为结果.
1 let "t1 = ((5 + 3, 7 - 1, 15 - 4))"
2 echo "t1 = $t1" # t1 = 11
3
4 let "t2 = ((a = 9, 15 / 3))" # Set "a" and calculate "t2".
5 echo "t2 = $t2 a = $a" # t2 = 5 a = 9
","主要用在for循环中,具体见Example 10-12.
8.2 数字常量
------------
shell脚本默认都是将数字作为10进制数处理,除非这个数字某种特殊的标记法或前缀开头.
以0开头就是8进制.以0x开头就是16进制数.使用BASE#NUMBER这种形式可以表示其它进制
表示法 Example 8-4 数字常量的处理
################################Start Script#######################################
1 #!/bin/bash
2 # numbers.sh: 数字常量的几种不同的表示法
3
4 # 10进制: 默认
5 let "dec = 32"
6 echo "decimal number = $dec" # 32
7 # 一切都很正常
8
9
10 # 8进制: 以'0'(零)开头
11 let "oct = 032"
12 echo "octal number = $oct" # 26
13 # 表达式的结果用10进制表示.
14 #
15
16 # 16进制表示:数字以'0x'或者'0X'开头
17 let "hex = 0x32"
18 echo "hexadecimal number = $hex" # 50
19 # 表达式的结果用10进制表示.
20
21 # 其它进制: BASE#NUMBER
22 # BASE between 2 and 64.
22 # 2到64进制都可以.
23 # NUMBER必须在BASE的范围内,具体见下边.
24
25
26 let "bin = 2#111100111001101"
27 echo "binary number = $bin" # 31181
28
29 let "b32 = 32#77"
30 echo "base-32 number = $b32" # 231
31
32 let "b64 = 64#@_"
33 echo "base-64 number = $b64" # 4031
34 # 这种64进制的表示法中的每位数字都必须在64进制表示法的限制字符内.
35 # 10 个数字+ 26 个小写字母+ 26 个大写字母+ @ + _
36
37
38 echo
39
40 echo $((36#zz)) $((2#10101010)) $((16#AF16)) $((53#1aA))
41 # 1295 170 44822 3375
42
43
44 # 重要的注意事项:
45 # ---------------
46 # 如果使用的每位数字超出了这个进制表示法规定字符的范围的话,
47 #+ 将给出一个错误消息.
48
49 let "bad_oct = 081"
50 # (部分的) 错误消息输出:
51 # bad_oct = 081: too great for base (error token is "081")
52 # Octal numbers use only digits in the range 0 - 7.
53
54 exit 0 # Thanks, Rich Bartell and Stephane Chazelas, for clarification.
################################End Script#########################################
相关阅读 更多 +