代码之家  ›  专栏  ›  技术社区  ›  Binarus

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

  •  0
  • Binarus  · 技术社区  · 5 年前

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

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

    而不是

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

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

    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操作arg2

    OP是-eq、-ne、-lt、-le、-gt或-ge之一。如果arg1等于或不等于,这些算术二元运算符将返回true 至、小于、小于或等于、大于或大于或 分别等于arg2。Arg1和arg2可以是正的或负的 整数。 当与[[命令一起使用时,会计算Arg1和Arg2 作为算术表达式(参见Shell算术)。

    由于上面代码的最后一行使用 -eq [[ 命令, 'A' 'B' 根据上述引文,它们被评估为算术表达式。令我惊讶的是,这并没有使bash抛出错误,因为显然没有合理的方法来翻转 A. B 转换为整数。

    然后,我试图找出bash在计算算术表达式时应用的规则。 The respective chapter 在手册中,对一些边缘情况(例如表达式的展开式为 null unset ),但其中没有任何关于一个表达式的展开是一个无法算术计算的任意字符串的情况。[注意:我知道我们不应该只使用链接引用,但相应的章节太长,无法引用。]

    出于好奇,我试图找出这样的算术值 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中是否有一个选项可以让它在 任何 它必须算术地计算表达式,但这是不可能的,因为该表达式不是算术的。如果没有这样的选择,我想知道这是否可能,至少对于参数的算术计算来说是这样 -eq , -ne (等等)当这些运算符在内部使用时 [[ ]] .

    0 回复  |  直到 5 年前
        1
  •  1
  •   choroba    5 年前

    -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
    
        2
  •  1
  •   larsks    5 年前

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

    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更脆弱 [[ ... ]] 表达。