如何在保留set -e的同时捕获函数的退出码?
关于 Bash get exit status of command when 'set -e' is active? 的回答展示了在set -e下捕获命令退出状态的不同方法。让我们来看看以下代码:
myfunc() {
false # set -e effect ignored
return "$1"
}
set -e
rc=0
myfunc 5 || rc=$?
echo "myfunc exited with $rc"
这都很好,但实际上 set -e 在函数执行期间会被禁用。 我不想这样。 set -e 在管道中被禁用,|| 和 ifs也会被禁用。
是否有一种方法,在保持set -e的效果以及在函数执行期间保持ERR trap值不变的前提下,能够捕获当前shell环境中函数的返回码?
我的尝试:
myfunc() {
trap 'set +e' RETURN
false
return "$1"
}
set -e
rc=0
myfunc 5
rc=$?
echo "myfunc exited with $rc"
或
# It looks that double ! ! just cancel each other out.
! ! myfunc 5
rc=$?
解决方案
你的问题源自两个shell反模式,一旦去除,它将使问题完全消失:
- 使用
set -e - 捕获
$?的值
关于 set -e,这是每个人都应该阅读并理解的一篇经典文章:
https://mywiki.wooledge.org/BashFAQ/105
始终优先使用显式的错误处理逻辑。set -e 含有太多微妙、依赖上下文的行为,值得使用的程度不足。如果你使用 set -e,你往往会花更多时间去对付它的行为,而不是直接编写正确的错误处理。
在shell脚本中检索 $? 的值几乎没有用处。
Shell不是通用的编程语言。它是一种胶水语言,设计用于对系统命令进行排序、实现自动化以及连接管道。
为什么显式检查 $? 往往不需要:
几乎所有的Unix命令在内部就已经进行了正确的错误处理,失败时会向 stderr(fd 2)输出有意义的信息。在绝大多数情况下,退出状态只是成功(0)或失败(非零)。因此,在shell脚本中最惯用、最干净、最有用的模式是:
the_command || exit
这会保留原始命令的错误信息,快速失败,并正确传播失败命令的退出状态。
只有在极少数情况下,当你确实需要检查或对具体退出码进行分支时,才有必要捕获 $?:
err=0
rare_case_command || err=$? # safely capture the exit code
[ "$err" -ge 3 ] && exit "$err" # exit immediately on critical errors (>= 3)
# Handle recoverable error
if [ "$err" -ne 0 ]; then
printf 'Non-critical failure of rare_case_command (exit code %d)\n' "$err" >&2
fi
就像在另一个需要存储退出状态的情形一样,我有一个命令
doY,需要在执行另一个命令doX且doX成功后再执行它。doX,然而不能在某些其他进程Foo正在运行时让它执行,所以我不得不停止Foo的运行,执行doX,然后在在执行doY之前不带条件地重新启动Foo,只有在doX成功,即stopFoo; doX; ret=$?; restartFoo; if (( "$ret" == 0 )); then doY ; fi时才执行doY。
我会将其改写为:
stopFoo
ret=0
doX || ret=$?
restartFoo # must run unconditionally, like a resource release
[ "$ret" -eq 0 ] && doY
并建议用一个更安全的try-finally实现来替代,它不需要捕获 $?:
if ( stopFoo && trap 'restartFoo' EXIT && doX ); then
doY
fi