如果任何命令失败,则在最后以非零代码退出 shell 脚本

Posted

技术标签:

【中文标题】如果任何命令失败,则在最后以非零代码退出 shell 脚本【英文标题】:Exiting a shell-script at the end with a non-zero code if any command fails 【发布时间】:2018-07-06 07:33:27 【问题描述】:

我正在制作一个 shell 脚本,它作为 CI 管道的一部分运行一系列测试。我想运行所有测试(如果一项测试失败,我不想提前退出)。然后,在脚本结束时,如果任何测试失败,我想返回一个否定的退出代码。

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

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

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

这看起来很奇怪,而且工作量比必要的要多,但我是 bash 脚本的新手。我错过了什么吗?

【问题讨论】:

您所拥有的是一个好方法。什么是失败的?将 $flag 替换为 flag 测试如何运行?在循环中? 使用... || flag=1(不需要$ 必须为脚本中的每一行指定这一点似乎过于冗长。目前,我只运行了几个命令,但如果将来添加更多测试,恐怕这将无法很好地扩展。 感谢您的反馈!我进行了编辑。我没有循环运行测试。 【参考方案1】:

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

#!/usr/bin/env bash

exitCodeArray=()

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


trap onFailure ERR

# Add all your tests here

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

在上述 sn-p 之后的任何位置添加您的测试。因此,每当测试返回非零返回代码时,我们都会将退出代码添加到数组中。所以对于最后的断言,我们检查数组元素的总和是否为0,因为在理想情况下,如果成功,所有情况都应该返回它。我们重置了之前设置的trap

trap '' ERR

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

【讨论】:

这里为什么需要数组?为什么不在onFailure 中使用一个简单的整数来总结退出代码? @codeforester:很好的收获!如果我没记错的话,我想要数组,这样如果需要,我们可以打印数组以从一开始就查看各个测试的状态,并将每个测试与其退出代码映射。如果不需要,可以使用变量【参考方案2】:

我能想象使用 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 退出之间触发,但仍然值得注意。)

【讨论】:

假设all 命令是逻辑与,any 命令是或不是更有意义吗?还是我读错了? 我用all 表示“所有都必须成功”,而不是“所有都必须失败”。例如,四个退出状态 0、0、1 和 0 应该产生一个 1(如果任何命令失败,块就会失败)。【参考方案3】:

我投票支持 Inian 的回答。陷阱似乎是最好的方法。

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

#!/usr/bin/env bash

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

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

exit $flag

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

【讨论】:

以上是关于如果任何命令失败,则在最后以非零代码退出 shell 脚本的主要内容,如果未能解决你的问题,请参考以下文章

命令 PhaseScriptExecution 以非零退出代码 iOS 失败

撤销钥匙串中的所有证书后,命令 PhaseScriptExecution 以非零退出代码失败

Sencha 构建失败的指南针进程以非零代码 1 退出

异常:Gradle 任务 assembleDebug 失败,退出代码 1 以非零退出值 1 完成

Command CompileSwift 在 Xcode 10 中以非零退出代码失败 [重复]

命令 CompileAssetCatalog 失败,退出代码为非零