shell 脚本中,if 判断的用法
本文抄自:阮一峰 - Bash 脚本教程
背景说明
在 Shell 脚本中,我们有时需要根据命令的成功或失败,来指定要采取的不同分支代码。和其它高级语言类似,Shell 中也有 if 语句可以让我们指定此类条件。
语法结构
if
是最常用的条件判断结构,只有符合给定条件时,才会执行指定的命令。它的语法如下。
1 | if commands; then |
这个命令分成三个部分:if
、elif
和else
。其中,后两个部分是可选的。
if
关键字后面是主要的判断条件,elif
用来添加在主条件不成立时的其他判断条件,else
则是所有条件都不成立时要执行的部分。
1 | if test $USER = "foo"; then |
上面的例子中,判断条件是环境变量$USER
是否等于foo
,如果等于就输出Hello foo.
,否则输出其他内容。
if
和then
写在同一行时,需要分号分隔。分号是 Bash 的命令分隔符。它们也可以写成两行,这时不需要分号。
1 | if true |
上面的例子中,true
和false
是两个特殊命令,前者代表操作成功,后者代表操作失败。if true
意味着命令部分总是会执行,if false
意味着命令部分永远不会执行。
除了多行的写法,if
结构也可以写成单行。
1 | $ if true; then echo 'hello world'; fi |
注意: if
关键字后面也可以是一条命令,该条命令执行成功(返回值0),就意味着判断条件成立。
1 | $ if echo 'hi'; then echo 'hello world'; fi |
上面命令中,if
后面是一条命令echo 'hi'
。该命令会执行,如果返回值是0,则执行then
的部分。
if
后面可以跟任意数量的命令。这时,所有命令都会执行,但是判断真伪只看最后一个命令,即使前面所有命令都失败,只要最后一个命令返回0,就会执行then的部分。
1 | $ if false; true; then echo 'hello world'; fi |
上面例子中,if
后面有两条命令(false;true;)
,第二条命令(true)
决定了then
的部分是否会执行。
elif
部分可以有多个。
1 |
|
上面例子中,如果用户输入3
,就会连续判断3次。
test命令
if
结构的判断条件,一般使用test
命令,有三种形式。
1 | # 写法一 |
上面三种形式是等价的,但是第三种形式还支持正则判断,前两种不支持。
上面的expression
是一个表达式。这个表达式为真,test
命令执行成功(返回值为0)
;表达式为伪,test
命令执行失败(返回值为1)
。注意,第二种和第三种写法,[
和]
与内部的表达式之间必须有空格。
1 | $ test -f /etc/hosts |
上面的例子中,test
命令采用两种写法,判断/etc/hosts
文件是否存在,这两种写法是等价的。命令执行后,返回值为0
,表示该文件确实存在。
实际上,[
这个字符是test
命令的一种简写形式,可以看作是一个独立的命令,这解释了为什么它后面必须有空格。
下面把test
命令的三种形式,用在if
结构中,判断一个文件是否存在。
1 | # 写法一 |
判断表达式
if
关键字后面,跟的是一个命令。这个命令可以是test
命令,也可以是其他命令。命令的返回值为0
表示判断成立,否则表示不成立。因为这些命令主要是为了得到返回值,所以可以视为表达式。
常用的判断表达式有下面这些。
文件判断
以下表达式用来判断文件状态。
[-a file ]
:如果 file 存在,则为true
。[-b file ]
:如果 file 存在并且是一个块(设备)文件,则为true
。[-c file ]
:如果 file 存在并且是一个字符(设备)文件,则为true
。[-d file ]
:如果 file 存在并且是一个目录,则为true
。[-e file ]
:如果 file 存在,则为true
。[-f file ]
:如果 file 存在并且是一个普通文件,则为true
。[-g file ]
:如果 file 存在并且设置了组 ID,则为true
。[-G file ]
:如果 file 存在并且属于有效的组 ID,则为true
。[-h file ]
:如果 file 存在并且是符号链接,则为true
。[-k file ]
:如果 file 存在并且设置了它的“sticky bit”,则为true
。[-L file ]
:如果 file 存在并且是一个符号链接,则为true
。[-N file ]
:如果 file 存在并且自上次读取后已被修改,则为true
。[-O file ]
:如果 file 存在并且属于有效的用户 ID,则为true
。[-p file ]
:如果 file 存在并且是一个命名管道,则为true
。[-r file ]
:如果 file 存在并且可读(当前用户有可读权限),则为true
。[-s file ]
:如果 file 存在且其长度大于零,则为true
。[-S file ]
:如果 file 存在且是一个网络 socket,则为true
。[-t fd ]
:如果 fd 是一个文件描述符,并且重定向到终端,则为true
。 这可以用来判断是否重定向了标准输入/输出错误。[-u file ]
:如果 file 存在并且设置了 setuid 位,则为true
。[-w file ]
:如果 file 存在并且可写(当前用户拥有可写权限),则为true
。[-x file ]
:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true
。[file1 -nt file2 ]
:如果 FILE1 比 FILE2 的更新时间最近,或者 FILE1 存在而 FILE2 不存在,则为true
。[file1 -ot file2 ]
:如果 FILE1 比 FILE2 的更新时间更旧,或者 FILE2 存在而 FILE1 不存在,则为true
。[FILE1 -ef FILE2 ]
:如果 FILE1 和 FILE2 引用相同的设备和 inode 编号,则为true
。
下面是一个示例:
1 |
|
上面代码中,$FILE
要放在双引号之中。这样可以防止$FILE
为空,因为这时[ -e ]
会判断为真。而放在双引号之中,返回的就总是一个空字符串,[ -e "" ]
会判断为伪。
字符串判断
以下表达式用来判断字符串。
[string ]
:如果string
不为空(长度大于0),则判断为真。[-n string ]
:如果字符串string
的长度大于零,则判断为真。[-z string ]
:如果字符串string
的长度为零,则判断为真。[string1 = string2 ]
:如果string1
和string2
相同,则判断为真。[string1 == string2 ]
等同于[ string1 = string2 ]
。[string1 != string2 ]
:如果string1
和string2
不相同,则判断为真。[string1 '>' string2 ]
:如果按照字典顺序string1
排列在string2
之后,则判断为真。[string1 '<' string2 ]
:如果按照字典顺序string1
排列在string2
之前,则判断为真。
注意: test
命令内部的>
和<
,必须用引号引起来(或者是用反斜杠转义)。否则,它们会被 shell 解释为重定向操作符。
下面是一个示例:
1 |
|
上面代码中,首先确定$ANSWER
字符串是否为空。如果为空,就终止脚本,并把退出状态设为1
。注意,这里的echo
命令把错误信息There is no answer
.重定向到标准错误,这是处理错误信息的常用方法。如果$ANSWER
字符串不为空,就判断它的值是否等于yes
、no
或者maybe
。
注意: 字符串判断时,变量要放在双引号之中,比如[ -n "$COUNT" ]
,否则变量替换成字符串以后,test
命令可能会报错,提示参数过多。另外,如果不放在双引号之中,变量为空时,命令会变成[ -n ]
,这时会判断为真。如果放在双引号之中,[ -n "" ]
就判断为伪。
整数判断
下面的表达式用于判断整数。
[integer1 -eq integer2 ]
:如果integer1
等于integer2
,则为true
。[integer1 -ne integer2 ]
:如果integer1
不等于integer2
,则为true
。[integer1 -le integer2 ]
:如果integer1
小于或等于integer2
,则为true
。[integer1 -lt integer2 ]
:如果integer1
小于integer2
,则为true
。[integer1 -ge integer2 ]
:如果integer1
大于或等于integer2
,则为true
。[integer1 -gt integer2 ]
:如果integer1
大于integer2
,则为true
。
下面是一个用法的例子:
1 |
|
上面例子中,先判断变量$INT
是否为空,然后判断是否为0
,接着判断正负,最后通过求余数判断奇偶。
正则判读
[[
expression ]]
这种判断形式,支持正则表达式。
1 | [[ string1 =~ regex ]] |
上面的语法中,regex
是一个正则表示式,=~
是正则比较运算符。
下面是一个例子:
1 |
|
上面代码中,先判断变量INT
的字符串形式,是否满足^-?[0-9]+$
的正则模式,如果满足就表明它是一个整数。
test
判断的逻辑判断
通过逻辑运算,可以把多个test
判断表达式结合起来,创造更复杂的判断。三种逻辑运算AND
,OR
,和NOT
,都有自己的专用符号。
AND
运算:符号&&
,也可使用参数-a
。OR
运算:符号||
,也可使用参数-o
。NOT
运算:符号!
。
下面是一个AND
的例子,判断整数是否在某个范围之内:
1 |
|
上面例子中,&&
用来连接两个判断条件:大于等于$MIN_VAL
,并且小于等于$MAX_VAL
。
使用否定操作符!
时,最好用圆括号确定转义的范围:
1 | if [ ! \( $INT -ge $MIN_VAL -a $INT -le $MAX_VAL \) ]; then |
上面例子中,test
命令内部使用的圆括号,必须使用引号或者转义,否则会被 Bash 解释。
算术判断
Bash 还提供了((...))
作为算术条件,进行算术运算的判断。
1 | if ((3 > 2)); then |
上面代码执行后,会打印出true
。
注意: 算术判断不需要使用test
命令,而是直接使用((...))
结构。这个结构的返回值,决定了判断的真伪。
如果算术计算的结果是非零值,则表示判断成立。这一点跟命令的返回值正好相反,需要小心。
1 | $ if ((1)); then echo "It is true."; fi |
上面例子中,((1))
表示判断成立,((0))
表示判断不成立。
算术条件((...))
也可以用于变量赋值。
1 | $ if (( foo = 5 ));then echo "foo is $foo"; fi |
上面例子中,(( foo = 5 ))
完成了两件事情。首先把5赋值给变量foo
,然后根据返回值5
,判断条件为真。
注意: 赋值语句返回等号右边的值,如果返回的是0
,则判断为假。
1 | $ if (( foo = 0 ));then echo "It is true.";else echo "It is false."; fi |
下面是用算术条件改写的数值判断脚本:
1 |
|
只要是算术表达式,都能用于((...))
语法,详见《Bash 的算术运算》一章。
普通命令的逻辑运算
如果if
结构使用的不是test
命令,而是普通命令,比如上一节的((...))
算术运算,或者test
命令与普通命令混用,那么可以使用 Bash 的命令控制操作符&&
(AND)和||
(OR),进行多个命令的逻辑运算。
1 | $ command1 && command2 |
对于&&
操作符,先执行command1
,只有command1
执行成功后, 才会执行command2
。对于||
操作符,先执行command1
,只有command1
执行失败后, 才会执行command2
。
1 | $ mkdir temp && cd temp |
上面的命令会创建一个名为temp
的目录,执行成功后,才会执行第二个命令,进入这个目录。
1 | $ [ -d temp ] || mkdir temp |
上面的命令中,如果temp
子目录不存在,脚本会终止,并且返回值为1
。
下面就是if
与&&
结合使用的写法。
1 | if [ condition ] && [ condition ]; then |
下面是一个示例:
1 |
|
上面的例子只有在指定文件里面,同时存在搜索词word1
和word2
,就会执行if
的命令部分。
下面的示例演示如何将一个&&
判断表达式,改写成对应的if
结构。
1 | [[ -d "$dir_name" ]] && cd "$dir_name" && rm * |