当条件发生错误时,“set -e”不会终止脚本

Chr*_*ski 8 shell bash shell-script error-handling

以下脚本存在某种语法错误或错误:

#!/usr/bin/env bash
set -euo pipefail

if [ ! -f /custom.log]; then
  echo "test"
fi
abcxyz
Run Code Online (Sandbox Code Playgroud)

脚本失败,输出为:

./test.sh: line 4: [: missing `]'
./test.sh: line 7: abcxyz: command not found
Run Code Online (Sandbox Code Playgroud)

我不关心如何修复此脚本,但是如果遇到此错误,我该如何阻止脚本继续运行?我原以为set -e会强制执行这种行为。

Sté*_*las 12

set -e不触发对未能被用作条件比如在条件部分的命令if/ while/until构建体或在左边的一个||&&或在功能,子shell,乳源文件,eval编码被在这些条件下被调用。

如果是这样,那么:

if [ ! -f /custom.log ]; then
Run Code Online (Sandbox Code Playgroud)

如果/custom.log是常规文件,[则会退出脚本,然后也会以非零退出状态退出。

如果不满足测试条件,并且存在语法错误(但并非所有语法错误,例如,不是 in )[bashshell(和大多数其他实现)的内置命令会以1状态退出。POSIX 要求退出状态大于 1 以防出错2[ -v 'a[+]' ]

因此,如果命令以大于 1 的代码退出,则您可以选择退出脚本,无论它是否在条件中使用,例如:

shopt -s extdebug # make sure the DEBUG trap propagates to subshells
trap '(($?>1 && (ret=$?))) && exit "$ret"' DEBUG
[ -f / ] || echo / not a regular file # OK
[ -f /] || echo was a syntax error # causes an exit, not output
echo not reached
Run Code Online (Sandbox Code Playgroud)

请注意,您不能ERR为此使用陷阱,因为ERR陷阱仅在与触发退出的条件相同的条件下运行set -e

现在,请注意其影响。例如,这将导致:

if grep -qs pattern /file; then
  echo pattern was found in /file
fi
Run Code Online (Sandbox Code Playgroud)

如果/file不存在或不可读,则退出,因为grep在这种情况下返回 2 状态,即使使用-s,其意图显然是忽略这些情况。

因此,您需要注意在哪些条件下您在条件中使用的命令可能会以大于 1 的状态退出。要解决这些问题,您需要类似以下内容:

if sh -c 'grep -sq pattern / file || exit 1'; then...
Run Code Online (Sandbox Code Playgroud)

你可以限制在退出状态大于出口比1[test有类似的命令:

unset -v previous_BASH_COMMAND
trap '
  case $previous_BASH_COMMAND in
    ("[ "* | "test "*) (($?>1 && (ret=$?))) && exit "$ret"
  esac
  previous_BASH_COMMAND=$BASH_COMMAND' DEBUG
Run Code Online (Sandbox Code Playgroud)

这有一些限制。在

echo x
([ -f/]; echo y)
Run Code Online (Sandbox Code Playgroud)

这将导致子外壳退出,但不会导致父外壳退出,因为$previous_BASH_COMMAND尚未在那里设置。在:

[ -f / ] && echo a regular file
(grep -qs foo /file && echo foo in /file)
echo here
Run Code Online (Sandbox Code Playgroud)

shell 将在运行时退出echo here,因为$? would be 2 和$previous_BASH_COMMANDwas [ -f / ]

在任何情况下,像

[ -f /] | cat
export var="$([ -f /])"
Run Code Online (Sandbox Code Playgroud)

无法检测到退出状态,因为退出状态不会传播到父 shell 进程(pipefail第一种情况下的选项除外)。

现在,我不确定在运行时添加这种(脆弱的)检测是否值得,因为在开发时(编写和测试脚本时)很容易检测到错误。