Kalle Richter Asked: 2018-12-22 12:28:29 +0800 CST2018-12-22 12:28:29 +0800 CST 2018-12-22 12:28:29 +0800 CST 在shell变量比较的两侧添加前缀到字符串文字的目的是什么? 772 多年来,我多次遇到变量与字符串文字的比较,其中有一个字符作为变量和文字的前缀,例如 if [ "x$A" = "xtrue" ]; then 为了检查是否$A是"true". 我认为这样做是为了实现 shell 兼容性或解决长期错误、不直观的行为等。没有什么明显的想法出现在脑海中。 今天我想我想知道原因,但我的研究没有发现任何结果。或者,也许只是我从相当频繁地接触罕见事件中做出了一些事情。 这种做法是否仍然有用,甚至可能是最好的? shell variable 2 个回答 Voted Best Answer Stéphane Chazelas 2018-12-23T04:34:38+08:002018-12-23T04:34:38+08:00 这里要理解的重要一点是,在大多数 shell 中,[它只是一个由 shell 解析的普通命令,就像任何其他普通命令一样。 然后 shell 调用带有参数列表的[(aka test) 命令,然后将[它们解释为条件表达式。 那时,这些只是一个字符串列表,并且关于哪些字符串是由某种形式的扩展产生的信息会丢失,即使在那些[内置的 shell 中(这些天都是类似 Bourne 的 shell)。 该[实用程序过去很难分辨它的哪些参数是运算符,哪些是操作数(运算符工作的东西)。语法本质上是模棱两可的,这并没有帮助。例如: [ -t ]曾经是(并且仍然在某些 shell/ [s 中)来测试 stdout 是否是终端。 [ x ]是[ -n x ]: 测试是否x是非空字符串的缩写(所以你可以看到与上面的冲突)。 在某些 shell/ [s 中,-a并且-o可以是一元的([ -a file ]对于可访问的文件(现在替换为[ -e file ]),[ -o option ]是否启用了该选项?)和二元运算符(and和or)。同样,! -a x可以是and(nonempty("!"), nonempty("x"))或not(isaccessible("x"))。 (,)并!添加更多问题。 在 C 或 等普通编程语言perl中,在: if ($a eq $b) {...} $a不可能将or的内容$b作为运算符,因为条件表达式在它们之前被解析$a并被$b扩展。但在贝壳中,在: [ "$a" = "$b" ] shell首先扩展变量²。例如,如果$acontains(和$bcontains ,则)所有[命令看到的是[、(、=和参数。这是否意味着(在词法上相等)或(是非空字符串)。)]"(" = ")"()( -n = )= 历史实现(test出现在 70 年代末期的 Unix V7 中)过去常常失败,即使只是因为它们处理参数的顺序并不模棱两可。 在 PDP11 模拟器中使用版本 7 Unix: $ ls -l /bin/[ -rwxr-xr-x 2 bin 2876 Jun 8 1979 /bin/[ $ [ ! = x ] test: argument expected $ [ "(" = x ] test: argument expected 大多数 shell 和[实现已经或曾经遇到过这些问题或其变体。今天有bash4.4: bash-4.4$ a='(' b=-o c=x bash-4.4$ [ "$a" = "$b" -o "$a" = "$c" ] bash: [: `)' expected, found = POSIX.2(于 90 年代初发布)设计了一种算法,当以最常见的使用模式(例如仍未指定)传递最多 4 个参数(除了和)时,该算法将使[' 的行为明确和确定。它弃用了, ,和, 并删除了没有操作数。确实在2.0中实现了该算法(或至少尝试过) 。[][ -f "$a" -o "$b" ]()-a-o-tbashbash 因此,在符合 POSIX 的[实现中,[ "$a" = "$b" ]保证比较内容$a和$b是否相等,无论它们是什么。没有-o,我们会写: [ "$a" = "$b" ] || [ "$a" = "$c" ] 也就是说,调用[两次,每次使用少于 5 个参数。 但是所有[实现都需要相当长的时间才能变得合规。bash's 直到 4.4 才符合要求(尽管最后一个问题是[ '(' ! "$var" ')' ]没有人会在现实生活中真正使用它) Solaris 10 和更早版本,/bin/sh它不是 POSIX shell,但 Bourne shell 仍然存在以下问题[ "$a" = "$b" ]: $ a='!' b='!' $ [ "$a" = "$b" ] test: argument expected [ "x$a" = "x$b" ]由于没有以 .[开头的运算符,因此使用可以解决该问题x。另一种选择是使用case: case "$a" in "$b") echo same;; *) echo different;; esac (引用是必要的$b,而不是围绕$a)。 无论如何,它不是空值,也从来不是空值。人们在忘记引用变量时会遇到空值问题[,但这不是问题[。 $ a= b='-o x' [ $a = $b ] 默认值为$IFS: [ = -o x ] 这是对是否为非空字符串的测试=,x但没有任何前缀会有所帮助³[ 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 正确的解决方案是始终引用: [ "$a" = "$b" ] # OK in POSIX compliant [ / shells [ "x$a" = "x$b" ] # OK in all Bourne-like shells 请注意,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,有些会返回真,有些会返回假)。 ¹ksh93是一个例外。in ksh93,[可以看作是一个保留字 in ,[ -t ]它实际上不同于var=-t; [ "$var" ], or from ""[ -t ]or cmd='['; "$cmd" -t ]。这是为了保持向后兼容性,并且在重要的情况下仍然符合 POSIX。仅当-t它是文字时才在此处作为运算符,并ksh93检测到您正在调用该[命令。 ² ksh 添加了一个[[...]]条件表达式运算符,它有自己的语法解析规则(以及它自己的一些问题)来解决这个问题(在其他一些 shell 中也有,但有一些不同)。 ³ 除非在参数扩展时不调用 split+glob,但zsh仍然可以进行空删除,或者在全局禁用 split+glob 时在其他 shell 中使用set -o noglob; IFS= JdeBP 2018-12-22T14:25:01+08:002018-12-22T14:25:01+08:00 人们经常将前缀归因于空字符串的问题,但这不是原因。问题是一个非常简单的问题:变量的扩展可能是test's 运算符之一,突然将二进制相等测试变成了不同的表达式。 该命令在大多数平台上的最新实现避免了表达式解析器中的前瞻陷阱,防止解析器将二元运算符的第一个操作数识别为操作数以外的任何内容,只要有足够的标记成为二元运营商当然:% 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 %
这里要理解的重要一点是,在大多数 shell 中,
[
它只是一个由 shell 解析的普通命令,就像任何其他普通命令一样。然后 shell 调用带有参数列表的
[
(akatest
) 命令,然后将[
它们解释为条件表达式。那时,这些只是一个字符串列表,并且关于哪些字符串是由某种形式的扩展产生的信息会丢失,即使在那些
[
内置的 shell 中(这些天都是类似 Bourne 的 shell)。该
[
实用程序过去很难分辨它的哪些参数是运算符,哪些是操作数(运算符工作的东西)。语法本质上是模棱两可的,这并没有帮助。例如:[ -t ]
曾经是(并且仍然在某些 shell/[
s 中)来测试 stdout 是否是终端。[ x ]
是[ -n x ]
: 测试是否x
是非空字符串的缩写(所以你可以看到与上面的冲突)。[
s 中,-a
并且-o
可以是一元的([ -a file ]
对于可访问的文件(现在替换为[ -e file ]
),[ -o option ]
是否启用了该选项?)和二元运算符(and和or)。同样,! -a x
可以是and(nonempty("!"), nonempty("x"))
或not(isaccessible("x"))
。(
,)
并!
添加更多问题。在 C 或 等普通编程语言
perl
中,在:$a
不可能将or的内容$b
作为运算符,因为条件表达式在它们之前被解析$a
并被$b
扩展。但在贝壳中,在:shell首先扩展变量²。例如,如果
$a
contains(
和$b
contains ,则)
所有[
命令看到的是[
、(
、=
和参数。这是否意味着(在词法上相等)或(是非空字符串)。)
]
"(" = ")"
(
)
( -n = )
=
历史实现(
test
出现在 70 年代末期的 Unix V7 中)过去常常失败,即使只是因为它们处理参数的顺序并不模棱两可。在 PDP11 模拟器中使用版本 7 Unix:
大多数 shell 和
[
实现已经或曾经遇到过这些问题或其变体。今天有bash
4.4:POSIX.2(于 90 年代初发布)设计了一种算法,当以最常见的使用模式(例如仍未指定)传递最多 4 个参数(除了和)时,该算法将使
[
' 的行为明确和确定。它弃用了, ,和, 并删除了没有操作数。确实在2.0中实现了该算法(或至少尝试过) 。[
]
[ -f "$a" -o "$b" ]
(
)
-a
-o
-t
bash
bash
因此,在符合 POSIX 的
[
实现中,[ "$a" = "$b" ]
保证比较内容$a
和$b
是否相等,无论它们是什么。没有-o
,我们会写:也就是说,调用
[
两次,每次使用少于 5 个参数。但是所有
[
实现都需要相当长的时间才能变得合规。bash
's 直到 4.4 才符合要求(尽管最后一个问题是[ '(' ! "$var" ')' ]
没有人会在现实生活中真正使用它)Solaris 10 和更早版本,
/bin/sh
它不是 POSIX shell,但 Bourne shell 仍然存在以下问题[ "$a" = "$b" ]
:[ "x$a" = "x$b" ]
由于没有以 .[
开头的运算符,因此使用可以解决该问题x
。另一种选择是使用case
:(引用是必要的
$b
,而不是围绕$a
)。无论如何,它不是空值,也从来不是空值。人们在忘记引用变量时会遇到空值问题
[
,但这不是问题[
。默认值为
$IFS
:这是对是否为非空字符串的测试
=
,x
但没有任何前缀会有所帮助³[ x$a = x$b ]
仍然会:[ x = x-o x ]
这会导致错误,并且可能会变得更糟,包括 DoS 和带有其他值的任意命令注入,例如bash
:正确的解决方案是始终引用:
请注意,
expr
有类似(甚至更糟)的问题。expr
还有一个=
运算符,尽管它用于测试两个操作数在看起来像十进制整数时是否为相等的整数,或者在不是时排序相同。在许多实现中,
expr + = +
, orexpr '(' = ')'
orexpr index = index
不进行相等比较。expr "x$a" = "x$b"
可以解决它进行字符串比较,但前缀 anx
可能会影响排序(例如在具有排序元素的区域设置中x
),并且显然不能用于数字比较expr "0$a" = "0$b"
不适用于比较负整数。expr " $a" = " $b"
适用于某些实现中的整数比较,但不适用于其他实现(因为a=01 b=1
,有些会返回真,有些会返回假)。¹
ksh93
是一个例外。inksh93
,[
可以看作是一个保留字 in ,[ -t ]
它实际上不同于var=-t; [ "$var" ]
, or from""[ -t ]
orcmd='['; "$cmd" -t ]
。这是为了保持向后兼容性,并且在重要的情况下仍然符合 POSIX。仅当-t
它是文字时才在此处作为运算符,并ksh93
检测到您正在调用该[
命令。² ksh 添加了一个
[[...]]
条件表达式运算符,它有自己的语法解析规则(以及它自己的一些问题)来解决这个问题(在其他一些 shell 中也有,但有一些不同)。³ 除非在参数扩展时不调用 split+glob,但
zsh
仍然可以进行空删除,或者在全局禁用 split+glob 时在其他 shell 中使用set -o noglob; IFS=
人们经常将前缀归因于空字符串的问题,但这不是原因。问题是一个非常简单的问题:变量的扩展可能是
test
's 运算符之一,突然将二进制相等测试变成了不同的表达式。该命令在大多数平台上的最新实现避免了表达式解析器中的前瞻陷阱,防止解析器将二元运算符的第一个操作数识别为操作数以外的任何内容,只要有足够的标记成为二元运营商当然: