我经常在sh脚本中看到这个结构:
if [ "z$x" = z ]; then echo x is empty; fi
为什么他们不这样写呢?
if [ "$x" = "" ]; then echo x is empty; fi
最佳答案
TL;DR short answer博士
在这个结构中:
if [ "z$x" = z ]; then echo x is empty; fi
z
是对$x
的有趣内容和许多其他问题的防范。如果你写的时候没有
z
:if [ "$x" = "" ]; then echo x is empty; fi
$x
包含字符串-x
您将得到:if [ "-x" = "" ]; then echo x is empty; fi
这就把
[
的一些旧实现搞混了。如果您进一步省略
$x
周围的引号,并且$x
包含字符串-f foo -o x
,您将得到:if [ -f foo -o x = "" ]; then echo x is empty; fi
现在它检查完全不同的东西。
详解
中的
z
if [ "z$x" = z ]; then echo x is empty; fi
被称为守卫。
为了解释为什么需要保护,我首先要解释bash conditional
if
的语法。必须理解[
不是语法的一部分。这是命令。它是test
命令的别名。在大多数当前的shell中,它是一个内置命令。if
的语法规则大致如下:if command; then morecommands; else evenmorecommands; fi
(可选择
else
部分)command
可以是任何命令。任何命令。bash遇到if
时所做的大致如下:执行
command
。检查< > >退出状态。
如果退出状态为“cc>”,则执行
command
。如果退出状态为其他状态,则存在0
部分,则执行morecommands
。我们试试看:
$ 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
让我们试试
else
命令:$ if test z = z; then echo yay; else echo boo; fi
yay
以及别名:
$ if [ z = z ]; then echo yay; else echo boo; fi
yay
你看
evenmorecommands
不是语法的一部分。只是命令而已。注意,这里的
test
没有特殊含义。只是一根绳子。让我们在
[
之外尝试[
命令:$ [ z = z ]
什么都没发生?它返回一个退出状态。您可以用“cc>”检查退出状态。
$ [ z = z ]
$ echo $?
0
让我们试试不相等的字符串:
$ [ z = x ]
$ echo $?
1
因为
z
是一个命令,它与其他任何命令一样接受参数。事实上,closing[
也是一个参数,必须是最后一个参数。如果缺少该命令,则会抱怨:$ [ z = z
bash: [: missing `]'
巴什抱怨是误导。实际上,内置命令
if
会产生抱怨。当我们调用系统时,我们可以更清楚地看到是谁在抱怨:$ /usr/bin/[ z = z
/usr/bin/[: missing `]'
有趣的是,系统并不总是坚持关闭:
$ /usr/bin/[ --version
[ (GNU coreutils) 7.4
...
在关闭
echo $?
之前需要一个空格,否则它将不会被识别为参数:$ [ z = z]
bash: [: missing `]'
您还需要在
[
后面加一个空格,否则bash会认为您想执行另一个命令:$ [z = z]
bash: [z: command not found
当您使用
]
时,这一点更为明显:$ testz = z
bash: testz: command not found
记住
[
只是[
的另一个名称。[
不仅仅是比较字符串。它可以比较数字:$ [ 1 -eq 1 ]
$ [ 42 -gt 0 ]
它还可以检查文件或目录的存在:
$ [ -f filename ]
$ [ -d dirname ]
有关
]
(或]
)功能的更多信息,请参见[
或test
。[
将显示系统命令的文档。test
将显示bash内置命令的文档。既然我已经讲到了基础,我可以回答你的问题:
为什么人们会这样写:
if [ "z$x" = z ]; then echo x is empty; fi
而不是这个:
if [ "$x" = "" ]; then echo x is empty; fi
为了简洁起见,我将去掉
[
,因为这只是关于help [
。此结构中的
man [
:[ "z$x" = z ]
是针对
[
的有趣内容与test
的旧实现相结合的一种保护,和/或是针对诸如忘记引用man
之类的人为错误的保护。当
help
有像if
这样有趣的内容时会发生什么?这个
[ "$x" = "" ]
会变成
[ "-f" = "" ]
当第一个参数以
[
开头时,z
的一些旧实现将变得混乱。$x
将确保无论[
的内容如何,第一个参数都不会以$x
开头。[ "z$x" = "z" ]
会变成
[ "z-f" = "z" ]
当您忘记引用
$x
时会发生什么?像-f
这样有趣的内容可以改变整个测试的意义。[ $x = "" ]
会变成
[ -f foo -o x = "" ]
该测试现在正在检查文件FO是否存在,然后检查是否逻辑或是否
[
是空字符串。最糟糕的是,你甚至没有注意到,因为没有错误消息,只有退出状态。如果-
来自用户输入,这甚至可以用于恶意攻击。带防护罩
z
[ z$x = z ]
会变成
[ z-f foo -o x = z ]
至少现在您将收到一条错误消息:
$ [ z-f foo -o x = z ]; echo $?
bash: [: too many arguments
保护也有助于防止未定义变量而不是空字符串的情况。一些旧的shell对于未定义的变量和空字符串有不同的行为。在现代shell中,未定义的大多数行为类似于空字符串。引用
-
有助于使未定义的大小写的行为更像空字符串大小写。保护$x
更有用,因为它还可以防止上述所有其他问题。注意引用:
$ x=""
$ [ "$x" = "" ]; echo $?
0
$ unset x
$ [ "$x" = "" ]; echo $?
0
未引用:
$ x=""
$ [ $x = "" ]; echo $?
bash: [: =: unary operator expected
2
$ unset x
$ [ $x = "" ]; echo $?
bash: [: =: unary operator expected
2
未引用警卫:
$ x=""
$ [ z$x = z ]; echo $?
0
$ unset x
$ [ z$x = z ]; echo $?
0
保护装置将防止所有这些可能的错误。
$x
的有趣内容,-f foo -o x
的旧实现,忘记引用x
或未定义$x
。保护z
要么做正确的事情,要么发出错误消息。现代的
$x
实现修复了许多问题,现代的shell为其他情况提供了解决方案,但它们也有自己的陷阱。保护$x
是不必要的,但它使编写简单的无错误测试变得更加简单。另见:
bash pitfalls about quoting in tests
bash FAQ more details about test
more about test
more about quoting
"test" operator robustness in various shells
关于bash - 为什么要以间接方式在sh脚本中测试是否相等?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18242822/