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

时间:2018-07-06 07:33:27

标签: bash shell sh exit

我正在制作一个外壳脚本,该外壳脚本作为CI管道的一部分运行一堆测试。我想运行所有测试(如果一项测试失败,我不想早日退出)。然后,在脚本末尾,如果任何测试失败,我想返回一个负的退出代码。

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

我当前的想法是创建一个标记以跟踪任何失败的测试:

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

这似乎很奇怪,并且比需要的工作还多,但是我对bash脚本还是陌生的。我想念什么吗?

3 个答案:

答案 0 :(得分:4)

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

#!/usr/bin/env bash

exitCodeArray=()

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

trap onFailure ERR

# Add all your tests here

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

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

之前的trap
trap '' ERR

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

答案 1 :(得分:1)

我可以想象使用 less 代码的唯一方法是,如果外壳具有某种特殊的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在执行命令之前而不是 之后立即执行另一个命令。唯一重要的是您希望陷阱在最后一个命令完成和外壳退出之间触发,但是仍然值得注意。)

答案 2 :(得分:1)

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

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

#!/usr/bin/env bash

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

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

exit $flag

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