七章:测试

每一个完善的编程语言都应该能测试一个条件。然后依据测试的结果做进一步的动作。Bash有test命令,各种括号及内嵌的操作符,还有if/then结构来完成上面的功能。

7.1. 测试结构


例子 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都是关键字.关键字(或命令)开始一个语句,如果在同一行开始另一个新语句时,前面一个语句必须用分号(;)结束。

   1 if [ -x "$filename" ]; then

Else if 和 elif

elif

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一个符号链接。

 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
 	      


例子 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结构([]或是[[]])。
   1 dir=/home/bozo
   2 
   3 if cd "$dir" 2>/dev/null; then   # "2>/dev/null"会隐藏错误的信息.
   4   echo "Now in $dir."
   5 else
   6   echo "Can't change to $dir."
   7 fi
"if COMMAND"结构会返回COMMAND命令的退出状态码。

同样的,在一个测试方括号里面的条件测试也可以用列表结构(list construct)而不必用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或假作为退出状态码。一个非零值的表达式返回一个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