如果任何命令失败,则在末尾使用非零代码退出 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 脚本的新手。我错过了什么吗?
一种可能的方法是通过 trap
和 ERR
捕获非零退出代码。假设您的测试不包含管道 |
并且只是 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
可能是填充数组的好方法。
我正在制作一个 shell 脚本,其中 运行 包含一系列测试,作为 CI 管道的一部分。我想 运行 所有测试(如果一个测试失败,我不想提前退出)。然后,在脚本的末尾,如果任何测试失败,我想 return 使用负退出代码。
如有任何帮助,我们将不胜感激。我觉得这将是一个非常常见的用例,但我无法通过一些研究找到解决方案。我很确定我不想 set -e
,因为它提前退出。
我目前的想法是创建一个标志来跟踪任何失败的测试:
flag=0
pytest -s || flag=1
go test -v ./... || flag=1
exit $flag
这看起来很奇怪,并且喜欢做不必要的工作,但我是 bash 脚本的新手。我错过了什么吗?
一种可能的方法是通过 trap
和 ERR
捕获非零退出代码。假设您的测试不包含管道 |
并且只是 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
可能是填充数组的好方法。