Bash测试操作符[…-eq…]中的错误或功能?

Bash测试操作符[…-eq…]中的错误或功能?,bash,Bash,有人能解释一下这两者之间的区别吗 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 以及: 这是Bash中的bug还是特性 如果我不使用[…]而使用[…],我得到的结果是,$VAR在这两种情

有人能解释一下这两者之间的区别吗

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
以及:

这是Bash中的bug还是特性


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

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

1.条件结构的确切含义 在大多数语言中,有某种值可以解释为真或假。这可能是一个布尔数据类型、一个整数(其中0为false,其他所有内容均为true)或某种“真实性”概念,它是按类型实现的

但是Posix shell没有“truthy”和“false”值。他们所拥有的是可能成功或失败的陈述。“成功”和“失败”的含义主要取决于命令本身,但bash本身会将某些行为归类为失败。例如,如果shell无法识别命令名所指的内容,它将认为该命令失败:

$ undefined_command && echo Yes || echo No
undefined_command: command not found
No
此外,如果命令因信号(如分段故障)而终止,则外壳程序会将其视为失败:

$ ./segfault && echo Yes || echo No
Segmentation fault (core dumped)
No
但许多命令也会发出失败信号,即使错误不是致命的。(他们通过将状态设置为非零值来实现这一点。)例如,
ls
在任何文件名参数不存在时返回失败(即使其他参数不存在):

如图所示,通常(尽管不总是)会有一条错误消息打印到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”,则会成功[[是一个内置命令——当然是这样,因为它需要不同的参数解析规则——它仍然是一个命令,并且它会计算其参数本身,就像
[
一样

2.算术评估的特质 算术计算发生在扩展
$(…)
(在任何Posix shell中)和许多其他数值上下文(在扩展Posix标准的Bash和其他shell中)中,包括算术条件
(…)
[…]中数值比较运算符的参数
$[…]
。在bash中,算术求值还用于赋值给声明为算术的变量(使用
declare-i
)和数组(非关联数组)的下标

就这个问题而言,算术求值的最重要的特性是参数可以是shell变量的名称(只是名称,没有
$
)在这种情况下,如果可能的话,该变量的值被转换成一个整数,尽管它不是POSIX标准所要求的,但是几乎所有的壳都会考虑一个未定义的变量或一个变量,它的值是空的,具有数值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

请您详细说明一下,唯一的区别是变量内容,“1xyz”与“xyz1”,但[…-eq…]]的存在状态在两种情况下是不同的!我在这两种情况下都不希望是整数…呵呵!我误读了,我的坏!
[…]
不关心整数。@user9751447,如果某个东西不是整数而是变量名,它将被视为可能包含整数的变量名。空变量实际上包含整数0。也就是说,
[[$foo-eq$foo]]
不是检查
foo
是否为整数的安全方法。请参阅一些有效的实践。做得好,rici。在你和Charles之间,我知道我们会查到底。
$ 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
$ x=y+7
$ y=35
$ echo $((x))
42