尽管存在语法错误,Shell 脚本仍返回 0 exit_status

尽管存在语法错误,Shell 脚本仍返回 0 exit_status

考虑这个脚本:

#!/bin/sh

foo=1
if [[ ! -z $foo ]]; then
    echo abc
fi

它使用 Bash 语法 [[ ... ]],当我在 Ubuntu (dash) 上使用默认 shell 运行它时,它不起作用(如预期)。然而,它的返回码仍然为零。

$ ./tmp.sh
./tmp.sh: 4: ./tmp.sh: [[: not found
$ echo $?
0

如果我不能依赖退出代码,如何检测脚本中的此类错误?

答案1

我首先解释一下为什么会出现这种情况。POSIX Shell 命令语言规范 说:

if 命令的退出状态应是已执行的 then 或 else 复合列表的退出状态,如果没有执行,则为零。

因为在你的情况下,then部分没有执行,并且没有else 退出状态为 0。如果你使用 Bash 运行此脚本,它也将是 0,如下man bash所示:

   if list; then list; [ elif list; then list; ] ... [ else list; ] fi

          The if list is executed.  If its exit status is zero,
          the then list is executed.  Otherwise, each elif list is
          executed in turn, and if its exit status is zero, the
          corresponding then list is executed and the command
          completes.  Otherwise, the else list is executed, if
          present.  The exit status is the exit sta‐ tus of the
          last command executed, or zero if no condition tested
          true.

如果我不能依赖退出代码,如何检测脚本中的此类错误?

我能想到的有两种方法:

  • 如果您可以修改脚本,请将else部分内容添加到 if 构造中:

      #!/bin/sh
    
      foo=1
      if [[ ! -z $foo ]]; then
          echo abc
      else
          echo not true
          exit 1
      fi
    
  • 如果您从某人那里得到 if 并且您不愿意修改它,请在sh模式下使用 shellcheck 静态分析器来查找代码中可能的错误并将其报告给作者:

      $ shellcheck -s sh dash-exit-status.sh
    
      In dash-exit-status.sh line 4:
      if [[ ! -z $foo ]]; then
         ^-------------^ SC2039: In POSIX sh, [[ ]] is undefined.
            ^-- SC2236: Use -n instead of ! -z.
    
      For more information:
        https://www.shellcheck.net/wiki/SC2039 -- In POSIX sh, [[ ]] is undefined.
        https://www.shellcheck.net/wiki/SC2236 -- Use -n instead of ! -z.
    

基本上,这是一个漏洞对我来说,不应该在应该执行的脚本中使用非 POSIX 功能,这些功能/bin/sh 可能但不一定是 Bash 的符号链接。

答案2

请注意,正如 @muru 评论的那样,就 Dash 而言,这不是语法错误。与和are[一样,对于 shell 来说不是一个特殊字符。只是一个常规命令,例如,只是有一个有趣的名称。在 Bash/Ksh/Zsh 中是类似 的关键字,并且关键字是 shell 语法的一部分,但至关重要的是,在 shell 中;([echo[[if支持它,[[将被视为另一个命令。

由于 Dash 不支持[[,因此在PATH,并且查找失败。作为Arkadiusz Drabczyk 的回答说,这里的结果是退出状态为零。如果这是一个实际的语法错误,shell 将立即以非零状态退出。

如果我不能依赖退出代码,如何检测脚本中的此类错误?

嗯,从技术上来说,你可以...

#!/bin/dash
[[ $1 == "foo" ]]
case $? in
    0)    echo "the first argument was 'foo'";;
    1)    echo "the first argument was something else";;
    127)  echo "[[ not supported";;
    *)    echo "there was some other error";;
esac

或者同样的 withret=$?和一系列if-elifwith 条件反对$ret。这样做有点尴尬,而且不是最好的解决方案,但如果您确实需要区分某些命令的不同错误代码,那么您就必须这样做。

如果您的目标是检测 Dash 与 Bash 的情况,您可以在脚本开头添加一个额外的测试来查看是否[[正常工作:

#!/bin/bash
if ! [[ a == a ]] 2>/dev/null; then
    echo "[[ not supported, exiting." >&2
    exit 1
fi

# ...

同样,您可能可以为您使用的所有其他非 POSIX 功能添加测试,但要测试它们全部当然可能会有点笨拙。

要准确地要求 Bash,而不是任何具有相同功能的 shell(Ksh 和 Zsh 与某些东西兼容,但不是全部),请$BASH_VERSION在脚本的开头进行检查:

#!/bin/bash
if [ -z "$BASH_VERSION" ]; then
    echo "Running only with Bash supported, exiting" >&2
    exit 1
fi

# ...

(当然可以手动设置变量,但用户最好知道他们在做什么。)

相关内容