每一个完善的编程语言都应该能测试一个条件。然后依据测试的结果做进一步的动作。Bash有test命令,各种括号及内嵌的操作符,还有if/then结构来完成上面的功能。
一个if/then结构测试一列命令的退出状态是否为0(因为依照惯例,0意味着命令执行成功),如果是0则会执行一个或多个命令。
有一个命令 [ (左方括是特殊字符). 它和test是同义词,因为效率的原因,它被内建在shell里。这个命令的参数是比较表达式或者文件测试,它会返回一个退出状态指示比较的结果(0表示真,1表示假)。
在版本2.02,Bash引入了[[ ... ]] 扩展的测试命令,它使熟悉其他语言中这种比较测试的程序员也能很快熟悉比较操作。注意[[是一个关键字 ,不是一个命令。
Bash把[[ $a -lt $b ]]看成一个返回退出状态的单元。
The (( ... )) and let ... constructs also return an exit status of 0 if the arithmetic expressions they evaluate expand to a non-zero value. These arithmetic expansion constructs may therefore be used to perform arithmetic comparisons.
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"使grep禁止输出. 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结构能包含嵌套的比较和测试。
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 |
谦虚的Stéphane Chazelas解释了"if-test"结构的细节
例子 7-1. 事实是什么?
1 #!/bin/bash 2 3 # 小技巧: 4 # 如果你不确定某一条件怎么被求值, 5 #+ 可以用一个if-test结构来测试. 6 7 echo 8 9 echo "Testing \"0\"" 10 if [ 0 ] # 0 11 then 12 echo "0 is true." 13 else 14 echo "0 is false." 15 fi # 0为真. 16 17 echo 18 19 echo "Testing \"1\"" 20 if [ 1 ] # 1 21 then 22 echo "1 is true." 23 else 24 echo "1 is false." 25 fi # 1为真. 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为真. 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为假. 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 # 任意字符串为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 # 未初始化的变量为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 # 未始初化的变量为false. 77 78 echo 79 80 81 xyz= # 已初始化, 但设置成null值. 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值变量为假. 90 91 92 echo 93 94 95 # 什么时候"false"为真? 96 97 echo "Testing \"false\"" 98 if [ "false" ] # "false"是一个字符串. 99 then 100 echo "\"false\" is true." #+ 它被测试为真. 101 else 102 echo "\"false\" is false." 103 fi # "false"为真. 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"变量为假. 114 # 现在, 我们取得了预期的效果. 115 116 # 如果我们测试未初始化的变量"$true"会发生什么? 117 118 echo 119 120 exit 0 |
练习. 上面例子 7-1的解释.
1 if [ condition-true ] 2 then 3 command 1 4 command 2 5 ... 6 else 7 # 或选的(如果不需要就可去掉). 8 # 如果条件测试失败,就在这里加入默认的执行命令. 9 command 3 10 command 4 11 ... 12 fi |
当if和then在同一行的时候,一个分号(;)必须用在if语句的结尾。if和then都是关键字.关键字(或命令)开始一个语句,如果在同一行开始另一个新语句时,前面一个语句必须用分号(;)结束。
|
elif是else if的缩写。作用是在一个if/then里嵌入一个内部的if/then结构。
1 if [ condition1 ] 2 then 3 command1 4 command2 5 command3 6 elif [ condition2 ] 7 # 和else if相同 8 then 9 command4 10 command5 11 else 12 default-command 13 fi |
if test condition-true结构是精确等同于if [ condition-true ].如果用[ condition-true ]结构,左方括[ , 是一个调用test命令的标识。右方括]在一个if/test中封闭左方括[,但它不是必须的,不过新一些的Bash版本会要求有。
Bash内建的test命令测试文件类型和比较字符串. 因此,在一个Bash脚本中test语句不必调用外部的/usr/bin/test的二进制文件,这个test程序是sh-utils包的一部分。同样的,[也不调用/usr/bin/[,/usr/bin/[是链接到/usr/bin/test一个符号链接。
|
例子 7-2. 等价的测试命令:test,/usr/bin/test,[]和/usr/bin/[
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 # 这个已经在bash 3.x版本被修补好了。 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 |
[[]]结构比Bash版本的[]更通用。它是从ksh88中引进的test命令的扩展。
在[[和]]之间的所有的字符都不会被文件扩展或是标记分割,但是会有参数引用和命令替换。 |
1 file=/etc/passwd 2 3 if [[ -e $file ]] 4 then 5 echo "Password file exists." 6 fi |
用[[ ... ]]测试结构比用[ ... ]更能防止脚本里的许多逻辑错误。比如说,&&,||,<和>操作符能在一个[[]]测试里通过,但在[]结构会发生错误。 |
在一个if的后面,不必一定是test命令或是test结构([]或是[[]])。
同样的,在一个测试方括号里面的条件测试也可以用列表结构(list construct)而不必用if。
|
(( ))结构扩展并计算一个算术表达式的值。如果表达式值为0,会返回1或假作为退出状态码。一个非零值的表达式返回一个0或真作为退出状态码。这个结构和先前test命令及[]结构的讨论刚好相反。
例子 7-3. 用(( ))进行算术测试
1 #!/bin/bash 2 # 算术测试. 3 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 )) # 真 14 echo "Exit status of \"(( 5 > 4 ))\" is $?." # 0 15 16 (( 5 > 9 )) # 假 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 # 起了什么作用? 34 # 如果不要"2>/dev/null"这句会怎么样? 35 # 试试去掉这句再运行这个脚本. 36 37 exit 0 |