如果 -eq 与 [[ ... ]] 中的非数字参数一起使用,如何使 bash 抛出错误?

How to make bash throw an error if -eq is used with non-numeric arguments inside [[ ... ]]?

我最近写了一个相当复杂的 bash 脚本,在测试过程中发现了一个不当行为。在分析过程中,我发现有一个拼写错误,我不小心在条件命令中使用了 -eq 而不是 ==,即我有类似

的东西
[[ "$var1" -eq "$var2" ]] || { ... }

而不是

[[ "$var1" == "$var2" ]] || { ... }

因为在这种情况下 var1var2 可以包含任意字符串,而不仅仅是整数,所以那个地方出现了可怕的错误。在命令行快速调查使问题更加清楚:

root@cerberus:~/scripts# [[ '0' -eq '0' ]] && echo Equal
Equal
root@cerberus:~/scripts# [[ '0' -eq '1' ]] && echo Equal
root@cerberus:~/scripts# [[ 'A' -eq 'B' ]] && echo Equal
Equal

最后一行让我非常惊讶。来自 bash manual(在页面末尾,强调我的):

arg1 OP arg2

OP is one of ‘-eq’, ‘-ne’, ‘-lt’, ‘-le’, ‘-gt’, or ‘-ge’. These arithmetic binary operators return true if arg1 is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to arg2, respectively. Arg1 and arg2 may be positive or negative integers. When used with the [[ command, Arg1 and Arg2 are evaluated as arithmetic expressions (see Shell Arithmetic).

由于上面代码的最后一行使用 -eq[[ 命令,根据上面的引用,'A''B' 被计算为算术表达式。令我惊讶的是,这并没有使 bash 抛出错误,因为显然没有合理的方法将 'A''B' 变成整数。

然后我试图找出 bash 在评估算术表达式时适用的规则。手册中的 The respective chapter 对一些边缘情况进行了陈述(例如,表达式的扩展是 nullunset),但是其中没有关于一个表达式的扩展是任意字符串的情况无法通过算术评估。 [注意:我知道我们不应该使用 link-only 参考文献,但相应的部分对于引用来说太长了。]

出于好奇,我试图找出像 'A' 这样的算术值实际计算的结果。结果证明这比预期的要困难,因为 bash 实际上拒绝对字符串进行“正常”算术扩展,即使这些字符串可以进行算术计算:

root@cerberus:~/scripts# echo $(( 0 ))
0
root@cerberus:~/scripts# echo $(( '0' ))
-bash: '0' : syntax error: operand expected (error token is "'0' ")
root@cerberus:~/scripts# echo $(( 'A' ))
-bash: 'A' : syntax error: operand expected (error token is "'A' ")

因为我想解决问题,而不是症状,所以我放弃了尝试找出 bash 在对任意字符串进行算术计算时适用的规则,或者它在执行时抛出错误的原因这在算术扩展期间,但不是在与 [[.

结合的算术评估期间

相反,我现在想知道 bash 中是否有一个选项使它在 any[=56 中抛出错误=] 必须对表达式进行算术运算的情况,但这是不可能的,因为该表达式不是算术运算。如果没有这样的选项,我想知道当这些运算符在 -eq-ne(等等里面)的参数的算术评估中是否可行 [[ ]].

如果你使用 [ ... ] 而不是 [[ ... ]],我想你会得到你想要的行为。给出以下函数:

check_equal() {
  if [ "$var1" -eq "$var2" ]; then
    echo equal
  else
    echo not equal
  fi
}

这些按预期工作:

$ var1=0 var2=0 check_equal
equal
$ var1=0 var2=1 check_equal
not equal

但是传递非数字参数会导致错误消息:

$ var1=A var2=0 check_equal
testnumbers.sh: line 2: [: A: integer expression expected
not equal

这里的缺点当然是您使用的是 [ ... ] 表达式,它在某些方面比 bash-only [[ ... ]] 表达式更脆弱。

[[ ... ]] 中的

-eq 将其操作数计算为算术表达式,即它们的行为类似于 ((...))。带或不带美元符号的变量都会被扩展,但如果它们的扩展是有效的标识符,则该名称的变量用于生成要比较的值。

#!/bin/bash
A=foo
B=bar
for foo in 0 1 ; do
    for bar in 0 1 ; do
        if [[ A -eq B ]] ; then
            echo "$A ($foo) eq $B ($bar)"
        else
            echo "$A ($foo) ne $B ($bar)"
        fi
    done
done

这个扩展是递归发生的

#!/bin/bash
a=1 b=a c=b d=c e=d f=e g=f h=g i=h j=i k=j l=k m=l
n=m o=n p=o q=p r=q s=r t=s u=t v=u w=v x=w y=x z=y
[[ z -eq 1 ]] && echo ok

递归限制为1024:

#!/bin/bash
(
    echo -n a=
    for var in {a..z}{a..z}{a..z} ; do
        echo $var
        echo -n $var=
    done
    echo 42
    echo '[[ a -eq 42 ]] && echo ok'
) | bash