bash - 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 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
推荐阅读
- dll - 缺少 api-ms-win-core-winrt-string-l1-1-0.dll
- python - 将列表拆分为最少数量的集合的最快方法,枚举所有可能的解决方案
- java - 将“Togglz”功能切换库添加到 Spring Boot REST API
- mysql - 带有 UNIX 时间戳的 MySQL 表 - 按时间戳排序并计算一行与前一行之间的差异
- google-apps-script - Google App Script:如何显示带有模板化 HTML 链接的 google 驱动器文件
- google-sheets - 在 Google 表格中按逗号分隔列表中的单个条目进行过滤
- html - 超过链接时移除手形光标
- c - 重复的“lo0”范围,其中一个挂起尝试连接()
- firebase - Firebase-admin :以用户身份从 firestore 查询数据
- php - PHP 如何在整个页面中回显