为了理解这里发生了什么,您需要明确两件事情:
1. 条件语句的确切含义
在大多数语言中,有某种值可以被解释为真或假。这可能是布尔数据类型、整数(其中0为假,其他所有值都为真)或实现逐个类型的“真值”概念。
但是,Posix shell没有“真值”和“假值”。它们拥有的是可能成功或失败的语句。 “成功”和“失败”意味着什么主要取决于命令本身来确定,但bash本身将把某些行为分类为失败。例如,如果shell无法确定命令名称指的是什么,它将认为该命令已失败:
$ undefined_command && echo Yes || echo No
undefined_command: command not found
No
另外,如果一个命令因为信号(如分段错误)而终止,那么shell会将其视为失败:
$ ./segfault && echo Yes || echo No
Segmentation fault (core dumped)
No
但是许多命令即使错误不致命,也会发出失败信号(通过将它们的状态设置为非零值来实现)。例如,ls
如果其中任何一个文件名参数不存在(即使其他参数存在),它也会返回失败:
$ ls no_file exists && echo Yes || echo No
ls: cannot access 'no_file': No such file or directory
-rw-rw-r-- 1 rici rici 0 May 7 13:13 exists
No
如图所示,通常(虽然不总是)会在stderr中打印出一个错误消息,这个消息可以给出一些关于失败原因的提示。如果你想让自己感到困惑,通常可以抑制掉这个错误消息:
$ undefined_command 2>/dev/null && echo Yes || echo No
No
$ ls no_file exists 2>/dev/null && echo Yes || echo No
-rw-rw-r-- 1 rici rici 0 May 7 13:13 exists
No
这正是您在原始问题中所做的。如果我们不隐藏错误消息,问题就更加明显:
$ VAR=1xyz && [[ $VAR -eq $VAR ]] && echo Yes || echo No
bash: [[: 1xyz: value too great for base (error token is "1xyz")
No
$ VAR=xyz1 && [[ $VAR -eq $VAR ]] && echo Yes || echo No
Yes
换句话说,试图将字符串
1xyz
作为数字使用(因为
-eq
是
数值相等),会产生错误,这被视为失败。然而,字符串
xyz1
是一个有效的数字值。我们将在接下来的部分中看到为什么会这样。
但在此之前,我们需要注意,
[[...]]
是一个
命令(尽管它是Bash的扩展),而不是一些例外规则,即shell没有布尔值。像任何其他命令一样,
[[
可以成功或失败;其文档表明,如果将其参数评估为“true”,则它将成功。虽然在Bash中
[[
是一个内置命令--必须如此,因为它需要不同的参数解析规则--它仍然是一个命令,并且像
[
一样自己评估其参数。
2. 算术求值的特异性
算术求值发生在
$((...))
的展开中(在任何Posix shell中),以及在许多其他数字上下文中(在扩展Posix标准的Bash和其他shell中),包括算术条件
((...))
以及
[[...]]
和
$[[...]]
中的数字比较运算符的参数。在Bash中,算术求值还用于声明为算术(使用
declare -i
)和数组下标的变量赋值(不是关联数组)。
对于这个问题而言,算术求值最重要的特性是一个参数可以是shell变量的名称(仅名称,没有
$
)。在这种情况下,如果可能的话,该变量的值将转换为整数,并用作参数。虽然Posix标准不要求,但几乎所有的shell都会认为未定义的变量或值为空的变量具有数值0。但是,如果变量具有无法转换为数字的非空值,则会产生错误。
这与变量名前面带有
$
的情况略有不同。如果变量名前面有
$
,则在计算算术表达式之前,将像正常情况下一样进行普通参数替换。因此,在问题的第二个示例中,
VAR=xyz1 && [[ $VAR -eq $VAR ]] && echo Yes || echo No
参数扩展的结果将会是:
[[ xyz1 -eq xyz1 ]]
由于xyz1
(很可能)未定义,因此它将被视为将0与0进行比较,这是正确的(因此命令将成功)。如果xyz1
被定义为数字字符串,则会产生相同的结果,但如果其值无法转换为整数,则不会产生相同的结果:
$ VAR=xyz1 && xyz1=42 && [[ $VAR -eq $VAR ]] && echo Yes || echo No
Yes
$ VAR=xyz1 && xyz1=42z && [[ $VAR -eq $VAR ]] && echo Yes || echo No
bash: [[: 42z: value too great for base (error token is "42z")
No
Bash的数字计算规则实际上相当复杂(如果应用于不受信任的输入就不安全)。我不会详细介绍所有细节,但基本上bash将在算术计算中使用变量名称作为参数的“值”上执行算术计算。实际上,这允许递归替换变量名称,但它也允许您将变量的值设置为更复杂的内容:
$ x=y+7
$ y=35
$ echo $((x))
42
[ ... ]
不关心整数。 - axiac[[ $foo -eq $foo ]]
不是检查foo
是否为整数的安全方法。请参阅如何在bash中检查变量是否为数字?以了解一些可行的做法。 - Charles Duffy