条件语句格式
if 命令的一般格式为:
if commandtthencommandcommand...fi
其中,commandt是要执行的命令,命令的退出状态会被测试。如果退出状态为0,执行then和fi之间的命令。否则,跳过这些命令。
退出状态
要程序执行完成,就会向Shell返回一个退出状态码。这个状态码是一个数值,指明了程序运行是否成功。按照惯例,为0的退出状态码表示程序运行成功;非0的退出状态码表示程序运行失败,不同的值对应着不同的失败原因。
对于管道而言,退出状态对应的是管道中的最后一个命令。因此,在下列管道中:
who | grep fred
grep的退出状态被Shell作为整个管道的退出状态。在这个例子中,为0的退出状态(成功)意味着在who的输出中找到了fred(也就是说,在命令执行的时候,fred处于已登录状态)
变量$?
Shell会将变量$?自动设置为最后一条命令的退出状态。你自然可以使用echo在终端中显示该变量的值。
$ cp phonebook phone2$ echo $?0 复制命令执行成功$ cp nosuch backupcp: cannot access nosuch$ echo $?2 复制命令执行失败
注意,对于一些命令来说,其表示
执行失败的数值在不同的UNIX版本中并不相同,但表示执行成功的退出状态码总是0。
## 确定用户是否已经登录#user="$1"if who | grep "$user"thenecho "$user is logged on"fi
grep不仅会在管道中返回退出状态,而且还会将匹配的行写入到标准输出,哪怕你对此并不感兴趣。考虑到除了测试退出状态码之外,我们并不想看到命令结果,可以将grep的输出重定向到系统的垃圾桶:/dev/null。这是一个特殊的系统文件,任何人都可以读取(立刻会得到一个 EOF)或写入。向该文件写入的任何东西都会消失,就像是一个巨大的黑洞一样。
who | grep "$user" > /dev/null
test命令
在测试单个或多个条件时,用得更多的是Shell内建的test命令。其一般格式为:
test expression
其中,expression描述了待测试的条件。test会对expression求值,如果结果为真,返回为0的退出状态码;如果结果为假,返回非0的退出状态码。
字符串操作符
来看一个例子,如果Shell变量name的值为julio,下列命令会返回为0的退出状态码:
test "$name" = julio
操作符=用来测试两个值是否一样。在本例中,我们要测试Shell变量 name 的内容是否和字符串julio一样。如果一样的话,test返回为 0的退出状态码;否则返回非0值。
注意,
test命令所有的操作数($name和julio)和操作符(=)都必须是独立的参数,也就是说,它们彼此之间必须使用一个或多个空白字符分隔。
再来看if命令,如果name中包含的是字符串julio,则显示信息”Would you like to play a game?”,可以这样来写:
if test "$name" = juliothenecho "Would you like to play a game?"fi
当if命令执行时,随后的命令也会被执行并会对其退出状态求值。test命令被传入了3个参数:$name(会被替换成相应的值)、=和 julio。然后 test 会测试第一个参数是否等于第三个参数,如果相等,会返回为0的退出状态码,反之则返回非0值。if语句会测试test返回的退出状态。如果为0,执行then和fi之间的命令,在本例中,执行的是echo命令。如果是非0的退出状态,则跳过echo命令。
如上例中所演示的,将test的参数放在双引号中(允许变量替换)是一种良好的编程实践。这确保了就算其值为空,test也能够将其视为参数。
$ name= 将变量 name 设为空$ test $name = juliosh: test: argument expected
因为name的值为空,所以只有两个参数被传给test:=和julio(Shell 会在将命令行解析为参数之前替换 name 的值)。实际上,当 Shell将$name替换之后,就相当于输入了下列命令:
test = julio
如果把变量放进双引号中,就能够确保 test 可以识别出这个参数,因为当参数值为空的时候,引号就相当于一个占位符。
$ test "$name" = julio$ echo $? 打印出退出状态码1
即便name的值为空,Shell传入test的仍旧是3个参数,其中第一个参数是空值。
| 操作符 | 如果满足下列条件,则返回真(退出状态码为 0) |
|---|---|
| string1 = string2 | string1 等于 string2 |
| string1 != string2 | string1 不等于 string2 |
| string | string 不为空 |
| -n string | string 不为空(test 必须能够识别出作为参数的 string) |
| -z string | string 为空(test 必须能够识别出作为参数的 string) |
注意,test对于参数也是有讲究的。举例来说,如果Shell变量symbol的值是一个等号,来看看在测试其长度是否为0时会发生什么: sh $ echo $symbol = $ test -z "$symbol" sh: test: argument expected 操作符=的优先级要比操作符-z高,因此test会将命令作为等量关系测试来处理,希望在=之后找到另一个参数。为了避免这种问题,很多Shell程序员会将test命令写作: sh test X"$symbol" = X 如果symbol为空值的话,结果为真;否则结果为假。symbol前面的X避免了test将保存在symbol中的字符误认为是操作符。
test的另一种格式
Shell程序员经常用到test命令,该命令的另外一种格式,能够让你的程序看起来整洁得多:[。这种写法提高了if语句以及Shell脚本中其他条件测试的可读性。test命令一般格式为:
test expression
也可以使用另外一种格式来表示:
[ expression ]
在[之后以及]之前必须要有空格。你可以用新的格式重写之前例子中的 test 命令:
$ [ -z "$nonnullvar" ]$ echo $?1
在if命令中使用的时候,格式如下:
if [ "$name" = julio ]thenecho "Would you like to play a game?"fi
整数操作符
test有不少可以用来执行整数比较的操作符。
| 操作符 | 如果满足下列条件,则返回真(退出状态码为 0) |
|---|---|
| int1 -eq int2 | int1等于 int2 |
| int1 -ge int2 | int1大于或等于 int2 |
| int1 -gt int2 | int1大于 int2 |
| int1 -le int2 | int1小于或等于 int2 |
| int1 -lt int2 | int1小于 int2 |
| int1 -ne int2 | int1不等于 int2 |
有一点值得注意:在使用整数操作符时,将变量的值视为整数的是test命令,而非Shell。因此,无论Shell变量的类型是什么,都能够进行比较。 sh $ x1="005" $ x2=" 10" $ [ "$x1" = 5 ] 字符串比较 $ echo $? 1 假 $ [ "$x1" -eq 5 ] 整数比较 $ echo $? 0 真 $ [ "$x2" = 10 ] 字符串比较 $ echo $? 1 假 $ [ "$x2" -eq 10 ] 整数比较 $ echo $? 0 真
文件操作符
几乎所有的Shell程序都少不了要跟一个或多个文件打交道。因此,test提供了各种可以用来查询文件信息的操作符。这些操作符都是一元操作符,也就是说,它们只接受其后跟随单个参数。在所有情况下,第二个参数都是文件名(包括必要的目录名)。
| 操作符 | 如果满足下列条件,则返回真(退出状态码为 0) |
|---|---|
| -d file | file 是一个目录 |
| -e file | file 存在 |
| -f file | file 是一个普通文件 |
| -r file | file 可由进程读取 |
| -s file | file 不是空文件 |
| -w file | file 可由进程写入 |
| -x file | file 是可执行的 |
| -L file | file 是一个符号链接 |
逻辑否定操作符!
一元逻辑否定操作符!可以放置在任意的test表达式之前,否定该表达式的求值结果。例如:
[ ! -r /users/steve/phonebook ]
如果/user/steve/phonebook 不可读的话,会返回为 0 的退出状态码(真)
逻辑’与’操作符-a
操作符-a在两个表达式之间执行逻辑’与’运算,仅当这两个表达式均为真时,才返回真。因此,下列命令:
[ -f "$mailfile" -a -r "$mailfile" ]
如果由$mailfile 指定的文件是一个普通文件且能够被当前用户读取,则返回真。
操作符
-a两边多出的空格是为了提高表达式的可读性,不影响执行效果。
在有些情况下,重要的是要知道test只要发现条件不成立,立刻就会停止对逻辑’与’表达式求值,因此像下面的语句:
[ ! -f "$file" -a $(who > $file) ]
如果! -f(文件不存在)测试没有通过的话,就不会在子Shell中执行who命令,因为test已经知道逻辑’与’(-a)表达式的结果为假了。
括号
你可以在 test 表达式中利用括号来根据需要改变求值顺序,不过记得把括号本身引用起来,因为它们对于Shell是有特殊含义的。
[ \( "$count" -ge 0 \) -a \( "$count" -lt 10 \) ]
括号两边必须有空格,因为
test要求条件语句中的每一个元素都是独立的参数。
逻辑’或’操作符-o
操作符-o和-a类似,只是它在两个表达式之间形成的是逻辑’或’关系。也就是说,两个表达式只要其中一个为真,或者两个都为真,整个逻辑表达式的值就为真。
[ -n "$mailopt" -o -r $HOME/mailfile ]
如果变量mailopt不为空或者$HOME/mailfile可由当前用户读取,则结果为真。
操作符-o的优先级要比操作符-a低,这就意味着表达式:
"$a" -eq 0 -o "$b" -eq 2 -a "$c" -eq 10
会由test按照以下方式求值:
"$a" -eq 0 -o ("$b" -eq 2 -a "$c" -eq 10)
else
if命令中可以加入else,其一般格式为:
if commandtthencommandcommand. . .elsecommandcommand. . .fi
其中,commandt会被执行并对其退出状态求值。如果为真(0),执行then代码块(then 和 else 之间的所有语句)并忽略else代码块。如果为假(非 0),则忽略then代码块,执行else代码块(else 与 fi 之间的所有语句)。无论在哪种情况下,都只执行一组语句:退出状态码为0,执行第一组语句;否则,执行第二组语句。
exit命令
Shell内建的exit命令可以立即终止Shell程序的执行。其一般格式为:
exit n
其中,n是你想要返回的退出状态码。如果没有指定,则使用在exit之前最后执行的那条命令的退出状态(其实就是exit $?)。
elif
命令格式:
if command1thencommandcommand...elif command2thencommandcommand...elsecommandcommand...fi
command1, command2, ..., commandn会依次执行并测试其退出状态。只要返回的退出状态为真(0),就会执行then之后的命令,直到碰上其他elif、else或fi。如果所有的条件表达式都不为真,则执行可选的else之后的命令。
case命令
case命令可以将单个值与一组值或表达式进行比较,在有匹配的时候执行一个或多个命令。其一般格式如下:
case value inpattern1)commandcommand...command;;pattern2)commandcommand...command;;...patternn)commandcommand...command;;esac
value会连续地和pattern1、pattern2...patternn比较,直到找到匹配项。接着,执行所匹配值之后的命令,碰到双分号后停止,双分号在这里起到一个break语句的作用,表明已经完成了特定条件下指定的语句。在这之后,case语句就结束了。如果没有发现匹配项,case中的命令一个都不执行。
## 将数字转换成对应的英语#if [ "$#" -ne 1 ]thenecho "Usage: number digit"exit 1ficase "$1"in0) echo zero;;1) echo one;;2) echo two;;3) echo three;;4) echo four;;5) echo five;;6) echo six;;7) echo seven;;8) echo eight;;9) echo nine;;esac
当符号|用于两个模式之间时,其效果等同于逻辑’或’。也就是说,模式:
pat1 | pat2
表明匹配pat1或pat2。例如:
-l | -list
能够匹配-l或-list
空命令:
每个匹配的case语句分支都需要对应的命令,每个if-then条件也是一样,但有时候你并不打算执行什么命令,只想把结果丢掉。那该怎么做?可以利用 Shell 内建的空命令来实现。该命令的格式非常简单:
:
在大多数情况下,它用来满足必须有命令存在的要求,尤其是在if语句中。
if grep "^$system" /users/steve/mail/systems > /dev/nullthen:elseecho "$system is not a valid system"exit 1fi
&&和||
Shell有两个特殊的操作符,可以根据之前命令成功与否来执行后续的命令。如果你觉得这听起来似乎有些像if命令,从某种程度上来说,的确是的。它是if命令的一种简写形式。
command1 && command2
command1会在Shell需要命令的地方被执行,如果该命令返回的退出状态码为0(成功),则接着执行command2。如果command1返回的退出态码不为0(失败),command2会被忽略。
command1 || command2
command1会在Shell需要命令的地方被执行,如果该命令返回的退出状态码为非0(失败),则接着执行command2。如果command1返回的退出态码为0(成功),command2会被忽略。
sort bigdata > /tmp/sortout && mv /tmp/sortout bigdata
mv 命令仅会在 sort 命令执行成功的情况下被执行。
操作符||的工作方式类似,除了第二个命令仅在第一个命令的退出状态码非0的时候才执行。
grep "$name" phonebook || echo "Couldn't find $name"
那么仅当grep失败的时候(也就是说,无法在phonebook中找到$name,或打不开文件phonebook)才会执行echo命令。
