如何在保留set -e的同时捕获函数的退出码?

编程语言 2026-07-12

关于 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反模式,一旦去除,它将使问题完全消失:

  1. 使用 set -e
  2. 捕获 $? 的值

关于 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

或如 Ed-Morton noted:

就像在另一个需要存储退出状态的情形一样,我有一个命令 doY,需要在执行另一个命令 doXdoX 成功后再执行它。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
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章