我经常在 sh 脚本中看到这个结构:
if [ "z$x" = z ]; then echo x is empty; fi
他们为什么不这样写呢?
if [ "$x" = "" ]; then echo x is empty; fi
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
可以是任何命令。真的任何命令。bash遇到an时的行为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
让我们试试这个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
此处没有特殊含义。它只是一个字符串。
让我们尝试一下[
a 之外的命令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 [
或。将向您显示系统命令的文档。将向您展示 bash 内置命令的文档。man [
[
test
man
help
现在我已经涵盖了基础,我可以回答你的问题:
为什么人们会这样写:
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
将确保第一个参数永远不会以 a 开头,-
而不管$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
(恶意用户代码注入)的搞笑内容[
(如果字符串以 开头会感到困惑-
)的旧实现$x
(将允许-f foo -o x
颠覆测试的含义)$x
。(如果未定义,旧实现的行为会有所不同)守卫要么做正确的事,要么引发错误消息。
的现代实现[
已经解决了一些问题,现代 shell 也为其他情况提供了一些解决方案,但它们也有自己的缺陷。z
如果您小心谨慎,则无需进行防护,但是它可以使编写简单测试时避免错误变得更加简单。
也可以看看:
要测试零长度,请使用-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"
请注意,第二种形式假定预期的字符串不能与任何一元主字符串混淆。如果预期的字符串以 '-'、'('、'!' 或什至 '=' 开头,则应使用第一种形式。
简短的回答:[
实际上不是 bash 指令,[[
而是。相反,它是命令行实用程序的符号链接test
。
现在为什么:
像任何其他命令行实用程序一样,test
将以 a 开头的任何内容解释-
为选项。它还认为任何=
以操作符开头的东西。如果您没有在参数[
(或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 系统(或至少现代系统)上很好,因为 test 已被重写以忽略=
or!=
运算符之前或之后的选项。但是,在某些较旧的 UNIX 系统上,这将中断并出现如下错误:
test: -lt: unary operator expected
如果您想确保定义 x :
if [ ${x:-Z} = 'Z' ];then
echo x is empty
fi