为什么要以间接方式测试sh脚本中的相等性?

Dog*_*Dog 3 bash shell sh

我经常在sh脚本中看到这个构造:

if [ "z$x" = z ]; then echo x is empty; fi
Run Code Online (Sandbox Code Playgroud)

他们为什么不这样写呢?

if [ "$x" = "" ]; then echo x is empty; fi
Run Code Online (Sandbox Code Playgroud)

les*_*ana 7

TL; DR简答

在这个结构中:

if [ "z$x" = z ]; then echo x is empty; fi
Run Code Online (Sandbox Code Playgroud)

z是一个防范有趣的内容$x和许多其他问题.

如果你写的没有z:

if [ "$x" = "" ]; then echo x is empty; fi
Run Code Online (Sandbox Code Playgroud)

$x包含-x您将获得的字符串:

if [ "-x" = "" ]; then echo x is empty; fi
Run Code Online (Sandbox Code Playgroud)

这让一些旧的实现混乱了[.

如果你进一步省略引号$x$x包含-f foo -o x你将获得的字符串:

if [ -f foo -o x = "" ]; then echo x is empty; fi
Run Code Online (Sandbox Code Playgroud)

现在它检查完全不同的东西.


精心解释

z

if [ "z$x" = z ]; then echo x is empty; fi
Run Code Online (Sandbox Code Playgroud)

被称为守卫.

为了解释你为什么要守卫,我首先要解释bash条件的语法if.重要的是要理解它[不是语法的一部分.这是一个命令.它是test命令的别名.在大多数当前的shell中,它是一个内置命令.

语法规则if大致如下:

if command; then morecommands; else evenmorecommands; fi
Run Code Online (Sandbox Code Playgroud)

(该else部分是可选的)

command可以是任何命令.真的是任何命令.bash在遇到a时的if作用大致如下:

  1. 执行command.
  2. 检查退出状态command.
  3. 如果退出状态0则执行morecommands.如果退出状态是其他任何内容,并且该else部分存在,则执行evenmorecommands.

我们试试看:

$ if true; then echo yay; else echo boo; fi
yay
$ if wat; then echo yay; else echo boo; fi
bash: wat: command not found
boo
$ if echo foo; then echo yay; else echo boo; fi
foo
yay
$ if cat foo; then echo yay; else echo boo; fi
cat: foo: No such file or directory
boo
Run Code Online (Sandbox Code Playgroud)

我们来试试这个test命令:

$ if test z = z; then echo yay; else echo boo; fi
yay
Run Code Online (Sandbox Code Playgroud)

别名[:

$ if [ z = z ]; then echo yay; else echo boo; fi
yay
Run Code Online (Sandbox Code Playgroud)

您看到[的不是语法的一部分.这只是一个命令.

请注意,z这里没有特殊含义.它只是一个字符串.

让我们尝试以下[命令if:

$ [ z = z ]
Run Code Online (Sandbox Code Playgroud)

什么都没发生?它返回了退出状态.您可以使用以下方式检查退出状态echo $?.

$ [ z = z ]
$ echo $?
0
Run Code Online (Sandbox Code Playgroud)

让我们尝试不等的字符串:

$ [ z = x ]
$ echo $?
1
Run Code Online (Sandbox Code Playgroud)

因为[它是一个命令,它接受参数就像任何其他命令一样.事实上,结束]也是一个参数,一个必须持续的强制参数.如果它丢失,命令会抱怨:

$ [ z = z
bash: [: missing `]'
Run Code Online (Sandbox Code Playgroud)

bash抱怨是有误导性的.实际上内置命令[会引发抱怨.当我们调用系统时,我们可以更清楚地看到谁在抱怨[:

$ /usr/bin/[ z = z
/usr/bin/[: missing `]'
Run Code Online (Sandbox Code Playgroud)

有趣的是,系统[并不总是坚持要求结束]:

$ /usr/bin/[ --version
[ (GNU coreutils) 7.4
...
Run Code Online (Sandbox Code Playgroud)

在结束前需要一个空格,]否则它将不会被识别为参数:

$ [ z = z]
bash: [: missing `]'
Run Code Online (Sandbox Code Playgroud)

你还需要一个空格,[否则bash会认为你想要执行另一个命令:

$ [z = z]
bash: [z: command not found
Run Code Online (Sandbox Code Playgroud)

当您使用时,这更加明显test:

$ testz = z
bash: testz: command not found
Run Code Online (Sandbox Code Playgroud)

记住[只是另一个名字test.

[可以做的不仅仅是比较字符串.它可以比较数字:

$ [ 1 -eq 1 ]
$ [ 42 -gt 0 ]
Run Code Online (Sandbox Code Playgroud)

它还可以检查文件或目录的存在:

$ [ -f filename ]
$ [ -d dirname ]
Run Code Online (Sandbox Code Playgroud)

help [man [有关的功能的更多信息[(或test).man将显示系统命令的文档.help将显示bash builtin命令的文档.

现在我已经涵盖了基础,我可以回答你的问题:

为什么人们写这个:

if [ "z$x" = z ]; then echo x is empty; fi
Run Code Online (Sandbox Code Playgroud)

而不是这个:

if [ "$x" = "" ]; then echo x is empty; fi
Run Code Online (Sandbox Code Playgroud)

为简洁起见,我将剥离它,if因为这只是关于[.

z这个结构:

[ "z$x" = z ]
Run Code Online (Sandbox Code Playgroud)

是一个防范有趣的内容$x与旧的实现相结合[,和/或防止人为错误,如忘记引用$x.

$x有趣的内容时会发生什么-f

这个

[ "$x" = "" ]
Run Code Online (Sandbox Code Playgroud)

会变成

[ "-f" = "" ]
Run Code Online (Sandbox Code Playgroud)

[当第一个参数以a开头时,一些较旧的实现会混淆-.这z将确保第一个参数永远不会以任何-内容开头$x.

[ "z$x" = "z" ]
Run Code Online (Sandbox Code Playgroud)

会变成

[ "z-f" = "z" ]
Run Code Online (Sandbox Code Playgroud)

当你忘了引用时会发生什么$x?有趣的内容-f foo -o x可以改变测试的整个意义.

[ $x = "" ]
Run Code Online (Sandbox Code Playgroud)

会变成

[ -f foo -o x = "" ]
Run Code Online (Sandbox Code Playgroud)

测试现在检查文件foo的存在,然后检查逻辑或是否x为空字符串.最糟糕的是你甚至都不会注意到,因为没有错误信息,只有退出状态.如果$x来自用户输入,这甚至可以用于恶意攻击.

随着守卫 z

[ z$x = z ]
Run Code Online (Sandbox Code Playgroud)

会变成

[ z-f foo -o x = z ]
Run Code Online (Sandbox Code Playgroud)

至少你现在会收到一条错误信息:

$ [ z-f foo -o x = z ]; echo $?
bash: [: too many arguments
Run Code Online (Sandbox Code Playgroud)

防护也有助于防止未定义的变量而不是空字符串.一些较旧的shell对于未定义的变量和空字符串具有不同的行为.在现代shell中,undefined主要表现为空字符串.引用$x有助于使未定义的案例更像空字符串案例.保护$x更有帮助,因为它还可以防止上面提到的所有其他问题.

观察引用:

$ x=""
$ [ "$x" = "" ]; echo $?
0
$ unset x
$ [ "$x" = "" ]; echo $?
0
Run Code Online (Sandbox Code Playgroud)

没有引用:

$ x=""
$ [ $x = "" ]; echo $?
bash: [: =: unary operator expected
2
$ unset x
$ [ $x = "" ]; echo $?
bash: [: =: unary operator expected
2
Run Code Online (Sandbox Code Playgroud)

没有引用警卫:

$ x=""
$ [ z$x = z ]; echo $?
0
$ unset x
$ [ z$x = z ]; echo $?
0
Run Code Online (Sandbox Code Playgroud)

防护z将防止所有这些可能的错误.有趣的内容$x,旧的实现[,忘记引用$x或未定义$x.守卫z将做正确的事情或提出错误信息.

现代实现[已经解决了许多问题,而现代外壳为其他案例提供了解决方案,但它们都有自己的缺陷.保护z是没有必要的,但它使编写简单的测试没有错误更简单.

也可以看看: