Bash 测试运算符 [[ .. -eq .. ]] 中的错误或功能



有人可以解释一下两者之间的区别吗:

VAR=1xyz && [[ $VAR -eq $VAR ]] 2>/dev/null && echo "Yes, VAR = $VAR is an integer" || echo "No, VAR = $VAR is NOT an integer"
No, VAR = 1xyz is NOT an integer

和:

VAR=xyz1 && [[ $VAR -eq $VAR ]] 2>/dev/null && echo "Yes, VAR = $VAR is an integer" || echo "No, VAR = $VAR is NOT an integer"
Yes, VAR = xyz1 is an integer

这是 Bash 中的错误或功能吗?

如果我不使用[[ ... ]][ ... ],我得到的预期结果是$VAR在这两种情况下都不是整数。

要了解这里发生的事情,您需要清楚两件事:

1. 条件构造的确切含义

在大多数语言中,存在某种可以解释为真或假的值。这可能是一个布尔数据类型,一个整数(其中0是假的,其他一切都是真的)或一些逐种实现的"真实性"概念。

但是Posix贝壳没有"真"和"假"值。他们拥有的是可能成功或失败的陈述。"成功"和"失败"的含义主要取决于命令本身,但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返回 failed:

$ 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 中)和许多其他数字上下文中(在 Bash 和其他扩展 Posix 标准的 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

最新更新