条件判断是 Shell 脚本流程控制的基石。本文系统整理 if 语句的基本语法、[ ] 中的各类测试条件,以及 (( )) 和 [[ ]] 这两种高级写法,并附上常见用法示例。
基本语法
if [ command ]; then
符合该条件执行的语句
fi
扩展语法
if [ command ]; then
符合该条件执行的语句
elif [ command ]; then
符合该条件执行的语句
else
符合该条件执行的语句
fi
语法说明
Shell 会按顺序执行 if 中的判断命令,如果命令执行后的返回状态是 0,则执行 then 后面的语句,否则跳过,继续后面的判断。
当有多个 elif 分支时,只有第一个返回 0 退出状态的分支会导致对应语句被执行;如果所有条件都不为 0,则执行 else 中的语句。
注意事项:
[ ]表示条件测试,空格很重要:[后面和]前面都必须要有空格。then和fi是分开的语句,如果要写在同一行,需要用分号隔开,如if [ ... ]; then。if判断中对变量要加双引号,以免不必要的错误。不加双引号时,含空格的字符串变量判断会出问题,比如[ -n "$var" ],如果 var 为空且不加引号会出错。[ ]中的数值判断不支持浮点值。- 如果单独使用
>或<号,系统会认为是输出或输入重定向,虽然有时结果显示正确,但其实是不对的,因此要对这些符号进行转义(或改用(( )))。 - 默认情况下,运行
if语句中的命令所产生的错误信息仍然会出现在脚本的输出结果中。 - 使用
-z或-n检查字符串长度时,未定义的变量也按长度为 0 处理。 - 空变量和未初始化的变量可能对脚本测试产生灾难性的影响,因此在不确定变量内容时,先用
-n或-z测试一下。 $?变量包含了上一条命令的退出状态(最近完成的前台进程),可用于检测退出状态。
常用参数
文件/目录判断
| 写法 | 说明 |
|---|---|
[ -a FILE ] |
FILE 存在则为真 |
[ -b FILE ] |
FILE 存在且是一个块设备文件则为真 |
[ -c FILE ] |
FILE 存在且是一个字符设备文件则为真 |
[ -d FILE ] |
FILE 存在且是一个目录则为真 |
[ -e FILE ] |
指定的文件或目录存在时为真 |
[ -f FILE ] |
FILE 存在且是一个普通文件则为真 |
[ -g FILE ] |
FILE 存在且设置了 SGID 则为真 |
[ -h FILE ] |
FILE 存在且是一个符号链接则为真(该选项在一些老系统上无效) |
[ -k FILE ] |
FILE 存在且设置了粘滞位则为真 |
[ -p FILE ] |
FILE 存在且是命令管道则为真 |
[ -r FILE ] |
FILE 存在且可读则为真 |
[ -s FILE ] |
FILE 存在且大小非 0 则为真 |
[ -u FILE ] |
FILE 存在且设置了 SUID 位则为真 |
[ -w FILE ] |
FILE 存在且可写则为真(目录要被写入内容,还必须是可执行的) |
[ -x FILE ] |
FILE 存在且可执行则为真 |
[ -O FILE ] |
FILE 存在且属主为有效用户 ID 则为真 |
[ -G FILE ] |
FILE 存在且默认组为当前组则为真(只检查系统默认组) |
[ -L FILE ] |
FILE 存在且是一个符号链接则为真 |
[ -N FILE ] |
FILE 存在且自上次读取后被修改过则为真 |
[ -S FILE ] |
FILE 存在且是一个套接字则为真 |
[ FILE1 -nt FILE2 ] |
FILE1 比 FILE2 新,或者 FILE1 存在但 FILE2 不存在则为真 |
[ FILE1 -ot FILE2 ] |
FILE1 比 FILE2 老,或者 FILE2 存在但 FILE1 不存在则为真 |
[ FILE1 -ef FILE2 ] |
FILE1 和 FILE2 指向相同的设备和节点号则为真 |
字符串判断
| 写法 | 说明 |
|---|---|
[ -z STRING ] |
STRING 长度为零则为真(即空为真) |
[ -n STRING ] |
STRING 长度非零则为真(即非空为真) |
[ STRING1 ] |
字符串不为空则为真,与 -n 类似 |
[ STRING1 == STRING2 ] |
两个字符串相同则为真 |
[ STRING1 != STRING2 ] |
两个字符串不相同则为真 |
[ STRING1 \< STRING2 ] |
STRING1 字典排序在 STRING2 前面则为真 |
[ STRING1 \> STRING2 ] |
STRING1 字典排序在 STRING2 后面则为真 |
数值判断
| 写法 | 说明 |
|---|---|
[ INT1 -eq INT2 ] |
两数相等则为真(=) |
[ INT1 -ne INT2 ] |
两数不等则为真(<>) |
[ INT1 -gt INT2 ] |
INT1 大于 INT2 则为真(>) |
[ INT1 -ge INT2 ] |
INT1 大于等于 INT2 则为真(>=) |
[ INT1 -lt INT2 ] |
INT1 小于 INT2 则为真(<) |
[ INT1 -le INT2 ] |
INT1 小于等于 INT2 则为真(<=) |
逻辑判断
| 写法 | 说明 |
|---|---|
[ ! EXPR ] |
逻辑非,EXPR 为 false 则为真 |
[ EXPR1 -a EXPR2 ] |
逻辑与,EXPR1 和 EXPR2 全真则为真 |
[ EXPR1 -o EXPR2 ] |
逻辑或,EXPR1 或 EXPR2 为真则为真 |
[ ] || [ ] |
用 OR 合并两个条件 |
[ ] && [ ] |
用 AND 合并两个条件 |
其他判断
[ -t FD ]:如果文件描述符 FD(默认值为 1)打开且指向一个终端则为真。[ -o optionname ]:如果 Shell 选项 optionname 开启则为真。
IF 高级特性
双圆括号 (( )):表示数学表达式。普通的 [ ] 判断中只允许进行简单的算术比较,而双圆括号提供了更多的数学运算符,并且其中的 >、< 号不需要转义。
双方括号 [[ ]]:表示高级字符串处理。双方括号中的判断使用标准的字符串比较,还可以使用匹配模式,从而支持与字符串相匹配的正则表达式。
双括号的作用举例:在 Shell 中,[ $a != 1 || $b = 2 ] 是不允许的,要写成 [ $a != 1 ] || [ $b = 2 ],而双括号就可以解决这个问题:[[ $a != 1 || $b = 2 ]]。又比如 [ "$a" -lt "$b" ],也可以改成双括号的形式 (( "$a" < "$b" ))。
实例
1:判断目录 $doiido 是否存在,若不存在,则新建一个
if [ ! -d "$doiido" ]; then
mkdir "$doiido"
fi
2:判断普通文件 $doiido 是否存在,若不存在,则新建一个
if [ ! -f "$doiido" ]; then
touch "$doiido"
fi
3:判断 $doiido 是否存在并且是否具有可执行权限
if [ ! -x "$doiido" ]; then
mkdir "$doiido"
chmod +x "$doiido"
fi
4:判断变量 $doiido 是否有值
if [ ! -n "$doiido" ]; then
echo "$doiido is empty"
exit 0
fi
5:两个变量判断是否相等
if [ "$var1" = "$var2" ]; then
echo '$var1 eq $var2'
else
echo '$var1 not eq $var2'
fi
6:测试退出状态
if [ $? -eq 0 ]; then
echo 'That is ok'
fi
7:数值的比较
if [ "$num" -gt "150" ]; then
echo "$num is bigger than 150"
fi
8:a > b 且 a < c
(( a > b )) && (( a < c ))
[[ $a > $b ]] && [[ $a < $c ]]
[ $a -gt $b -a $a -lt $c ]
三种写法等价,其中前两种借助双括号,可读性更好,也更推荐。