为什么要以间接方式测试sh脚本中的相等性?

时间:2013-08-14 21:43:19

标签: bash shell sh

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

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

他们为什么不这样写呢?

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

4 个答案:

答案 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的作用大致如下:

  1. 执行command
  2. 检查command
  3. 的退出状态
  4. 如果退出状态为0,请执行morecommands。如果退出状态是其他任何内容,并且else部分存在,则执行evenmorecommands
  5. 让我们试试:

    $ 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 [)功能的详情,请参阅[testman将显示系统命令的文档。 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