4

我经常在 sh 脚本中看到这个结构:

if [ "z$x" = z ]; then echo x is empty; fi

他们为什么不这样写呢?

if [ "$x" = "" ]; then echo x is empty; fi
4

4 回答 4

9

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大致如下:

  1. 执行command
  2. 检查退出状态command
  3. 如果退出状态是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 [[testmanhelp

现在我已经涵盖了基础,我可以回答你的问题:

为什么人们会这样写:

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如果您小心谨慎,则无需进行防护,但是它可以使编写简单测试时避免错误变得更加简单。

也可以看看:

于 2013-08-16T01:00:37.850 回答
1

要测试零长度,请使用-z

if [ -z "$x" ] ; then
    echo x is empty
fi

使用 bash,您可以使用[[不需要引号的它:

if [[ -z $x ]] ; then
    echo x is empty
fi

我刚刚在man 1p shPOSIX shell 的文档中找到了以下内容:

考虑到共同的结构,历史系统也不可靠:

    test "$response" = "expected string"

以下形式之一是更可靠的形式:

    test "X$response" = "Xexpected string"
    test "expected string" = "$response"

请注意,第二种形式假定预期的字符串不能与任何一元主字符串混淆。如果预期的字符串以 '-'、'('、'!' 或什至 '=' 开头,则应使用第一种形式。

于 2013-08-14T21:56:38.133 回答
1

简短的回答:[实际上不是 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
于 2013-08-16T08:50:10.327 回答
0

如果您想确保定义 x :

 if [ ${x:-Z} = 'Z' ];then 
    echo x is empty
 fi
于 2013-08-14T23:38:01.570 回答