精准自捕获需组合set -euo pipefail与set -E,封装ERR陷阱为含$BASH_COMMAND、${FUNCNAME[1]}、$?的错误处理器,并注意子shell陷阱继承问题。

用 trap 'echo "Error at line $LINENO"' ERR 捕获错误,确实能快速定位失败行号,但仅靠这一行远远达不到“精准自捕获”——它不显示错误命令、不保留上下文、不区分函数内外、也不阻止脚本继续执行(除非显式 exit)。要真正实现可靠、可追溯的异常捕获,需围绕 ERR 陷阱做系统性增强。
基础陷阱必须配合 set -e 和 -E 才生效
单独写 trap ... ERR 不会自动中断脚本。必须启用错误退出机制:
-
set -e:让任何非零退出状态的命令立即终止脚本(但默认不进入函数内的 ERR 陷阱) -
set -E:关键补充,使 ERR 陷阱在函数内部也触发(否则函数出错只退出函数,不触发 trap) - 推荐组合:
set -euo pipefail+set -E,覆盖变量未定义、命令失败、管道断裂等常见静默错误
单行 echo 不够,应封装为带上下文的错误处理器
直接 echo "Error at line $LINENO" 只输出行号,无法还原现场。建议定义函数并绑定到 ERR:
- 使用
$BASH_COMMAND获取实际执行的出错命令 - 用
${FUNCNAME[1]}显示上层调用函数名(便于追踪嵌套) - 记录
$?实际退出码,不同码常对应不同错误类型(如 127=命令未找到,130=Ctrl+C) - 示例:
err_handler() { local exit_code=$? echo "❌ ERROR at line $LINENO in ${FUNCNAME[1]:-main}: '$BASH_COMMAND' (exit $exit_code)" >&2 exit $exit_code } trap err_handler ERR
注意作用域与子 shell 的陷阱继承问题
trap 默认不传递给子 shell(如 (...) 或管道中的右侧命令),导致错误悄无声息:
- 避免用
(cmd1; cmd2)包裹关键逻辑,改用{ cmd1; cmd2; }保持同一 shell 环境 - 管道中若需捕获右侧错误,改用
set -o pipefail并确保整个管道作为单个命令被 trap 覆盖 - 用
trap -p验证当前作用域是否已正确设置 ERR 陷阱
搭配 EXIT 和 DEBUG 陷阱提升可观测性
ERR 只响应失败,完整调试还需辅助信号:
-
trap 'echo "✅ EXIT at line $LINENO, status $?"' EXIT:确认正常/异常退出路径,验证是否真被 ERR 拦截 -
trap 'echo "? DEBUG: $BASH_COMMAND (line $LINENO)"' DEBUG:逐行打印执行命令(慎用于生产,性能开销大) - 三者组合可构建“失败前最后几条命令+失败点+退出结果”的完整链路
不复杂但容易忽略。核心不是加一行 trap,而是让 trap 成为错误处理流程的起点——有上下文、可中断、跨作用域、可验证。


















