在 shell 变量与字符串文字比较的两侧添加前缀的目的是什么?

Kar*_*ter 11 shell string test variable

多年来,我多次遇到变量与字符串文字的比较,其中在变量和文字前有一个字符,例如

if [ "x$A" = "xtrue" ]; then
Run Code Online (Sandbox Code Playgroud)

为了检查是否$A"true"

我认为这样做是为了实现 shell 兼容性或解决长期错误、不直观的行为等。没有什么明显的想法。

今天我想我想知道原因,但我的研究没有任何结果。或者也许只是我在频繁接触罕见事件中有所作为。

这种做法是否仍然有用,甚至是最好的?

Sté*_*las 15

这里要理解的重要一点是,在大多数 shells¹ 中,[它只是一个由 shell 解析的普通命令,就像任何其他普通命令一样。

然后 shell使用参数列表调用该[(又名test)命令,然后[将它们解释为条件表达式。

在这一点上,那些只是一个字符串列表,并且关于哪些是由某种形式的扩展产生的信息丢失了,即使在那些[内置的shell中(现在都是类似 Bourne 的shell )。

[实用程序过去很难分辨哪些参数是操作符,哪些是操作数(操作符处理的事情)。语法本质上是模棱两可的,这无济于事。例如:

  • [ -t ]曾经(并且仍然在某些 shell/ [s 中)用于测试 stdout 是否为终端。
  • [ x ][ -n x ]: test 是否x为非空字符串的缩写(所以你可以看到与上面的冲突)。
  • 在某些 shell/ [s 中,-a并且-o可以是一元的([ -a file ]对于可访问的文件(现在被替换为[ -e file ]),[ -o option ]因为是否启用了该选项?)和二元运算符()。同样,! -a x可以是and(nonempty("!"), nonempty("x"))not(isaccessible("x"))
  • ()!添加更多问题。

在像 C 或 那样的普通编程语言中perl,在:

if ($a eq $b) {...}
Run Code Online (Sandbox Code Playgroud)

没有办法将$aor的内容$b作为运算符,因为条件表达式在它们之前被解析$a并被$b扩展。但在贝壳中,在:

[ "$a" = "$b" ]
Run Code Online (Sandbox Code Playgroud)

外壳首先扩展变量²。例如,如果$a包含($b包含)所有的[命令看到的是[(=)]参数。这意味着"(" = ")"(are (and )lexically equal) or ( -n = )(is =a non-empty string) 也是如此。

历史上的实现(test出现在 70 年代后期的 Unix V7 中)曾经失败,即使在它们处理参数的顺序没有歧义的情况下也是如此。

在 PDP11 模拟器中使用 Unix 7 版:

$ ls -l /bin/[
-rwxr-xr-x 2 bin      2876 Jun  8  1979 /bin/[
$ [ ! = x ]
test: argument expected
$ [ "(" = x ]
test: argument expected
Run Code Online (Sandbox Code Playgroud)

大多数 shell 和[实现都或曾经遇到过这些或它们的变体的问题。随着bash今日4.4:

bash-4.4$ a='(' b=-o c=x
bash-4.4$ [ "$a" = "$b" -o "$a" = "$c" ]
bash: [: `)' expected, found =
Run Code Online (Sandbox Code Playgroud)

POSIX.2(发布于 90 年代初)设计了一种算法[当以最常见的使用模式(例如仍未指定)最多传递 4 个参数(除了[])时,该算法将使的行为明确且具有确定性[ -f "$a" -o "$b" ]。它弃用了()-a-o,并且-t没有操作数就删除了。bash确实在bash2.0 中实现了该算法(或至少尝试过)。

因此,在符合 POSIX 的[实现中,[ "$a" = "$b" ]保证比较内容$a$b是否相等,无论它们是什么。没有-o,我们会写:

[ "$a" = "$b" ] || [ "$a" = "$c" ]
Run Code Online (Sandbox Code Playgroud)

也就是说,调用[两次,每次都少于 5 个参数。

但是所有[实现都需要很长时间才能合规。bash直到 4.4 才符合标准(尽管最后一个问题是[ '(' ! "$var" ')' ]在现实生活中没有人会真正使用)

/bin/shSolaris 10和年纪大了,这是不是一个POSIX外壳,但Bourne shell中的还是有问题[ "$a" = "$b" ]

$ a='!' b='!'
$ [ "$a" = "$b" ]
test: argument expected
Run Code Online (Sandbox Code Playgroud)

使用[ "x$a" = "x$b" ]可以解决这个问题,因为没有[x. 另一种选择是使用case

case "$a" in
  "$b") echo same;;
     *) echo different;;
esac
Run Code Online (Sandbox Code Playgroud)

(引用是必要的$b,而不是周围$a)。

无论如何,它不是也从来没有关于空值。人们在[忘记引用变量时会遇到空值问题,但这不是问题[

$ a= b='-o x'
[ $a = $b ]
Run Code Online (Sandbox Code Playgroud)

默认值$IFS变为:

[ = -o x ]
Run Code Online (Sandbox Code Playgroud)

这是无论测试=还是x是一个非空字符串,但没有前缀的量将help³如[ x$a = x$b ]将仍然是:[ x = x-o x ]这将导致一个错误,它可能会变得更糟,包括DoS和任意命令注射像其他值bash

bash-4.4$ a= b='x -o -v a[`uname>&2`]'
bash-4.4$ [ x$a = x$b ]
Linux
Run Code Online (Sandbox Code Playgroud)

正确的解决方案是始终引用

[ "$a" = "$b" ]   # OK in POSIX compliant [ / shells
[ "x$a" = "x$b" ] # OK in all Bourne-like shells
Run Code Online (Sandbox Code Playgroud)

请注意,它expr有类似(甚至更糟)的问题。

expr也有一个=运算符,虽然它是为了测试两个操作数在看起来像十进制整数时是否是相等的整数,或者在不是时排序。

在许多实现中,expr + = +, or expr '(' = ')'orexpr index = index不进行相等比较。expr "x$a" = "x$b"将解决它的字符串比较,但前缀 anx可能会影响排序(例如,在具有整理元素的语言环境中x)并且显然不能用于数字比较expr "0$a" = "0$b"不适用于比较负整数。expr " $a" = " $b" 适用于某些实现中的整数比较,但不适用于其他实现(对于a=01 b=1,有些会返回 true,有些会返回 false)。


¹ksh93是一个例外。在ksh93,[可以看作是一个保留字,即[ -t ]实际上不同于 from var=-t; [ "$var" ], or from""[ -t ]cmd='['; "$cmd" -t ]。这是为了保持向后兼容性,并且在重要的情况下仍然符合 POSIX。该-t只作为这里的运营商,如果它的文字,并ksh93检测到您当前调用[命令。

² ksh 添加了一个[[...]]条件表达式运算符,它有自己的语法解析规则(以及它自己的一些问题)来解决这个问题(在其他一些 shell 中也有,但有一些不同)。

³ 除了在参数扩展时不调用split+glob 的zsh地方,但空删除仍然是,或者在其他 shell 中全局禁用 split+glob 时set -o noglob; IFS=


Jde*_*eBP 7

人们经常将前缀归因于空字符串的问题,但这不是原因。问题是一个非常简单的问题:变量的扩展可能是test的运算符之一,突然将二元相等性测试转换为不同的表达式。

在大多数平台上的命令的近期实现避免与表达式解析器前瞻的陷阱,防止解析器承认第一个操作数为二进制运算符,不是操作其他任何东西,只要有足够的令牌来一个二进制运营商当然:

% a=-n
% /bin/test "$a" = -n ; 回声 $?
0
% /bin/test "$a" = ; 回声 $?
0
% /bin/test x"$a" = ; 回声 $?
测试:=:预期的参数
2
% a='('
% /bin/test "$a" = "(" ; echo $?
0
% /bin/test "$a" = ; 回声 $?
测试:预期关闭括号
2
%