首页 > 解决方案 > 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都不是整数。

标签: bash

解决方案


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

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 没有布尔值的规则的一些例外。像任何其他命令一样,[[可以成功或失败;它的文档表明,如果将其参数评估为“真”,则它会成功。尽管在 bash[[中是一个内置命令——必然如此,因为它需要不同的参数解析规则——但它仍然是一个命令,它自己评估它的参数,就像[.

2.算术评估的特性

算术评估发生在$(( ... ))(在任何 Posix shell 中)的扩展中,以及在许多其他数字上下文中(在 Bash 和其他扩展 Posix 标准的 shell 中),包括算术条件(( ... ))和数字比较运算符的参数[[ ... ]]$[[ ... ]]. 在 bash 中,算术评估也用于对声明为算术 (with 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 将对变量的值执行算术评估,该变量的名称用作算术评估中的参数。实际上,这允许递归替换变量名,但它也允许您将变量的值设置为更复杂的值:

$ x=y+7
$ y=35
$ echo $((x))
42

推荐阅读