Bash 条件判断
其实本文不是介绍 条件分支语法 的, 只介绍测试(test)语法,也就是说:本文只介绍如何写条件语法中的表达式。 其实本文介绍的内容也并未针对 Bash,多数 Shell 都是适用的。
与其他编程语言类似,if
的条件可以接受任何 表达式,计算为真就进入分支。
只是在 Bash 中表达式是一个命令调用。
真与假
在 POSIX 标准下,程序的 退出状态 为零表示成功,非零表示失败。 而 Bash 的 表达式 就是命令调用,因此 0 为真(命令调用成功)1 为假(命令调用失败)。
> true
> echo $? # 查看上一个命令的退出状态
0
> false
> echo $?
1
有些 Shell 中 true
是 built-in 命令,有些 Shell 中是 /usr/bin/true
二进制文件,但它们都会给出退出码零。
() subshell
如果命令很复杂可能会破坏 if
的结构,这时可以通过 ( expression )
来强制在 subshell 中执行。
其中 ()
是 Shell 特殊字符。
没错,跟管道一样这会开一个 subshell。这里提一下 subshell 和子进程的区别:
subshell 是一种特殊的子进程,可以访问父进程的任何变量(不需要 export
)。下面的例子来自 wooledge.org:
unset a; a=1
(echo "a is $a in the subshell") # a is 1 in the subshell
sh -c 'echo "a is $a in the child shell"' # a is in the child shell
但 subshell 仍然是一种子进程,这意味着 subshell 中环境变量的定义和赋值对父 shell 是不可见的。 这一点是 subshell 的主要用途。
test 命令
test 命令可以用来测试文件是否存在、文件类型,也可以进行字符串判等、数字比较等操作。
在测试为真时 test
命令的退出码为 0,测试为假时退出码为 1,发生错误时大于 1。
使用示例
多数 Shell 实现中它们都属于 built-in 命令,它有两种使用方式:
test expression
[ expression ]
例如:
if test -e harttle.png; then
echo file exists
fi
等价于:
if [ -e harttle.png ]; then
echo file exists
fi
注意第二种用法中 [
是一个命令,expression
和 ]
是它的两个参数。
因此如果 [
后没有空格会发生 command not found 错误。
常用参数
下面介绍常见的几种用法,完整列表请 man test
或 man [
。
文件判断
可以检查文件是否存在,文件类型,权限状态等。下面常见的文件判断参数:
-e file: file 存在
-d file: file 是一个目录
-f file: file 是一个普通文件(regular file)
-s file: file 非空(大小不为零)
-w file: file 是否有可写标志
-x file: file 是否有可执行标志
字符串判断
主要包括字符串长度判断,和字符串判等两类:
-n string: 字符串长度非零
-z string: 字符串长度为零
string1 = string2: 字符串相等
string1 != string2: 字符串不相等
string: 字符串不是null时为真,否则为假
数字判断
数字测试参数包括:
num1 -eq num2: 等于
num1 -ne num2: 不等于
num1 -le num2: 小于等于
num1 -lt num2: 小于
num1 -ge num2: 大于等于
num1 -gt num2: 大于
这里有一个陷阱,既然 num1
, -gt
, num2
都是以参数的形式传递给 test
命令的,
那么如果参数替换的结果是空,那么就会因为缺少参数而崩掉。例如:
> a=
> test 1 -eq $a
zsh: parse error: condition expected: 1
所以需要引号包裹所有变量来保证参数总是存在的,字符串判断也需要一样的处理。
> a=
> test 1 -eq "$a"
> echo $?
1
逻辑运算
当然,与(-a
)或(-o
)非(!
)都是支持的。但括号要转义,因为括号是 Shell 特殊字符,用来开启子 Shell 的。
一个复杂的例子如下:
if [ ! \( -f harttle.png \) ]; then
echo file not found
fi
需要强调的仍然是注意空格!即使 \(
也是需要单独一个参数传递给 [
命令。
下面的代码会产生错误:[: (-f: unary operator expected
,
因为 !
是单目运算符(Unary Operator),但后面跟着的是两个参数:\(-f
和 harttle.png\)
。
if [ ! \(-f harttle.png\) ]; then
echo file not found
fi
[[ 条件表达式
可以把 [[ expression ]]
看做是 [
的现代版本,它与 test
命令有几乎一样的参数。
但它是一个新的语法结构,不再是一个普通的 test
命令,因此不受制与 Shell 的 参数展开,比如
不需要用引号包裹所有变量,也支持类似 &&
,||
这样的逻辑操作而不需要用类似 -a
,-o
这样的参数。
例如:
if [[ $string1 == 'harttle' ]]; then
echo The author for this article is harttle
fi
[[
语法还支持正则匹配,文章评论中有具体的例子,感谢 yantze。
详细的文档见 https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html#Conditional-Constructs。
算术展开(Arithmetic Expansion)
算术展开
的语法是 $((expression))
,这是从其他编程语言来的人最顺手的算术操作方式。与 test
命令相比,
- C 风格的算术操作语法
- Shell 会把
expression
内容的每一项都当做就像被引号包裹了一样。 - 会依次进行参数展开、命令替换,和 引号移除。
因此可以不需要像 test
命令那样用引号包裹所有变量,也不需要注意空格,也可以写 C 风格的操作:
> a=2
> echo $(($a+3))
5
这只是一种 shell 展开并没有 subshell,因此在括号内进行的赋值会直接反映到当前的变量上。
需要注意的是 C 风格的判等 1 为 True,0 为 False。因此用作 if
命令的条件时需要反过来:
if test $(( 1 == 1 )) -eq 1; then
echo one equals one
fi
是不是很变态?是的。因为 C 风格与 Shell 风格就是不同,下面介绍的 [[ 条件表达式 更加 Bash 风格,用起来表现地更一致。
总结
- Shell 中的
if
通过命令的退出码来确定成功与否,而成功(True)的退出码是零。 test
是最基本的 Shell 风格的判断命令,它是一个命令所以要命令行参数的方式去使用,要小心参数替换。[[ expression ]]
是现代版的test
,是一个语法构造而非命令。因此在传参上可以舒服很多。- 如果你是来自 C 的程序员,算术展开 会很适合你进行任何算术操作,包括条件判断。
需要注意的是,所有空格仍然需要保留,因为它仍然依赖 Bash 的词法解析。 更多使用方式请参考 Bash Hackers Wiki。
本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2018/10/23/bash-test.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。