我经常在sh脚本中看到这个构造:
if [ "z$x" = z ]; then echo x is empty; fi
他们为什么不这样写呢?
if [ "$x" = "" ]; then echo x is empty; fi
答案 0 :(得分:7)
TL; DR简短回答
在这个结构中:
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条件if
的语法。重要的是要理解[
不是语法的一部分。这是一个命令。它是test
命令的别名。在大多数当前的shell中,它是一个内置命令。
if
的语法规则大致如下:
if command; then morecommands; else evenmorecommands; fi
(else
部分是可选的)
command
可以是任何命令。真的是任何命令。遇到if
时bash的作用大致如下:
command
。command
。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
让我们试试test
命令:
$ if test z = z; then echo yay; else echo boo; fi
yay
别名[
:
$ if [ z = z ]; then echo yay; else echo boo; fi
yay
您看到[
不是语法的一部分。这只是一个命令。
请注意,此处的z
没有特殊含义。它只是一个字符串。
让我们在[
之外尝试if
命令:
$ [ z = z ]
什么都没发生?它返回了退出状态。您可以使用echo $?
检查退出状态。
$ [ z = z ]
$ echo $?
0
让我们尝试不等的字符串:
$ [ z = x ]
$ echo $?
1
因为[
是一个命令,它接受参数就像任何其他命令一样。事实上,结束]
也是一个参数,一个必须持续的强制参数。如果它丢失,命令会抱怨:
$ [ z = z
bash: [: missing `]'
bash引起抱怨是误导。实际上内置命令[
做了抱怨。当我们调用系统[
时,我们可以更清楚地看到谁在抱怨:
$ /usr/bin/[ z = z
/usr/bin/[: missing `]'
有趣的是,系统[
并不总是坚持要求结束]
:
$ /usr/bin/[ --version
[ (GNU coreutils) 7.4
...
在结束]
之前需要一个空格,否则它将不会被识别为参数:
$ [ z = z]
bash: [: missing `]'
在[
之后你还需要一个空格,否则bash会认为你想要执行另一个命令:
$ [z = z]
bash: [z: command not found
使用test
:
$ testz = z
bash: testz: command not found
请记住[
只是test
的另一个名称。
[
可以做的不仅仅是比较字符串。它可以比较数字:
$ [ 1 -eq 1 ]
$ [ 42 -gt 0 ]
它还可以检查文件或目录的存在:
$ [ -f filename ]
$ [ -d dirname ]
有关help [
(或man [
)功能的详情,请参阅[
或test
。 man
将显示系统命令的文档。 help
将显示bash builtin命令的文档。
既然我已经涵盖了基础,我可以回答你的问题:
为什么人们这样写:
if [ "z$x" = z ]; then echo x is empty; fi
而不是这个:
if [ "$x" = "" ]; then echo x is empty; fi
为简洁起见,我将删除if
,因为这只是[
。
此构造中的z
:
[ "z$x" = z ]
可以防范$x
的有趣内容与[
的旧版实施相结合,和/或防止人为错误,例如忘记引用$x
。
$x
有-f
等有趣内容时会发生什么?
此
[ "$x" = "" ]
将成为
[ "-f" = "" ]
当第一个参数以[
开头时,-
的一些较旧实现会混淆。无论z
的内容如何,-
都会确保第一个参数永远不会以$x
开头。
[ "z$x" = "z" ]
将成为
[ "z-f" = "z" ]
当您忘记引用$x
时会发生什么?像-f foo -o x
这样的有趣内容可以改变测试的整个含义。
[ $x = "" ]
将成为
[ -f foo -o x = "" ]
测试现在正在检查文件foo的存在,然后检查逻辑或x
是否为空字符串。最糟糕的是,你甚至没有注意到,因为没有错误信息,只有退出状态。如果$x
来自用户输入,则甚至可以用于恶意攻击。
保护z
[ z$x = z ]
将成为
[ z-f foo -o x = z ]
至少你现在会收到一条错误信息:
$ [ z-f foo -o x = z ]; echo $?
bash: [: too many arguments
后卫还有助于防止未定义变量而不是空字符串。一些较旧的shell对于未定义的变量和空字符串具有不同的行为。在现代shell中,undefined主要表现为空字符串。引用$x
有助于使未定义的案例更像空字符串案例。保护$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
防护z
将阻止所有这些可能的错误。 $x
的有趣内容,[
的旧实现,忘记引用$x
或未定义$x
。保护z
将做正确的事情或提出错误信息。
[
的现代实现解决了许多问题,现代shell为其他案例提供了解决方案,但它们有自己的缺陷。保护z
不是必需的,但它使得编写简单的测试没有错误变得更加简单。
另见:
答案 1 :(得分:1)
要测试零长度,请使用-z
:
if [ -z "$x" ] ; then
echo x is empty
fi
使用bash,您可以使用不需要引号的[[
:
if [[ -z $x ]] ; then
echo x is empty
fi
我刚刚在man 1p sh
中找到了以下内容,即POSIX shell的文档:
鉴于共同的结构,历史系统也不可靠:
test "$response" = "expected string"
以下之一是更可靠的表格:
test "X$response" = "Xexpected string" test "expected string" = "$response"
请注意,第二种形式假定期望的字符串不能与任何一元主要混淆。如果预期字符串以' - ','(','!'或甚至'='开头,则应使用第一个表单。
答案 2 :(得分:1)
简短回答:[
实际上不是bash指令,而[[
是。它是命令行实用程序test
的符号链接。
现在为什么:
与任何其他命令行实用程序一样,test
将以-
开头的任何内容解释为选项。它还会将以=
开头的任何内容视为运算符。如果您没有为[
(或test
)的参数添加带字母字符的前缀,则无法保证测试能够可靠地运行。
考虑值:
a=1
b=1
评价:
[ "$a" = "$b" ] && echo "Yes, they match"
本质上运行以下命令(当exec名称为]
时,测试忽略结束[
):
test 1 = 1 ] && echo "Yes, they match"
现在考虑以下值:
a="-lt"
b="-lt"
参数-lt
是一个测试选项。因此,当您执行相同的测试时,它会扩展为:
test -lt = -lt ] && echo "Yes, they match"
现在,这在Linux系统(或至少是现代系统)上很好用,因为测试已被重写以忽略=
或!=
运算符之前或之后的选项。但是,在某些较旧的UNIX系统上,这会因以下错误而中断:
test: -lt: unary operator expected
答案 3 :(得分:0)
如果你想确定x被定义:
if [ ${x:-Z} = 'Z' ];then
echo x is empty
fi