我经常在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)
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作用大致如下:
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
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是没有必要的,但它使编写简单的测试没有错误更简单.
也可以看看: