如果任何命令失败,则在最后以非零代码退出 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】:
一种可能的方法是通过trap
和ERR
捕获非零退出代码。假设您的测试不包含管道 |
并且只是将错误代码直接返回到启动的 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 以非零退出代码失败
异常:Gradle 任务 assembleDebug 失败,退出代码 1 以非零退出值 1 完成