6. Shell 脚本的条件测试
6.1 Shell 脚本的条件测试
6.1.1 条件测试方法综述
通常,在 bash 的各种条件结构和流程控制结构中都要进行各种测试,然后根据测试结果执行不同的操作,有时也会与if等条件语句相结合,来完成测试判断,以减少程序运行的错误。
执行条件判断表达式后通常会返回"真"或"假",就像执行命令后的返回值为0,表示真;非0,表示假。
在 bash 编程里,条件测试常用的语法如下:
- 语法1:test <判断表达式>,test命令和"<判断表达式>"之间至少有一个空格。
- 语法2:[ <判断表达式> ] ,和test命令的用法相同,这是推荐方法。
[]的边界和内容之间至少有一个空格。 - 语法3:[[ <判断表达式> ]] ,是比test和[]更新的语法格式。
[[]]的边界和内容之间至少有一个空格。 - 语法4:(( <判断表达式> )) ,一般用于 if 语句。
(())(双小括号)两端不需要有空格。 - 语法5:comand,命令的返回值确定表达式的真值或假值。
有几个注意事项需要说明一下:
- 语法1 中的
test命令和语法2 中的[]是等价的,语法3中的[[]]为扩展的test命令,语法4 中的(())常用于计算。建议使用相对友好的语法2 ,即中括号[]的语法格式。 - 在
[[ ]](双中括号)中可以使用通配符等进行模式匹配,这是其区别于其他几种语法格式的地方。 &&、||、>、<等操作符可以应用于[[]]中,但不能应用于[]中,在[]中一般用-a、-o、-gt(用于整数)、-lt(用于整数)代替上述操作符。- 对于整数的关系运算,也可以使用 Shell 的算术运算符
(())。
6.1.2 test 条件测试的简单语法及示例
test条件测试的语法格式为: test <判断表达式>
bash
[shizhan@shell ~]$ help test
test: test [expr]
Evaluate conditional expression.
Exits with a status of 0 (true) or 1 (false) depending on
the evaluation of EXPR. Expressions may be unary or binary. Unary
expressions are often used to examine the status of a file. There
are string operators and numeric comparison operators as well.
The behavior of test depends on the number of arguments. Read the
bash manual page for the complete specification.
File operators:
-a FILE True if file exists.
-b FILE True if file is block special.
-c FILE True if file is character special.
-d FILE True if file is a directory.
-e FILE True if file exists.
-f FILE True if file exists and is a regular file.
-g FILE True if file is set-group-id.
-h FILE True if file is a symbolic link.
-L FILE True if file is a symbolic link.
-k FILE True if file has its `sticky' bit set.
-p FILE True if file is a named pipe.
-r FILE True if file is readable by you.
-s FILE True if file exists and is not empty.
-S FILE True if file is a socket.
-t FD True if FD is opened on a terminal.
-u FILE True if the file is set-user-id.
-w FILE True if the file is writable by you.
-x FILE True if the file is executable by you.
-O FILE True if the file is effectively owned by you.
-G FILE True if the file is effectively owned by your group.
-N FILE True if the file has been modified since it was last read.
FILE1 -nt FILE2 True if file1 is newer than file2 (according to
modification date).
FILE1 -ot FILE2 True if file1 is older than file2.
FILE1 -ef FILE2 True if file1 is a hard link to file2.
String operators:
-z STRING True if string is empty.
-n STRING True if string is not empty.
STRING1 = STRING2
True if the strings are equal.
STRING1 != STRING2
True if the strings are not equal.
STRING1 < STRING2
True if STRING1 sorts before STRING2 lexicographically.
STRING1 > STRING2
True if STRING1 sorts after STRING2 lexicographically.
Other operators:
-o OPTION True if the shell option OPTION is enabled.
-v VAR True if the shell variable VAR is set
! EXPR True if expr is false.
EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.
EXPR1 -o EXPR2 True if either expr1 OR expr2 is true.
arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,
-lt, -le, -gt, or -ge.
Arithmetic binary operators return true if ARG1 is equal, not-equal,
less-than, less-than-or-equal, greater-than, or greater-than-or-equal
than ARG2.
Exit Status:
Returns success if EXPR evaluates to true; fails if EXPR evaluates to
false or an invalid argument is given.
示例1:判断文件是否存在
bash
[shizhan@shell ~]$ test -f file && echo true || echo false
该语句表示如果file文件存在,则输出true,否则输出false。这里的&&是逻辑与,||是逻辑或。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&后面的命令,而||后面的命令是test命令执行失败之后(为假)所执行的命令。
test命令判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个&&或||)来测试,示例如下。
bash
[shizhan@shell ~]$ test -f file && echo true
[shizhan@shell ~]$ test -f file || echo false
另外,逻辑操作符&&和||的两端既可以有空格,也可以无空格。带空格看起来会更美观一些,建议使用带空格的格式。
示例2:判断目录是否存在
bash
# 如果目录不存在则创建
[root@shell ~ 16:47:58]# test ! -d /tmp/webapp/ && mkdir /tmp/webapp
[root@shell ~ 16:48:28]# ls -d /tmp/webapp/
/tmp/webapp/
[root@shell ~ 16:48:37]# test -d /tmp/webapp/ && ls -d /tmp/webapp
/tmp/webapp
# 如果目录存在,删除该目录
[root@shell ~ 16:49:05]# test -d /tmp/webapp/ && rm -rf /tmp/webapp/
[root@shell ~ 16:49:34]# ls -d /tmp/webapp
ls: cannot access /tmp/webapp: No such file or directory
test -d /tmp/webapp && echo /tmp/webapp is a directory. || echo /tmp/webapp is not a directory.
expr && command1 || command2
如果expr为真值,则执行command1,command2不执行
如果expr为假值,则执行command2,command1不执行
示例3: -z 选项判断变量值是否为空,如果为空,则为真值,反之为假值。
bash
[shizhan@shell ~]$ unset string
[shizhan@shell ~]$ test -z "$string" && echo null string || echo $string
null string
[shizhan@shell ~]$ string="hello world"
[shizhan@shell ~]$ test -z "$string" && echo null string || echo $string
hello world
结论:test命令测试的功能很强大,但是和[]、[[]]的功能有所重合,因此,在实际工作中选择一种适合自己的语法就好了。对于其他的语法,能读懂别人写的脚本就可以了。
示例4:判断用户是否是root,脚本只能以root身份执行
bash
[root@shell ~ 17:00:42]# vim run_as_root.sh
#!/bin/bash
test "$(id -un)" != "root" && echo "please run as root" && exit
echo I am root.
# 以root身份测试
[root@shell ~ 17:04:38]# chmod +x run_as_root.sh
[root@shell ~ 17:05:00]# cp run_as_root.sh /usr/local/bin/
[root@shell ~ 17:05:10]# bash run_as_root.sh
I am root.
# 以普通用户身份测试
[shizhan@shell ~ 17:05:28]$ bash run_as_root.sh
please run as root
6.1.3 [](中括号)条件测试语法及示例
[]条件测试的语法格式为:[ < 判断表达式> ]
注意:中括号内部的两端要有空格,[]和test等价,即test的所有判断选项都可以直接在[]里使用。
**示例1:**判断文件是否存在
bash
[shizhan@shell ~]$ [ -f file ] && echo true || echo false
该语句表示如果file文件存在,则输出true,否则输出false。这里的&&是逻辑与,||是逻辑或。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&后面的命令,而||后面的命令是test命令执行失败之后(为假)所执行的命令。
\]条件判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个\&\&或\|\|)来测试,示例如下。 ```bash [shizhan@shell ~]$ [ -f file ] && echo true [shizhan@shell ~]$ [ -f file ] || echo false ``` > 逻辑操作符 `&&` 和 `||` 的两端既可以有空格,也可以无空格。带空格看起来会更美观一些,建议使用带空格的格式。 #### 6.1.4 \[\[\]\](双中括号)条件测试语法及示例 `[[]]`条件测试的语法格式为: `[[ < 判断表达式> ]]`。 注意:双中括号里的两端也要有空格。 **示例1**:判断文件是否存在 ```bash [shizhan@shell ~]$ [[ -f file ]] && echo true || echo false ``` 该语句表示如果file文件存在,则输出true,否则输出false。这里的\&\&是逻辑与,\|\|是逻辑或。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行\&\&后面的命令,而\|\|后面的命令是test命令执行失败之后(为假)所执行的命令。 \[\]条件判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个\&\&或\|\|)来测试,示例如下。 ```bash [shizhan@shell ~]$ [[ -f file ]] && echo true [shizhan@shell ~]$ [[ -f file ]] || echo false ``` > 逻辑操作符 `&&` 和 `||` 的两端既可以有空格,也可以无空格。带空格看起来会更美观一些,建议使用带空格的格式。 ### 6.2 文件判断表达式 #### 6.2.1 文件判断表达式的用法 文件测试常用选项: * **-d 文件** ,d的全拼为**directory**文件存在且为目录则为真,即判断表达式成立。 * **-f 文件** ,f的全拼为**file**文件存在且为普通文件则为真,即判断表达式成立。 * **-e 文件** ,e的全拼为**exist**文件存在则为真,即判断表达式成立。区别于"-f",-e不辨别是目录还是文件。 * **-r 文件** ,r的全拼为**read**文件存在且可读则为真,即判断表达式成立。 * **-s 文件** ,s的全拼为size文件存在且文件**size不为0**则为真,即判断表达式成立。 * **-w 文件** ,w的全拼为**write**文件存在且可写则为真,即判断表达式成立。 * **-x 文件** ,x的全拼为**executable**文件存在且可执行则为真,即判断表达式成立。 * **-L 文件** ,L的全拼为**link**文件存在且为链接文件则为真,即判断表达式成立。 * **f1 -nt f2** ,nt的全拼为**newer than**,文件fl比文件f2新则为真,即判断表达式成立。根据文件的修改时间来计算。 * **f1 -ot f2**,ot的全拼为older than,文件fl比文件f2旧则为真,即判断表达式成立。根据文件的修改时间来计算。 #### 6.2.2 文件判断表达式举例 ```bash [shizhan@shell ~]$ data_path=/tmp/shizhan [shizhan@shell ~]$ mkdir ${data_path} [shizhan@shell ~]$ [ -d ${data_path} ] && echo ${data_path} does exist /tmp/shizhan does exist [shizhan@shell ~]$ rmdir ${data_path} [shizhan@shell ~]$ [ -d ${data_path} ] || echo ${data_path} does not exist /tmp/shizhan does not exist [shizhan@shell ~]$ [ -r /etc/shadow ] && cat /etc/shadow || echo hello hello [shizhan@shell ~]$ [ -w ~/.bashrc ] && echo "alias pa='ps axu'" >> ~/.bashrc [shizhan@shell ~]$ tail -1 ~/.bashrc alias pa='ps axu' ``` #### 6.2.3 特殊条件判断表达式案例 以下写法适用于所有的条件判断表达式,是工作中比较常用的替代if语句的方法。判断条件判断表达式的条件成立或不成立后,还需要继续**执行多条命令语句**的语法。 例如,当条件1成立时,同时执行命令1、命令2、命令3。 ```bash # 格式1 [ 条件1 ] && { 命令1 命令2 命令3 } # 格式2 [[ 条件1 ]] && { 命令1 命令2 命令3 } # 格式3 test 条件1 && { 命令1 命令2 命令3 } ``` **示例:** ```bash [shizhan@shell ~]$ [ -w ~/.bashrc ] && { echo "alias sa='ssh root@servera'" >> ~/.bashrc echo "alias sb='ssh root@serverb'" >> ~/.bashrc echo "alias sc='ssh root@serverc'" >> ~/.bashrc } [shizhan@shell ~]$ tail -3 ~/.bashrc alias sa='ssh root@servera' alias sb='ssh root@serverb' alias sc='ssh root@serverc' # 一行写法 [shizhan@shell ~]$ [ -r /etc/passwd ] && { echo ha; echo ha; echo ha; } ha ha ha ``` ### 6.3 字符串判断表达式 #### 6.3.1 字符串测试操作符 字符串测试操作符的作用包括:比较两个字符串是否相同、测试字符串的长度是否为零、字符串是否为NULL等。 常用字符串测试操作符: * **-n "字符串"**,若字符串的长度不为0,则为真,即判断表达式成立,n可以理解为no zero。 * **-z "字符串"**,若字符串的长度为0,则为真,即判断表达式成立,z可以理解为zero的缩写。 * **"串1" = "串2"**,若字符串1等于字符串2,则为真,即判断表达式成立,可使用"=="代替"="。 * **"串1" != "串2"**,若字符串1不等于字符串2,则为真,即判断表达式成立,但不能用"!=="代替"!="。 以下是针对字符串测试操作符的提示: * 对于字符串的测试,**一定要将字符串加双引号之后再进行比较** ,如 `[ -n "$myvar" ]`,特别是使用`[]` 的场景。 * 比较符号(例如=和!=)的两端一定要有空格。 **示例1:-z 选项**,判断变量值是否为空,如果为空,则为真值,反之为假值。 ```bash [shizhan@shell ~]$ unset string [shizhan@shell ~]$ [ -z "$string" ] && echo null string || echo $string null string [shizhan@shell ~]$ string="hello world" [shizhan@shell ~]$ [ -z "$string" ] && echo null string || echo $string hello world ``` **示例2:-n 选项**,判断变量值是否为非空,如果为非空,则为真值,反之为假值。 ```bash [shizhan@shell ~]$ unset string [shizhan@shell ~]$ [ -n "$string" ] && echo $string || echo null string null string [shizhan@shell ~]$ string="hello world" [shizhan@shell ~]$ [ -n "$string" ] && echo $string || echo null string hello world ``` **示例3**:判断是否相对 ```bash [shizhan@shell ~]$ [ "$string" = "hi" ] && echo hi hi [shizhan@shell ~]$ [ "$string" = "ha" ] && echo ha [shizhan@shell ~]$ [ "$string" != "hi" ] && echo no hi [shizhan@shell ~]$ [ "$string" != "ha" ] && echo ha ha ``` #### 6.3.2 字符串测试生产案例 **示例:** /etc/init.d/network 部分内容 ```bash [shizhan@shell ~]$ sed -n '30,31p' /etc/init.d/network # Check that networking is up. [ "${NETWORKING}" = "no" ] && exit 6 ``` #### 6.3.3 \[\[\]\] 中=\~操作符 **语法:** `[[ "$str" =~ pattern ]]` \*\*作用:\*\*使用 `=~` 操作符时,其右边的字符串被认为是一个扩展正则表达式。扩展之后跟左边字符串进行比较,看左边字符串是否包含右边模式,也就是判断右边的模式是否为左边字符串的子字符串,而不是判断右边的模式是否完全等于左边字符串。 **`=~` 操作符右边的字符串不能使用引号括起来**,无论是双引号、还是单引号,否则表示匹配这个字符串自身的内容,不再解析成正则表达式。 示例: ```bash # 判断变量值是否包涵数字 [shizhan@shell ~]$ str=123abc [shizhan@shell ~]$ [[ "$str" =~ [0-9]+ ]] && echo str is a string with num || echo str is a string without any num str is a string with num [shizhan@shell ~]$ str=abcdef [shizhan@shell ~]$ [[ "$str" =~ [0-9]+ ]] && echo str is a string with num || echo str is a string without any num str is a string without any num # 判断变量值是否只包涵数字 [shizhan@shell ~]$ str=123;[[ "$str" =~ ^[0-9]+$ ]] && echo str is a num || echo str is not a num str is a num # 加引号对比 [shizhan@shell ~]$ str=123;[[ "$str" =~ [0-9]+ ]] && echo true || echo false true [shizhan@shell ~]$ str=123;[[ "$str" =~ "[0-9]+" ]] && echo true||echo false false ``` ### 6.4 整数二元比较操作符 #### 6.4.1 整数二元比较操作符介绍 整数二元比较操作符使用说明如下: | \[\]和test中比较符号 | (())和\[\[\]\]中比较符号 | 说明 | |:--------------:|:------------------:|:----------------------:| | -eq | ==或= | 相等,全拼为 equal | | -ne | != | 不相等,全拼为 not equal | | -gt | \> | 大于,全拼为 greater than | | -ge | \>= | 大于等于,全拼为 greater equal | | -It | \< | 小于,全拼为less than | | -le | \<= | 小于等于,全拼为 less equal | 以下是针对上述符号的特别说明: * `=` 和 `!=` 也可在\[\]中做比较使用,但在\[\]中使用包含 `>` 和 `<` 的符号时,需要用反斜线转义,有时不转义虽然语法不会报错,但是结果可能会不对。 * 也可以在\[\[\]\]中使用包含`-gt` 和 `-lt` 的符号,但是不建议这样使用。 * 比较符号两端也要有空格。 **示例1:** ```bash [shizhan@shell ~]$ [ 2 -lt 3 ] && echo true || echo false true [shizhan@shell ~]$ [ 2 -gt 3 ] && echo true || echo false false [shizhan@shell ~]$ [ 2 -le 3 ] && echo true || echo false true [shizhan@shell ~]$ [ 4 -ge 3 ] && echo true || echo false true ``` **示例2:** 错误的示例 ```bash [shizhan@shell ~]$ [ 2 > 1 ] && echo true true # 事实是2<3 [shizhan@shell ~]$ [ 2 > 3 ] && echo true true # 转义>符号 [shizhan@shell ~]$ [ 2 \> 3 ] && echo true || echo false false ``` **\[\]、\[\[\]\]、(())用法的小结:** * 整数加双引号的比较是对的。 * `[[]]`中用类似-eq等的写法是对的,\[\[\]\]中用类似\>、\<的写法也可能不对,有可能会只比较第一位,逻辑结果不对。 * `[]`中用类似\>、\<的写法在语法上虽然可能没错,但逻辑结果不对,可以使用=、!=正确比较。 * `(())`中不能使用类似-eq等的写法,可以使用类似\>、\<的写法。 #### 6.4.2 整数变量测试实践示例 **示例1:** ```bash [shizhan@shell ~]$ a=98;b=99 [shizhan@shell ~]$ [ $a -eq $b ] && echo true || echo false false [shizhan@shell ~]$ [ $a -ne $b ] && echo true || echo false true [shizhan@shell ~]$ [ $a -gt $b ] && echo true || echo false false [shizhan@shell ~]$ [ $a -lt $b ] && echo true || echo false true ``` **示例2:** ```bash [shizhan@shell ~]$ a=98;b=99 [shizhan@shell ~]$ [[ $a = $b ]] && echo true || echo false false [shizhan@shell ~]$ [[ $a != $b ]] && echo true || echo false true [shizhan@shell ~]$ (( $a >= $b )) && echo true || echo false false [shizhan@shell ~]$ (( $a <= $b )) && echo true || echo false true ``` ### 6.5 逻辑操作符 #### 6.5.1 逻辑操作符介绍 **表达式中**逻辑操作符使用说明如下: | \[\]和test中逻辑操作符 | \[\[\]\]和(())中逻辑操作符 | 说明 | |:---------------:|:-------------------:|:--------------------| | -a | \&\& | and,与,两端都为真,则结果为真 | | -o | II | or,或 ,两端有一个为真,则结果为真 | | ! | ! | not,非,两端相反,则结果为真 | 对于上述操作符,有如下提示: * 逻辑操作符前后的表达式是否成立,一般用真假来表示。 * "!"的中文意思是反,即与一个逻辑值相反的逻辑值。 * -a的中文意思是"与"(and或\&\&),前后两个逻辑值都为"真",综合返回值才为"真",反之为"假"。 * -o的中文意思是"或"(or或\|\|),前后两个逻辑值只要有一个为"真",返回值就为"真"。 **表达式外:**\&\&或\|\|可用于连接两个含\[\]、test或\[\[\]\]的表达式: * 使用\&\&表达式时: * 当左边为真,右边表达式才会执行。 * 当左边为假,右边表达式不会执行。 * 使用\|\|的表达式时: * 当左边为真,右边表达式不会执行。 * 当左边为假,右边表达式才会执行。 #### 6.5.2 逻辑操作符实践示例 ```bash # 判断表达式内部:逻辑与 [shizhan@shell ~]$ file=/etc/passwd [shizhan@shell ~]$ [ -f $file -a -r $file ] && head -1 $file root:x:0:0:root:/root:/bin/bash [shizhan@shell ~]$ [[ -f $file && -r $file ]] && head -1 $file root:x:0:0:root:/root:/bin/bash # 判断表达式内部:逻辑或 [shizhan@shell ~]$ num=10 [shizhan@shell ~]$ [ $num -lt 20 -o $num -gt 1 ] && echo $num is between 1 and 20. 10 is between 1 and 20. [shizhan@shell ~]$ [[ $num -lt 20 || $num -gt 1 ]] && echo $num is between 1 and 20. is between 1 and 20. # 判断表达式内部:逻辑非 [shizhan@shell ~]$ [ ! 6 -eq 5 ] && echo 6!=5 6!=5 [shizhan@shell ~]$ [[ ! 6 -eq 5 ]] && echo 6!=5 6!=5 ``` #### 6.5.3 逻辑操作符企业案例 ```bash [shizhan@shell ~]$ data_path=/tmp/shizhan [shizhan@shell ~]$ mkdir ${data_path} [shizhan@shell ~]$ [ -d ${data_path} ] && echo ${data_path} is exist /tmp/shizhan is exist [shizhan@shell ~]$ rmdir ${data_path} [shizhan@shell ~]$ [ -d ${data_path} ] && echo ${data_path} is not exist [shizhan@shell ~]$ [ -d ${data_path} ] || mkdir ${data_path} [shizhan@shell ~]$ ls -d ${data_path} /tmp/shizhan ``` ### 6.6 判断表达式 test、\[\]、\[\[\]\]、(())的区别总结 | 判断表达式符号 | \[\] | test | \[\[\]\] | (()) | |-----------|:-----------------------:|:-----------------------:|:-----------------------:|:------------------:| | 边界是否需要空格 | 需要 | 需要 | 需要 | 不需要 | | 逻辑操作符 | !、-a、-o | !、 -a、 -o | !、\&\&、\|\| | !、\&\&、\|\| | | 整数比较操作符 | -eq、-ne、-gt、-ge、-lt、-le | -eq、-ne、-gt、-ge、-lt、-le | -eq、-ne、-gt、-ge、-lt、-le | =、!=、\>、\>=、\<、\<= | | 字符串比较操作符 | =、== 、!= | =、== 、!= | =、== 、!= | == 、!= | | 是否支持通配符匹配 | 不支持 | 不支持 | 支持(=\~) | 不支持 |