25. 列表结构

"与列表""或列表"结构能够提供一种手段, 这种手段能够用来处理一串连续的命令. 这样就可以有效的替换掉嵌套的if/then结构, 甚至能够替换掉case语句.

把命令连接到一起

与列表

  1 command-1 && command-2 && command-3 && ... command-n
如果每个命令执行后都返回true(0)的话, 那么命令将会依次执行下去. 如果其中的某个命令返回false(非零值)的话, 那么这个命令链就会被打断, 也就是结束执行, (那么第一个返回false的命令, 就是最后一个执行的命令, 其后的命令都不会执行).


例子 25-1. 使用"与列表"来测试命令行参数

  1 #!/bin/bash
  2 # "与列表"
  3 
  4 if [ ! -z "$1" ] && echo "Argument #1 = $1" && [ ! -z "$2" ] && echo "Argument #2 = $2"
  5 then
  6   echo "At least 2 arguments passed to script."
  7   # 所有连接起来的命令都返回true. 
  8 else
  9   echo "Less than 2 arguments passed to script."
 10   # 整个命令列表中至少有一个命令返回false. 
 11 fi  
 12 # 注意, "if [ ! -z $1 ]"也可以, 但它是有所假定的等价物. 
 13 #   if [ -n $1 ] 这个不行. 
 14 #     然而, 如果加了引用就行了. 
 15 #  if [ -n "$1" ] 这样就行了. 
 16 #     小心!
 17 # 最好将你要测试的变量引用起来, 这么做是非常好的习惯. 
 18 
 19 
 20 # 下面这段代码与上面代码是等价的, 不过下面这段代码使用的是"纯粹"的if/then结构. 
 21 if [ ! -z "$1" ]
 22 then
 23   echo "Argument #1 = $1"
 24 fi
 25 if [ ! -z "$2" ]
 26 then
 27   echo "Argument #2 = $2"
 28   echo "At least 2 arguments passed to script."
 29 else
 30   echo "Less than 2 arguments passed to script."
 31 fi
 32 # 这么写的话, 行数太多了, 没有"与列表"来的精简. 
 33 
 34 
 35 exit 0


例子 25-2. 使用"与列表"来测试命令行参数的另一个例子

  1 #!/bin/bash
  2 
  3 ARGS=1        # 期望的参数个数. 
  4 E_BADARGS=65  # 如果传递的参数个数不正确, 那么给出这个退出码. 
  5 
  6 test $# -ne $ARGS && echo "Usage: `basename $0` $ARGS argument(s)" && exit $E_BADARGS
  7 #  如果"条件1"测试为true (表示传递给脚本的参数个数不对), 
  8 #+ 则余下的命令会被执行, 并且脚本将结束运行. 
  9 
 10 # 只有当上面的测试条件为false的时候, 这行代码才会被执行. 
 11 echo "Correct number of arguments passed to this script."
 12 
 13 exit 0
 14 
 15 # 为了检查退出码, 在脚本结束的时候可以使用"echo $?"来查看退出码. 

当然, 与列表也可以给变量设置默认值.
  1 arg1=$@       # 不管怎样, 将$arg1设置为命令行参数. 
  2 
  3 [ -z "$arg1" ] && arg1=DEFAULT
  4               # 如果没有指定命令行参数, 则把$arg1设置为DEFAULT. 

或列表

  1 command-1 || command-2 || command-3 || ... command-n
如果每个命令都返回false, 那么命令链就会执行下去. 一旦有一个命令返回true, 命令链就会被打断, 也就是结束执行, (第一个返回true的命令将会是最后一个执行的命令). 显然, 这和"与列表"完全相反.


例子 25-3. 将"或列表""与列表"结合使用

  1 #!/bin/bash
  2 
  3 #  delete.sh, 不是很聪明的文件删除方法. 
  4 #  Usage: delete filename
  5 
  6 E_BADARGS=65
  7 
  8 if [ -z "$1" ]
  9 then
 10   echo "Usage: `basename $0` filename"
 11   exit $E_BADARGS  # 没有参数? 退出脚本. 
 12 else  
 13   file=$1          # 设置文件名.
 14 fi  
 15 
 16 
 17 [ ! -f "$file" ] && echo "File \"$file\" not found. \
 18 Cowardly refusing to delete a nonexistent file."
 19 # 与列表, 在文件不存在时将会给出错误信息. 
 20 # 注意echo命令使用了一个续行符, 这样下一行的内容, 也会作为echo命令的参数. 
 21 
 22 [ ! -f "$file" ] || (rm -f $file; echo "File \"$file\" deleted.")
 23 # 或列表, 如果文件存在, 那就删除此文件. 
 24 
 25 # 注意, 上边的两个逻辑相反. 
 26 # 与列表在true的情况下才执行, 或列表在false的时候才执行. 
 27 
 28 exit 0

Caution

如果"或列表"中的第一个命令返回true, 那么, "或列表"中的第一个命令还是执行.

  1 # ==> 下面的片断摘自于脚本/etc/rc.d/init.d/single, 这个脚本由Miquel van Smoorenburg所编写. 
  2 #+==> 用于展示"与"/"或"列表的使用. 
  3 # ==> "箭头"注释是由本书作者添加的. 
  4 
  5 [ -x /usr/bin/clear ] && /usr/bin/clear
  6   # ==> 如果/usr/bin/clear存在, 那么就调用它. 
  7   # ==> 在调用一个命令前, 检查一下它是否存在. 
  8   #+==> 这样可以避免错误信息, 和其他愚蠢的结果. 
  9 
 10   # ==> . . .
 11 
 12 # 如果他们想在单用户模式下运行某些程序, 可能也会运行它...
 13 for i in /etc/rc1.d/S[0-9][0-9]* ; do
 14         # 检查一下脚本是否在那里. 
 15         [ -x "$i" ] || continue
 16   # ==> 如果在$PWD中没发现相应的文件, 
 17   #+==> 则会使用"continue"跳过本次循环. 
 18 
 19         # 不接受备份文件, 也不接受由rpm产生的文件. 
 20         case "$1" in
 21                 *.rpmsave|*.rpmorig|*.rpmnew|*~|*.orig)
 22                         continue;;
 23         esac
 24         [ "$i" = "/etc/rc1.d/S00single" ] && continue
 25   # ==> 设置脚本名, 但现在还不执行它. 
 26         $i start
 27 done
 28 
 29   # ==> . . .

Important

与列表或列表退出状态码由最后一个命令的退出状态所决定.

可以灵活的将"与"/"或"列表组合在一起, 但是这么做的话, 会使得逻辑变得很复杂, 并且需要经过仔细的测试.
  1 false && true || echo false         # false
  2 
  3 # 与下面的结果相同
  4 ( false && true ) || echo false     # false
  5 # But *not*
  6 false && ( true || echo false )     # (没有输出)
  7 
  8 #  注意, 以从做到右的顺序进行分组与求值, 
  9 #+ 这是因为逻辑操作符"&&"和"||"具有相同的优先级. 
 10 
 11 #  最好避免这么复杂的情况, 除非你非常了解你到底在做什么. 
 12 
 13 #  感谢, S.C.

也请参考例子 A-7例子 7-4, 这两个例子展示了如何使用与/或列表来测试变量.