如果任何命令失败,则在末尾使用非零代码退出 shell 脚本

Exiting a shell-script at the end with a non-zero code if any command fails

我正在制作一个 shell 脚本,其中 运行 包含一系列测试,作为 CI 管道的一部分。我想 运行 所有测试(如果一个测试失败,我不想提前退出)。然后,在脚本的末尾,如果任何测试失败,我想 return 使用负退出代码。

如有任何帮助,我们将不胜感激。我觉得这将是一个非常常见的用例,但我无法通过一些研究找到解决方案。我很确定我不想 set -e,因为它提前退出。

我目前的想法是创建一个标志来跟踪任何失败的测试:

flag=0
pytest -s || flag=1
go test -v ./... || flag=1
exit $flag

这看起来很奇怪,并且喜欢做不必要的工作,但我是 bash 脚本的新手。我错过了什么吗?

一种可能的方法是通过 trapERR 捕获非零退出代码。假设您的测试不包含管道 | 并且只是 return 直接到 shell 启动的错误代码,您可以

#!/usr/bin/env bash

exitCodeArray=()

onFailure() {
    exitCodeArray+=( "$?" )
}

trap onFailure ERR

# Add all your tests here

addNumbers () {
    local IFS='+'
    printf "%s" "$(( $* ))"
}

在上述代码段之后的任意位置添加您的测试。因此,只要测试 return 是非零 return 代码,我们就会不断将退出代码添加到数组中。所以对于最后的断言,我们检查数组元素的总和是否为0,因为在理想情况下所有情况都应该return,如果它是成功的。我们重置

之前设置的 trap
trap '' ERR

if (( $(addNumbers "${exitCodeArray[@]}") )); then
    printf 'some of your tests failed\n' >&2
    exit -1
fi

我能想到的使用 less 代码的唯一方法是,如果 shell 有某种特殊的 all 复合命令,可能看起来像

# hypothetical all command
all do
  pytest -s
  go test -v ./...
done

其退出状态是所含命令退出状态的逻辑 or。 (类似的 any 命令会将其命令退出状态的逻辑 and 作为其自身的退出状态。)

缺少这样的命令,我将使用您当前的方法。您可以采用@melpomene 对 chk 函数的建议(我会在 之后调用 一个命令,而不是让它调用您的命令,以便它可以与任意 shell命令):

chk () { flag=$(( flag | $? )); }

flag=0
pytest -s; chk
go test -v ./...; chk
exit "$flag"

如果您不将它用于任何其他用途,您可以滥用 DEBUG 陷阱在每个命令之前更新 flag

trap 'flag=$((flag | $?))' DEBUG
pytest -s
go test -v ./...
exit "$flag"

(请注意,调试陷阱会在 之前 执行 shell 执行另一个命令,而不是在 之后 立即执行命令执行。唯一重要的可能是你希望陷阱在最后一个命令完成和 shell 退出之间触发,但它仍然值得注意。)

我投票赞成 Inian 的回答。陷阱似乎是完美的选择。

也就是说,您也可以通过使用数组来简化事情。

#!/usr/bin/env bash

testlist=(
  "pytest -s"
  "go test -v ./..."
)

for this in "${testlist[@]}"; do
  $this || flag=1
done

exit $flag

如果你想制作一个可以被多种工具使用的更通用的测试工具,你当然可以从另一个文件中获取数组的内容。哎呀,mapfile 可能是填充数组的好方法。