Shell 编程中的 test, [ 和 [[
涛叔写过 Shell 的朋友一定摆脱不了被方括号支配的恐惧,Shell 分支判断语法简直是奇葩。时而用[
,时而用 [[
,甚至有时还会用 test
,跟常见的编程语言差距太大。我自己也没弄明白为什么要分单括号和双括号,直到我遇到这篇文章1。今天就结合原文以及自己的理解为大家梳理一下 Shell 中的方括号语法。
先来一段 Shell 脚本:
a=1
if [ $a = 1 ]; then
echo yes
else
echo no
fi
执行上面的脚本会输出yes
。如果我们去掉第一行的赋值语句a=1
,再执行会输出no
,但同时还会一行报错:
sh: [: =: unary operator expected
这是为什么呢?如果换而双括号再试试:
if [[ $a = 1 ]]; then
echo yes
else
echo no
fi
就不会有报错,直接输出no
。当然了,还有一种奇技淫巧可以让单括号版本的比较也不报错,那就是给变量$a
加上双引号,改为"$a" = 1
。这又是什么原理呢?
此事从根上说就得刨到 UNIX 的基本哲学:一次只做一件事,并把它做到极致。
在 UNIX 上,最早期的 Shell 分支语法如下:
if 执行某命令; then
# 如果命令执行后返回码(也就是 $? 的值)为 0
# 则继续执行本分枝内的指令
fi
所以我们可以写这样的脚本:
if grep hello a.txt > /dev/null; then
echo find hello
fi
我们还可以写的再复杂一点:
if grep hello a.txt | grep hi > /dev/null; then
echo find hello and hi
fi
这里if
和;
之间可以是任意 Shell 命令的组合。if
只根据最后的状态码决定要执行哪个分支。那怎样比较两个变量呢?UNIX 里有专门的命令叫 test
,该命令支持多种比较操作:
test s1 = s2 # 比较字符串
test n1 -eq n2 # 比较整数
test -z string # 判断是为空字符串
test -f path # 判断文件是否存在
如果比较成功,$?
返回值就是0
,否则为非零。所以前面的数字比较可以改写为:
a=1
if test a = 1; else
echo yes
fi
虽然还是在比较是否相等,但这次执行的是 test
这个程序。整个判断过程跟前面的 grep
没有任何区别。
为了让分支判看起来更美观,前人又搞出一个种序叫 [
。对,你没看错,[
跟 grep
一样也是普通的可执行文件,路径为 /usr/bin/[
。
功能几乎跟 test
一样,便有一点小区别,[
要求最后额外加一个参数 ]
。这个参数纯属装饰,没有实际作用。但如果不传就会报错。前面的 test
判断可以改写成:
[ s1 = s2 ] # 比较字符串
[ n1 -eq n2 ] # 比较整数
[ -z string ] # 判断是为空字符串
[ -f path ] # 判断文件是否存在
对于 [ s1 = s2 ]
而言,[
是命令,后面接收四个参数s1
,=
,s2
,]
。最后的参数]
没有实际意义。前三个有用,分别是比较参数一、比较运算符、比较参数二。
如果参数不够,就会报错。
[ = 1 ]
ash: 1: unknown operand
回到最上的的例子:
if [ $a = 1 ]; then
echo yes
fi
如果脚本没有给变量$a
赋值,那么上面的比较就会变成 [ = 1 ]
,少了一个操作数,自然就会报错。如果改成加引号的版本,则比较命令就会变成 [ "" = 1 ]
,仍然有四个参数,所以不会报错。
到现在我们就解释了 [
相关的奇技淫巧。那 [[
又是什么鬼呢?
我们前面讲过,传统 Shell 的比较会起新的进程运行对应的命令,再根据返回值判断。但每次都起新的进程太耗资源,所以后来的诸如 Bash/Zsh 等新兴的 Shell 都提供了内置的比较运算符,也叫 builtin。所以内置,就是不需要依赖外部的 test
或者 [
命令,而是由 Shell 在解释执行的时候直接比较。
比如上面的
if [[ $a = 1 ]]; then
echo yes
else
echo no
fi
如果由 Bash 执行,它会把 if [[ $a = 1 ]]; then ... then ... fi
当成一个整体。当 Bash 看到 if [[ $a = 1 ]];
的时候会直接计算比较结果。因为 Bash 自己知道变量 $a
到底有没有定义,所以可以确定 $a = 1
的结果。所以使用 [[
不需要在变量上加双引号。
虽然 [[
在性能上有优势,但可能会有兼容性问题。而且在功能上也相对不灵活。如果是用 [
命令,我们甚至能写出 if [ $a = 1 ] && grep hi a.txt; then ... fi
这样的脚本,确实比较黑科技。但无论如何,很多事情表面看似复杂,实则是我们没有理解它简单的内核。Keep it simple, stupid!