shell教程
OutOfMemory.CN技术专栏-> shell-> shell教程-> 第七章:测试

第七章:测试

每一个完善的编程语言都应该能测试一个条件。然后依据测试的结果做进一步的动作。Bash有test命令,各种括号及内嵌的操作符,还有if/then结构来完成上面的功能。一个if/then结构测试一列命令的退出状态是否为0(因为依照惯例,0意味着命令执行成功),如果是0则会执行一个或多个命令。有一个命令[(左方括是特殊字符).它和test是同义词,因为效率的原因,它被内建在shell里。这个命令的参数是

每一个完善的编程语言都应该能测试一个条件。然后依据测试的结果做进一步的动作。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.

    let "1<2" returns 0 (as "1<2" expands to "1")
    (( 0 && 1 )) returns 1 (as "0 && 1" expands to "0")

  • if 命令不仅能测试由方括号括起来的条件,也能测试任何命令。

    if cmp a b &> /dev/null  # 禁止输出.
    then echo "Files a and b are identical."
    else echo "Files a and b differ."
    fi
    
    # 非常有用的"if-grep"组合:
    # -----------------------------------
    if grep -q Bash file
    then echo "File contains at least one occurrence of Bash."
    fi
    
    word=Linux
    letter_sequence=inu
    if echo "$word" | grep -q "$letter_sequence"
    # 选项"-q"使grep禁止输出.
    then
      echo "$letter_sequence found in $word"
    else
      echo "$letter_sequence not found in $word"
    fi
    
    
    if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
    then echo "Command succeeded."
    else echo "Command failed."
    fi

  • 一个if/then结构能包含嵌套的比较和测试。

    if echo "Next *if* is part of the comparison for the first *if*."
    
      if [[ $comparison = "integer" ]]
        then (( a < b ))
      else
        [[ $a < $b ]]
      fi
    
    then
      echo '$a is less than $b'
    fi

    谦虚的Stéphane Chazelas解释了"if-test"结构的细节


例子 7-1. 事实是什么?

#!/bin/bash

#  小技巧:
#  如果你不确定某一条件怎么被求值,
#+ 可以用一个if-test结构来测试.

echo

echo "Testing \"0\""
if [ 0 ]      # 0
then
  echo "0 is true."
else
  echo "0 is false."
fi            # 0为真.

echo

echo "Testing \"1\""
if [ 1 ]      # 1
then
  echo "1 is true."
else
  echo "1 is false."
fi            # 1为真.

echo

echo "Testing \"-1\""
if [ -1 ]     # -1
then
  echo "-1 is true."
else
  echo "-1 is false."
fi            # -1为真.

echo

echo "Testing \"NULL\""
if [ ]        # NULL (空条件)
then
  echo "NULL is true."
else
  echo "NULL is false."
fi            # NULL为假.

echo

echo "Testing \"xyz\""
if [ xyz ]    # 字符串
then
  echo "Random string is true."
else
  echo "Random string is false."
fi            # 任意字符串为true.

echo

echo "Testing \"\$xyz\""
if [ $xyz ]   # 变量$xyz为null值, 但...
              # 它只是一个未初始化的变量.
then
  echo "Uninitialized variable is true."
else
  echo "Uninitialized variable is false."
fi            # 未初始化的变量为false.

echo

echo "Testing \"-n \$xyz\""
if [ -n "$xyz" ]            # 进一步实验核实.
then
  echo "Uninitialized variable is true."
else
  echo "Uninitialized variable is false."
fi            # 未始初化的变量为false.

echo


xyz=          # 已初始化, 但设置成null值.

echo "Testing \"-n \$xyz\""
if [ -n "$xyz" ]
then
  echo "Null variable is true."
else
  echo "Null variable is false."
fi            # Null值变量为假.


echo


# 什么时候"false"为真?

echo "Testing \"false\""
if [ "false" ]              #  "false"是一个字符串.
then
  echo "\"false\" is true." #+ 它被测试为真.
else
  echo "\"false\" is false."
fi            # "false"为真.

echo

echo "Testing \"\$false\""  # 再来,未初始化的变量.
if [ "$false" ]
then
  echo "\"\$false\" is true."
else
  echo "\"\$false\" is false."
fi            # "$false"变量为假.
              # 现在, 我们取得了预期的效果.

#  如果我们测试未初始化的变量"$true"会发生什么?

echo

exit 0

练习. 上面例子 7-1的解释.

if [ condition-true ]
then
   command 1
   command 2
   ...
else
   # 或选的(如果不需要就可去掉).
   # 如果条件测试失败,就在这里加入默认的执行命令.
   command 3
   command 4
   ...
fi

当if和then在同一行的时候,一个分号(;)必须用在if语句的结尾。if和then都是关键字.关键字(或命令)开始一个语句,如果在同一行开始另一个新语句时,前面一个语句必须用分号(;)结束。

if [ -x "$filename" ]; then

Else if 和 elif

elif

elif是else if的缩写。作用是在一个if/then里嵌入一个内部的if/then结构。

if [ condition1 ]
then
   command1
   command2
   command3
elif [ condition2 ]
# 和else if相同
then
   command4
   command5
else
   default-command
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/[

#!/bin/bash

echo

if test -z "$1"
then
  echo "No command-line arguments."
else
  echo "First command-line argument is $1."
fi

echo

if /usr/bin/test -z "$1"      # 和内建的"test"命令一样.
then
  echo "No command-line arguments."
else
  echo "First command-line argument is $1."
fi

echo

if [ -z "$1" ]                # 和上面代码块的功能一样
#   if [ -z "$1"                应该来说会运行, 但是...
#+  Bash给出错误说少了一个封闭的右方括.
then
  echo "No command-line arguments."
else
  echo "First command-line argument is $1."
fi

echo


if /usr/bin/[ -z "$1" ]       # 同样和上面的代码块一样.
# if /usr/bin/[ -z "$1"       # 工作, 但还是给出一个错误信息.
#                             # 注意:
#                               这个已经在bash 3.x版本被修补好了。
then
  echo "No command-line arguments."
else
  echo "First command-line argument is $1."
fi

echo

exit 0

[[]]结构比Bash版本的[]更通用。它是从ksh88中引进的test命令的扩展。

在[[和]]之间的所有的字符都不会被文件扩展或是标记分割,但是会有参数引用和命令替换。

file=/etc/passwd

if [[ -e $file ]]
then
  echo "Password file exists."
fi

[[ ... ]]测试结构比用[ ... ]更能防止脚本里的许多逻辑错误。比如说,&&,||,<和>操作符能在一个[[]]测试里通过,但在[]结构会发生错误。

在一个if的后面,不必一定是test命令或是test结构([]或是[[]])。

dir=/home/bozo

if cd "$dir" 2>/dev/null; then   # "2>/dev/null"会隐藏错误的信息.
  echo "Now in $dir."
else
  echo "Can't change to $dir."
fi
"if COMMAND"结构会返回COMMAND命令的退出状态码。

同样的,在一个测试方括号里面的条件测试也可以用列表结构(list construct)而不必用if。

var1=20
var2=22
[ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"

home=/home/bozo
[ -d "$home" ] || echo "$home directory does not exist."

(( ))结构扩展并计算一个算术表达式的值。如果表达式值为0,会返回1或假作为退出状态码。一个非零值的表达式返回一个0或真作为退出状态码。这个结构和先前test命令及[]结构的讨论刚好相反。


例子 7-3. 用(( ))进行算术测试

#!/bin/bash
# 算术测试.

# (( ... ))结构会求值并测试该值。
# 退出状态码与[ ... ]结构正好相反!

(( 0 ))
echo "Exit status of \"(( 0 ))\" is $?."         # 1

(( 1 ))
echo "Exit status of \"(( 1 ))\" is $?."         # 0

(( 5 > 4 ))                                      # 真
echo "Exit status of \"(( 5 > 4 ))\" is $?."     # 0

(( 5 > 9 ))                                      # 假
echo "Exit status of \"(( 5 > 9 ))\" is $?."     # 1

(( 5 - 5 ))                                      # 0
echo "Exit status of \"(( 5 - 5 ))\" is $?."     # 1

(( 5 / 4 ))                                      # 除法有效.
echo "Exit status of \"(( 5 / 4 ))\" is $?."     # 0

(( 1 / 2 ))                                      # 除法计算结果< 1
echo "Exit status of \"(( 1 / 2 ))\" is $?."     # 截取为0.
                                                 # 1

(( 1 / 0 )) 2>/dev/null                          # 除以0的非法计算.
#           ^^^^^^^^^^^
echo "Exit status of \"(( 1 / 0 ))\" is $?."     # 1

# 起了什么作用?
# 如果不要"2>/dev/null"这句会怎么样?
# 试试去掉这句再运行这个脚本.

exit 0

© 内存溢出 OutOfMemory.CN