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

vit*_*tix 10 shell dash exit-status

考虑这个脚本:

#!/bin/sh

foo=1
if [[ ! -z $foo ]]; then
    echo abc
fi
Run Code Online (Sandbox Code Playgroud)

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

$ ./tmp.sh
./tmp.sh: 4: ./tmp.sh: [[: not found
$ echo $?
0
Run Code Online (Sandbox Code Playgroud)

如果我不能依赖退出代码,如何在脚本中检测这种错误?

Ark*_*zyk 13

让我先解释一下为什么会发生这种情况。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.
Run Code Online (Sandbox Code Playgroud)

如果我不能依赖退出代码,如何在脚本中检测这种错误?

我能想到的有两种方法:

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

      #!/bin/sh
    
      foo=1
      if [[ ! -z $foo ]]; then
          echo abc
      else
          echo not true
          exit 1
      fi
    
    Run Code Online (Sandbox Code Playgroud)
  • 如果您从某人那里得到 if 并且您不愿意修改它,请在shmode 中使用 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.
    
    Run Code Online (Sandbox Code Playgroud)

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


ilk*_*chu 5

请注意,正如@muru 所评论的那样,就 Dash 而言,这不是语法错误。[不是外壳的特殊字符,;并且(是。[只是一个像 一样的普通命令echo,只有一个有趣的名字。在 Bash/Ksh/Zsh 中[[是一个关键字,如if,关键字是 shell 语法的一部分,但至关重要的是,在支持它的shell 中,[[将被视为另一个命令。

由于 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
Run Code Online (Sandbox Code Playgroud)

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

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

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

# ...
Run Code Online (Sandbox Code Playgroud)

同样,你也可能添加测试为所有其他非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

# ...
Run Code Online (Sandbox Code Playgroud)

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