在 bash 中,条件表达式(Conditional expressions)用于进行一些判断。
例如判断文件是否存在、字符串是否相等、比较数值大小,等等。
查看 man bash 的 CONDITIONAL EXPRESSIONS 小节,对条件表达式说明如下:
Conditional expressions are used by the [[ compound command and the test and [ builtin commands to test file attributes and perform string and arithmetic comparisons.
Expressions are formed from the following unary or binary primaries.
When used with [[, the < and > operators sort lexicographically using the current locale.
The test command sorts using ASCII ordering.
即,条件表达式被 [[ 复合命令、test 内置命令、和 [ 内置命令用来判断文件属性、进行字符串比较、进行算术比较。
在 CONDITIONAL EXPRESSIONS 小节中,列举了很多条件表达式,对常用的条件表达式说明如下。
判断文件属性的条件表达式
用于判断文件属性的条件表达式如下表所示。
条件表达式
含义
-a file
如果 file 文件存在则返回 true,否则返回 false
-d file
如果 file 文件存在、且是一个目录则返回 true,否则返回 false
-e file
跟 -a file 含义相同,如果 file 文件存在则返回 true
-f file
如果 file 文件存在、且是文本文件则返回 true,否则返回 false
-s file
如果 file 文件存在、且文件大小大于0则返回 true,否则返回 false
-N file
如果 file 文件存在、且在读过之后被修改则返回 true,否则返回 false
file1 -nt file2
如果 file1 的修改时间比 file2 新、或者 file1 存在但 file2 不存在则返回 true
file1 -ot file2
如果 file1 的修改时间比 file2 早、或者 file2 存在但 file1 不存在则返回 true
如果所给的文件名本身带有空格,一定要用引号括起来,否则会出现不预期的结果。
注意:在 bash,true 对应的值是 0,false 对应的值是 1.
对这些条件表达式举例说明如下:
$ test -a testfile; echo $?
0
$ test -d testfile; echo $?
1
$ test -e testfile; echo $?
0
$ test -f testfile; echo $?
0
$ test -s testfile; echo $?
0
$ test -N testfile; echo $?
0
$ test testfile -nt retestfile; echo $?
0
$ test testfile -ot retestfile; echo $?
1
这里用 test 命令进行测试,这个命令会返回条件表达式的返回值,然后用 echo $? 打印上一个命令返回值,就能看到返回结果。
可以看到,test -a testfile 命令返回 0,也就是 true,testfile 文件存在。
test -d testfile 命令返回 false,testfile 文件不是一个目录。
test -e testfile 命令也是判断文件是否存在,返回 true。
test -f testfile 命令返回 true,testfile 文件是一个文本文件。
test -s testfile 命令返回 true,testfile 文件大小大于 0,也就是不为空。
可以使用 -s file 条件表达式来判断文件内容是否为空。
test -N testfile 命令返回 true,说明上次读过 testfile 文件后,这个文件被再次修改。
可以使用 -N file 条件表达式来判断文件是否发生改变。
例如实现一个版本管理系统,要查看仓库下的文件是否发生改变,就可以这样简单判断。
test testfile -nt retestfile 命令返回 true,testfile 文件的修改时间新于 retestfile 文件。
可以使用 file1 -nt file2 来判断不同目录下的文件是否发生更新。
例如实际工作中,项目代码由多人维护开发,我们可能只修改其中几个文件,并在本地备份这几个文件,就可以用这个条件表达式来判断项目代码文件是否新于本地备份文件。
如果是,就进行备份。如果在项目代码新增了文件,也可以判断到本地还没有备份过这个文件。
test testfile -ot retestfile 命令返回 false,testfile 文件的修改时间早于 retestfile 文件。
可以使用 file1 -ot file2 来判断不同目录下的文件是否发生更新。
具体使用场景跟 file1 -nt file2 类似。
判断字符串的条件表达式
用于判断字符串的条件表达式如下表所示。
条件表达式
含义
-z string
如果 string 字符串长度为 0,返回 true,否则返回 false
-n string
如果 string 字符串长度不为 0,返回 true,否则返回 false
string
跟 -n string 含义相似,如果 string 字符串长度不为 0,返回 true
string1 = string2
如果所给的两个字符串相等,返回 true,否则返回 false
string1 == string2
如果所给的两个字符串相等,返回 true,否则返回 false
string1 != string2
如果所给的两个字符串不相等,返回 true,否则返回 false
string1 < string2
如果 string1 字符串在词典上的顺序早于 string2 字符串,返回 true,否则返回 false
string1 > string2
如果 string1 字符串在词典上的顺序晚于 string2 字符串,返回 true,否则返回 false
可以看到,判断字符串的条件表达式不支持 >=、<= 操作符。
在实际书写后面五个比较字符串的条件表达式时,有下面一些需要注意的地方。
在 bash 中,< 和 > 字符是重定向操作符。
所以 string1 < string2、string1 > string2 这两个条件表达式在书写的时候,需要用 \ 转义字符、或者引号来去掉 < 和 > 的特殊含义,否则会执行报错。
具体举例如下:
$ test a < b
-bash: b: No such file or directory
$ test a \< b; echo $?
0
$ test a '
0
$ test a "
0
可以看到,test a < b 命令执行报错,提示找不到 b 这个文件。
这里的 < 是重定向标准输入操作符,并没有传递该字符给 test 命令来作为它的参数。
test a \< b 命令使用 \< 来进行转义,从而把 < 这个字符自身传递给 test 命令,没有执行报错。
test a '
另外,在 =、==、!=、 操作符的左右两边,必须用空格隔开,不能写为 string=string2、string1
string1=string2 这个形式其实是一个名为 "string1=string2" 的参数,而不是对应 "string1"、"="、"string2" 三个参数。
而 test 命令、[[ 命令的参数个数不同会导致不同的判断结果。
具体举例如下:
$ test a = b; echo $?
1
$ test a=b; echo $?
0
$ test a!=b; echo $?
0
可以看到,test a = b 命令返回 1,所比较的字符串不相等,这是正确的。
而 test a=b 命令和 test a!=b 命令都是返回 0。
看起来像是认为字符串 a 即等于字符串 b,也不等于字符串 b,这是错误的。
其实这两个命令都只传递了一个参数给 test 命令。
而 test 命令在只有一个参数时,只要这个参数不是空字符串,就会返回 true,也就是 0。
可见,在条件表达式的操作符前后不加空格,会导致不预期的判断结果。
注意:在 test 命令中,要使用 string1 = string2 条件表达式,以符合 POSIX 一致性,不建议使用 string1 == string2 条件表达式。
在 help test 的帮助说明中,没有列出 string1 == string2 这个条件表达式。
但实际测试,test 命令还是支持 string1 == string2 条件表达式,只是不建议使用。
在 [[ 命令中,没有说明不建议使用 string1 == string2 条件表达式。
string1 == string2 和 string1 = string2 都可以使用,且这两个表达式的含义完全相同。
在 [[ 命令中使用 ==、=、或者 != 操作符时,操作符右边的字符串可以使用通配符来匹配特定模式。
此时,模式字符串不能用引号括起来。
具体支持的通配符可以查看 man bash 的 Pattern Matching 小节,常用通配符说明如下:
星号 * 可以匹配任意字符串,包括空字符串。
问号 ? 匹配任意一个字符。
方括号 [...] 匹配方括号内的任意一个字符。如果在左大括号 [ 之后的第一个是 ^,也就是写为 [^...] 的形式,表示匹配除了方括号内字符之外的任意一个字符。[^...] 也可以写为 [!...] 的形式。方括号内支持字符类表达式和范围表达式。例如,[[:alpha]] 匹配任意一个字母。[A-Z] 匹配任意一个大写字母。
注意:这里使用的是 bash 通配符进行匹配,不是使用正则表达式。
注意这两者的区别。
例如,在通配符中,a* 匹配以字符 ‘a’ 开头的任意字符串、包括空字符串。
而在正则表达式中,a* 匹配零个或连续多个字符 ‘a’。
这两者的含义完全不同。
test 命令和 [ 命令都不支持使用通配符来匹配特定模式。具体举例说明如下:
$ test abc = ab?; echo $?
1
$ test abc == ab?; echo $?
1
$ [[ abc == ab? ]]; echo $?
0
$ [[ abc == ab* ]]; echo $?
0
$ [[ abc == ab[a-z] ]]; echo $?
0
$ [[ "abc" == "ab?" ]]; echo $?
1
可以看到,test abc = ab? 命令返回 1,也就是 false,所比较的两个字符串不相等。
这里的 ? 并没有被当成通配符处理,而是对应字符 ‘?’ 自身。
虽然 test 命令不建议使用 == 操作符,但是 test abc == ab? 命令还是可以正常执行,也是返回 false。
[[ abc == ab? ]] 命令在 == 操作符右边的字符串使用 ? 通配符来匹配任意一个字符。
该命令返回 0,也就是 true,所比较的两个字符串可以匹配。[[ abc == ab* ]] 命令使用 * 通配符来匹配任意字符串,也是匹配。
[[ abc == ab[a-z] ]] 命令使用 [a-z] 来匹配任意一个小写字母,可以匹配到左边的字符 ‘c’。
如果模式字符串用引号括起来,引号内的字符匹配自身,没有特殊含义。
所以 [[ "abc" == "ab?" ]] 命令返回为 1。
这里的 ? 被引号括起来,匹配字符 ‘?’ 自身。
判断整数的条件表达式
下面条件表达式的参数要求是整数、或者算术表达式,算术表达式也是返回整数值。
条件表达式
含义
arg1 -eq arg2
如果 arg1 等于 arg2,返回 true,否则返回 false
arg1 -ne arg2
如果 arg1 不等于 arg2,返回 true,否则返回 false
arg1 -lt arg2
如果 arg1 小于 arg2,返回 true,否则返回 false
arg1 -le arg2
如果 arg1 小于或等于 arg2,返回 true,否则返回 false
arg1 -gt arg2
如果 arg1 大于 arg2,返回 true,否则返回 false
arg1 -ge arg2
如果 arg1 大于或等于 arg2,返回 true,否则返回 false
在 test 命令和 [[ 命令中使用算术表达式时,算术表达式的写法有所不同。
后面会具体说明。
下面先用 test 命令举例如下:
$ test a -eq b
-bash: test: a: integer expression expected
$ test 1 -ne 2; echo $?
0
$ test $((4-2)) -eq 2; echo $?
0
可以看到,test a -eq b 命令执行报错,提示 -eq 操作预期要提供整数表达式。
test 1 -ne 2 命令返回 0,所给的两个整数不相等。
test $((4-2)) -eq 2 命令使用 $((4-2)) 来获取 4-2 这个算术运算的结果,然后进行比较,返回 0。所比较的两个整数值相等。
在进行整数比较时,这里使用使用 -lt 来表示小于。用 -gt 来表示大于。这是标准用法。
虽然也可以用 来比较整数,但其实这两个操作符是把整数当成字符串来比较。
Bash 的数据类型是弱类型,所以 1 既可以是整数,也可以是一个字符串,要看上下文环境。
具体举例说明如下:
$ test 1 '>' 2; echo $?
1
$ test 1 '>=' 2
-bash: test: >=: binary operator expected
可以看到,test 1 '>' 2 命令返回 1,判断结果正确。
这其实是一个判断字符串的条件表达式。
而 test 1 '>=' 2 命令则执行报错,因为判断字符串的条件表达式不支持 >= 这个操作符。
同样也不支持 <= 操作符。
刚接触 test 命令的常见误区就是认为条件表达式可以使用 >=、<= 操作符。
注意:在 test 命令和 [ 命令中使用 操作符,会用 ASCII 编码值来比较字符串。
在 ASCII 编码中,数字 1 到 9 的编码值是递增的。
基于这个词典顺序可以用 操作符来比较数字字符串的大小关系。
而在 [[ 命令中使用 操作符,会用当前语言环境编码集的编码值来比较字符串。例如 UTF8、GBK 等等。
如果在某个编码集中,数字 1 到 9 的编码值不是递增顺序,使用 操作符来比较数字字符串的大小关系,会得到错误的结果。
例如,当数字 1 的编码值大于数字 2 的编码值时,[[ 1 > 2 ]] 命令会返回 true。
建议不要用 操作符来比较数字。
条件表达式不支持与或非操作符
在 CONDITIONAL EXPRESSIONS 小节描述的条件表达式中,没有描述关于逻辑与、逻辑或、逻辑非的表达式。
即,条件表达式自身不支持与、或、非操作符。
与、或、非操作符是由评估条件表达式的命令自身所支持,而且具体支持的操作符有所不同。
例如,test 命令使用 -o 作为或操作符。
而 [[ 命令使用 && 作为或操作符。