要理解这里发生了什么,您需要明确两件事:
1. 条件结构的确切含义
在大多数语言中,都有某种可以解释为 true 或 false 的值。这可能是布尔数据类型、整数(其中 0 为假,其他一切为真)或按类型实现的某种“真实性”概念。
但 Posix shell 没有“true”和“falsey”值。他们所拥有的是可能成功或失败的陈述。 “成功”和“失败”的含义主要由命令本身来确定,但 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
is numericequal) 会产生错误,该错误被视为失败。然而,字符串xyz1
是一个有效的数值。我们将在下一节中了解为什么会出现这种情况。
但在我们开始之前,我们需要注意的是[[ ... ]]
is a command(尽管是 bash 扩展),shell 没有布尔值这一规则也不例外。与任何其他命令一样,[[
可以成功也可以失败;它的文档表明,如果它将其参数评估为“true”,则它会成功。虽然在bash中[[
是一个内置命令——必然如此,因为它需要不同的参数解析规则——它仍然是一个命令,并且它本身评估其参数,就像[
.
2. 算术评估的特殊性
算术评估发生在扩展中$(( ... ))
(在任何 Posix shell 中),以及许多其他数字上下文(在 Bash 和扩展 Posix 标准的其他 shell 中),包括算术条件(( ... ))
以及内部数字比较运算符的参数[[ ... ]]
and $[[ ... ]]
。在 bash 中,算术求值还用于对声明为算术的变量进行赋值(使用declare -i
) 以及数组的下标(不是关联数组)。
就这个问题而言,算术求值最重要的特征是参数可以是 shell 变量的名称(只是名称,没有$
)。在这种情况下,如果可能,该变量的值将转换为整数,并用作参数。虽然 Posix 标准没有要求,但几乎所有 shell 都会将未定义的变量或值为空的变量视为数值 0。但是,如果该变量具有无法转换为数字的非空值,则产生错误。
这与变量名前面带有 a 的情况略有不同$
。如果变量名前面有$
,那么普通参数替换将正常发生在计算算术表达式之前。因此,在问题的第二个例子中,
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 会对value其名称在算术求值中用作参数的变量。实际上,这允许变量名称的递归替换,但它也允许您将变量的值设置为更复杂的值:
$ x=y+7
$ y=35
$ echo $((x))
42