bash - 为什么要以间接方式在sh脚本中测试是否相等?

标签 bash shell sh

我经常在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 conditionalif的语法。必须理解[不是语法的一部分。这是命令。它是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/

相关文章:

linux - 查找文本文件中最长的单词

linux - 将 cat、head、tail 和 tr 与管道结合使用

linux - 使用 shell 脚本向电子邮件组发送电子邮件

linux - shellscript 中行之间的空格被视为命令

linux - 如何使用 shell 脚本替换属性文件中的点

Bash Select 显示超过 9 个选项很奇怪

c - Linux | C 中的 Shell 实现 |输入重定向不显示

c - "Syntax error near unexpected token ` 636 ' "

bash - 如何通过 Cygwin 在 Windows 7 上运行 .sh 文件?

shell - 使 LDAP 搜索不显示 DN