我正在尝试执行以下代码:
set -euxo pipefail
yes phrase | make installer
其中Makefile
使用phrase
标准输入来创建安装程序文件。但是,此命令以错误代码 141 结束,这破坏了我的 CI 构建。这个例子可以简化为:
yes | tee >(echo yo)
从这里可以看出:当管道输出到三通时管道失败 (141) - 为什么?- 这个错误意味着管道消费者刚刚停止消耗输出 - 这在我的情况下完全没问题。
有没有办法抑制管道错误,并只从中获取返回码make installer
?
答案1
141 退出代码表示进程失败,并显示SIGPIPE
;yes
当管道关闭时会发生这种情况。要为您的 CI 屏蔽此错误,您需要使用类似的内容来屏蔽错误
(yes phrase ||:) | make installer
这将运行yes phrase
,如果失败,则运行:
并以代码 0 退出。这足够安全,因为yes
除了无法写入之外,没有太多原因导致失败。
要调试此类管道问题,最好的方法是查看PIPESTATUS
:
yes phrase | make installer || echo "${PIPESTATUS[@]}"
这将显示失败时管道所有部分的退出代码。然后可以适当地处理那些因退出代码 141 而失败的情况。特定错误代码的通用处理模式是
(command; ec=$?; if [ "$ec" -eq 141 ]; then exit 0; else exit "$ec"; fi)
(谢谢豪克拉格);这会运行command
,如果成功则以代码 0 退出command
,或者如果以代码 141 退出。其他退出代码按原样反映。
答案2
有没有办法抑制管道错误,并只从中获取返回码
make installer
?
如果您不想在子 shell 中运行整个命令管道(例如您希望能够设置变量),则可以使用已发布的其他优秀答案的替代解决方案:
yes phrase | make installer || { ec=$?; [ $ec -eq 141 ] && true || (exit $ec); }
# generic syntax:
cmd1 | cmd2 | cmd3 || { ec=$?; [ $ec -eq 141 ] && true || (exit $ec); }
这使用exit
子 shell 中的命令来完整保留命令管道中的原始退出代码(如果它不是 141)。因此,如果set -e
( set -o errexit
) 与 一起生效,它将达到预期效果set -o pipefail
。
我们可以使用一个函数来获得更简洁的代码,它允许在子 shell 技巧中使用 来return
代替:exit
handle_pipefails() {
# ignore exit code 141 from simple command pipes
# - use with: cmd1 | cmd2 || handle_pipefails $?
(( $1 == 141 )) && return 0
return $1
}
# then use it or test it as:
yes | head -n 1 || handle_pipefails $?
echo "ec=$?"
# then change the tested code from 141 to e.g. 999 in
# the function, and see that ec was in fact captured as
# 141
如果您想测试更复杂的管道中涉及的其他命令的退出代码,另一种方法是测试整个 PIPESTATUS:
handle_pipefails2() {
# ignore exit code 141 from more complex command pipes
# - use with: cmd1 | cmd2 | cmd3 || handle_pipefails2 "${PIPESTATUS[@]}"
for x in "$@"; do
(( $x == 141 )) || { (( $x > 0 )) && return $x; }
done
return 0
}
答案3
( yes phrase ; exit 0 ) | make installer
答案4
经过一番挖掘,我找到了这个答案:https://stackoverflow.com/questions/22464786/ignoring-bash-pipefail-for-error-code-141#comment60412687_33026977 基本上,使用:
set -euxo pipefail
yes phrase | make installer || (ec=$? ; if [ "$ec" -eq 141 ]; then exit 0; else exit "$ec"; fi)
SIGPIPE
只是从返回代码中过滤掉。