条件语句格式
if 命令的一般格式为:
if commandt
then
command
command
...
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 backup
cp: cannot access nosuch
$ echo $?
2 复制命令执行失败
注意,对于一些命令来说,其表示
执行失败
的数值在不同的UNIX版本中并不相同,但表示执行成功的退出状态码总是0。
#
# 确定用户是否已经登录
#
user="$1"
if who | grep "$user"
then
echo "$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" = julio
then
echo "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 = julio
sh: 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 ]
then
echo "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 commandt
then
command
command
. . .
else
command
command
. . .
fi
其中,commandt
会被执行并对其退出状态求值。如果为真(0),执行then
代码块(then 和 else 之间的所有语句)并忽略else
代码块。如果为假(非 0),则忽略then
代码块,执行else
代码块(else 与 fi 之间的所有语句)。无论在哪种情况下,都只执行一组语句:退出状态码为0,执行第一组语句;否则,执行第二组语句。
exit
命令
Shell
内建的exit
命令可以立即终止Shell
程序的执行。其一般格式为:
exit n
其中,n
是你想要返回的退出状态码。如果没有指定,则使用在exit
之前最后执行的那条命令的退出状态(其实就是exit $?
)。
elif
命令格式:
if command1
then
command
command
...
elif command2
then
command
command
...
else
command
command
...
fi
command1, command2, ..., commandn
会依次执行并测试其退出状态。只要返回的退出状态为真(0),就会执行then
之后的命令,直到碰上其他elif
、else
或fi
。如果所有的条件表达式都不为真,则执行可选的else
之后的命令。
case
命令
case
命令可以将单个值与一组值或表达式进行比较,在有匹配的时候执行一个或多个命令。其一般格式如下:
case value in
pattern1)
command
command
...
command;;
pattern2)
command
command
...
command;;
...
patternn)
command
command
...
command;;
esac
value
会连续地和pattern1、pattern2...patternn
比较,直到找到匹配项。接着,执行所匹配值之后的命令,碰到双分号后停止,双分号在这里起到一个break
语句的作用,表明已经完成了特定条件下指定的语句。在这之后,case
语句就结束了。如果没有发现匹配项,case
中的命令一个都不执行。
#
# 将数字转换成对应的英语
#
if [ "$#" -ne 1 ]
then
echo "Usage: number digit"
exit 1
fi
case "$1"
in
0) 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/null
then
:
else
echo "$system is not a valid system"
exit 1
fi
&&
和||
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
命令。