当字符串是左括号时,shell 括号测试出错

当字符串是左括号时,shell 括号测试出错

我曾经确信引用字符串始终是一个很好的做法,以避免 shell 解析它。

然后我遇到了这个:

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

尝试隔离问题,得到相同的错误:

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

我这样解决了这个问题:

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

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

答案1

[这是一个非常模糊的极端情况,人们可能会认为测试内置的定义方式存在错误;但是,它[确实与许多系统上可用的实际二进制文件的行为相匹配。据我所知,它只影响某些情况以及具有与、、、等[运算符匹配的值的变量。(!=-e

让我解释一下原因,以及如何在 Bash 和 POSIX shell 中解决这个问题。


解释:

考虑以下:

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

没问题;上面的代码不会产生错误,并且输出yes.这就是我们期望事物发挥作用的方式。如果您愿意,您可以将比较字符串更改为'1',以及 的值x,它将按预期工作。

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

当我们用第二个表达式。第二个表达式是什么并不重要,只要它有效即可。例如,

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

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

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

x当且仅当is (or时,Bash 拒绝表达式!

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

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

该错误是可以理解的:由于 shell 进行了变量替换,因此/usr/bin/[二进制文件仅接收参数( = ( -a 1 = 1和终止符],因此它无法解析左括号是否开始子表达式,这是可以理解的 涉及操作。当然,将其解析为两个字符串比较是可能的,但是当应用于带有括号的子表达式的正确表达式时,贪婪地这样做可能会导致问题。

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

(这些歧义以及与变量扩展相关的其他歧义是 Bash 实现并且现在建议使用[[ ... ]]测试表达式的一个重要原因。)


该解决方法很简单,并且经常出现在使用旧shshell 的脚本中。您通常会x在字符串(正在比较的两个值)前面添加一个“安全”字符,以确保表达式被识别为字符串比较:

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

答案2

[又名test看到:

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

test接受括号中的子表达式;所以它认为左括号打开一个子表达式并尝试解析它;解析器将其视为=子表达式中的第一件事,并认为它是隐式字符串长度测试,因此它很高兴;子表达式后面应该跟一个右括号,解析器会找到1而不是).它抱怨。

test恰好有三个参数,并且中间参数是可识别的运算符之一时,它将将该运算符应用于第一个和第三个参数,而不查找括号中的子表达式。

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

结论:所使用的解析算法test比较复杂。仅使用简单的表达式并使用运算符!&&并将||它们组合起来。

相关内容