字符串为左括号时,shell括号测试错误


27

我曾经对以下事实充满信心:对字符串加引号始终是一种好习惯,以避免shell对其进行解析。

然后我遇到了这个:

$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1

试图找出问题,并得到相同的错误:

$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1

我解决了这样的问题:

[ "$x" = '1' ] && [ "$y" = '1' ]

我仍然需要知道这里发生了什么。


2
作为解决方法,在bash中,您可以使用[[ "$x" = '1' && "$y" = '1' ]]
terdon

3
POSIX测试规范对此进行了明确描述,-a并且-o由于这个原因而过时了(这就是[OB]其规范旁边的上标的含义)。如果您改写了[ "$x" = 1 ] && [ "$y" = 1 ],那么您会很好的,并且会很好地定义/规范化的行为范围内。
查尔斯·达菲

6
这就是为什么人们习惯于用来[ "x$x" = "x1" ]防止将参数误解为运算符的原因。
乔纳森·莱夫勒

@JonathanLeffler:嘿,您将我的回答压缩为一个句子,这不公平!:)如果使用的是POSIX shell dash而不是Bash,那仍然是一种有用的做法。
Nominal Animal

我要感谢大家花时间回答我的问题,非常感谢大家:)!我还要感谢对我的问题所做的重要编辑。进入这个论坛有时会让我同样有逃离恶魔岛的快感,错误的举动意味着你的生活。无论如何,我真的希望我的感谢能在删除此评论之前到达您的位置
Claudio 2016年

Answers:


25

这是一个非常模糊的极端情况,可能会考虑如何[定义内置测试的错误。但是,它确实与[许多系统上可用的实际二进制文件的行为相匹配。据我所知道的,它只影响某些情况下以及具有相匹配的值的变量[运营商一样(!=-e,等。

让我解释一下为什么以及如何在Bash和POSIX Shell中解决它。


说明:

考虑以下:

x="("
[ "$x" = "(" ] && echo yes || echo no

没问题; 上面没有错误,并输出yes。这就是我们期望工作的方式。您可以根据需要将比较字符串更改为'1',并将的值更改x为预期值。

请注意,实际的/usr/bin/[二进制文件的行为方式相同。如果您运行例如'/usr/bin/[' '(' = '(' ']',则没有错误,因为程序可以检测到参数由单个字符串比较操作组成。

该错误在我们第二个表达式出现时发生。第二表达式是什么都没有关系,只要它是有效的即可。例如,

[ '1' = '1' ] && echo yes || echo no

输出yes,显然是有效的表达式;但是,如果我们将两者结合起来,

[ "$x" = "(" -a '1' = '1' ] && echo yes || echo no

当且仅当x(或时,Bash拒绝该表达式!

如果我们使用实际[程序运行上述程序,即

'/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no

该错误是可以理解的:由于外壳程序执行变量替换,因此/usr/bin/[二进制仅接收参数( = ( -a 1 = 1和终止符],因此可以理解,无法解析开括号是否开始子表达式,并且 涉及到操作。当然,可以将其解析为两个字符串比较,但是如果将其应用于带有括号的子表达式的正确表达式,可能会产生贪婪的感觉,这可能会引起问题。

实际上,问题在于[内置的shell 行为相同,就像x在检查表达式之前扩展了value 一样。

(这些歧义性以及其他与变量扩展有关的歧义是实施Bash的很大原因,现在建议[[ ... ]]改为使用测试表达式。)


解决方法很简单,通常在使用旧版shShell的脚本中看到。您通常x在字符串(两个要比较的值)之前添加一个“安全”字符,以确保将该表达式识别为字符串比较:

[ "x$x" = "x(" -a "x$y" = "x1" ]

1
我不会将[内置行为称为错误。如果有的话,这是一个固有的设计缺陷。 [[是一个shell 关键字,而不仅仅是一个内置命令,因此它可以在删除引号之前查看内容,并实际上覆盖了通常的单词拆分。例如[[ $x == 1 ]],不需要使用"$x",因为[[上下文不同于普通上下文。无论如何,这是如何[[能够避免的陷阱的[。POSIX要求[以行为方式进行操作,并且bash甚至在没有的情况下也与POSIX兼容--posix,因此更改[为关键字没有吸引力。
彼得·科德斯

POSIX不建议您采用解决方法。只需对调用两次即可[
2016年

@PeterCordes:完全正确;好点子。(我也许应该在第一段中使用设计而不是定义,但是[在行为上受历史的局限在POSIX之前,因此我选择了后者。)我个人避免[[在示例脚本中使用它,但这只是因为它是一个我经常竖起的引号建议的例外(因为省略引号是我看到的脚本错误的最常见原因),而且我没有想到一个简单的段落来解释为什么[[规则例外,而没有提出我的引号建议疑似。
Nominal Animal

@Wildcard:否。POSIX不建议您反对这种做法,这很重要。仅仅因为没有权威推荐这种做法,就不会使这种情况恶化。确实,正如我指出的那样,这是sh脚本中使用的历史惯例,早于POSIX标准化。使用括号中的子表达式和-a-o在测试逻辑运算符/ [是更有效的,依靠表达链接(经由&&||); 只是在当前的机器上,差异无关紧要。
Nominal Animal

@Wildcard:但是,我个人更喜欢使用&&和链接测试表达式||,但是原因是,它使我们的人类更容易维护(阅读,理解并在必要时进行修改)而不会引入错误。因此,我不是在批评您的建议,而只是在批评该建议的理由。(非常类似的原因,.LT..GE.,在FORTRAN 77等比较运算得到了更人性化的版本<>=等等。在以后的版本。)
标称动物

11

[aka test看到:

 argc: 1 2 3 4  5 6 7 8
 argv: ( = 1 -a 1 = 1 ]

test接受括号中的子表达式;因此它认为左括号打开了一个子表达式并试图对其进行解析;解析器将其=视为子表达式中的第一件事,并认为它是隐式的字符串长度测试,因此很高兴;然后,子表达式后应带有右括号,而解析器将找到1而不是)。它抱怨。

test正好具有三个自变量,而中间自变量是公认的运算符之一时,它将在第一和第三自变量中应用该运算符,而无需在括号中查找子表达式。

有关完整的详细信息man bash,请搜索test expr

结论:所使用的解析算法test很复杂。仅使用简单的表达式并使用shell运算符!&&并将||其组合在一起。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.