trap 不捕获未处理异常,而是捕获信号;用 trap err(需 set -e 和 errtrace)记录错误详情,用 trap exit 结合标志变量判断退出状态,并可用 caller 增强调用栈追踪。

trap 本身不捕获“未处理的异常”(Linux Shell 没有传统编程语言中的异常机制),而是捕获指定的信号(如 INT、TERM、EXIT 等)。真正能模拟“捕获脚本意外退出”的方式,是利用 trap 监听 ERR 和 EXIT 信号,并结合 $?、$LINENO、调用栈等信息记录上下文。
用 trap ERR 记录命令失败的详细信息
ERR 是 Bash 内置信号,在任意命令以非零状态退出时触发(需开启 set -o errtrace 保证函数内也生效)。这是最接近“捕获错误”的方式:
- 必须先启用
set -e或手动检查返回值,否则错误不会中断执行,ERR也不一定触发; - 搭配
set -o errtrace,确保在函数、子 shell 中的错误也能被捕获; - 在 trap 处理函数中,可用
$?获取上一命令退出码,$LINENO获取出错行号,$BASH_COMMAND获取实际执行的命令。
示例:
#!/bin/bash
set -e -o errtrace
<p>log_error() {
local exit_code=$?
local line=$LINENO
local cmd="$BASH_COMMAND"
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR at line $line: '$cmd' (exit $exit_code)" >> /var/log/myscript.log
}</p><p>trap log_error ERR</p><p>echo "开始执行"
false # 这里会触发 ERR
echo "这行不会执行"
</p>用 trap EXIT 统一收尾并检查最终状态
EXIT 在脚本退出前总会触发(无论成功或失败),适合做清理和最终日志归档。但它无法区分是否出错——需配合全局变量或临时文件标记错误状态:
- 定义一个标志变量(如
has_error=0),在ERR处理函数中设为1; - 在
EXIT的 trap 函数中读取该变量,决定是否记录“异常退出”; - 注意:
EXITtrap 中的$?是脚本整体退出码,不是上一条命令的。
示例补充:
has_error=0
<p>log_error() {
has_error=1</p><h1>... 同上记录逻辑</h1><p>}</p><p>log_exit() {
if [[ $has_error -eq 1 ]]; then
echo "[$(date)] SCRIPT ABORTED with error" >> /var/log/myscript.log
else
echo "[$(date)] SCRIPT FINISHED successfully" >> /var/log/myscript.log
fi
}</p><p>trap log_error ERR
trap log_exit EXIT
</p>增强可观测性:记录调用栈和上下文
仅靠行号有时不够,特别是多层函数调用时。可使用 caller 命令获取调用栈:
-
caller 0返回当前出错位置(文件+行号+函数名); -
caller 1返回上一层调用位置,依此类推; - 配合
declare -p或env可选记录关键变量或环境(注意敏感信息脱敏)。
改进的 log_error 示例:
log_error() {
local exit_code=$?
local line=$LINENO
local cmd="$BASH_COMMAND"
local stack=""
local i=0
while caller $i >/dev/null; do
stack="$stack$(caller $i) | "
((i++))
done
echo "[$(date)] ERROR line $line: '$cmd' (exit $exit_code) | STACK: ${stack% | }" >> /var/log/myscript.log
}
注意事项与常见陷阱
-
trap不捕获被kill -9(SIGKILL)终止的情况——该信号无法被捕获或忽略; - 在子 shell(如
(...))中设置的 trap 不会继承到父 shell,反之亦然; - 避免在 trap 处理函数中使用可能失败的命令(如未加判断的
cp),否则可能引发递归 trap 或静默失败; - 日志路径要确保有写入权限,建议用绝对路径,并提前创建目录(如
mkdir -p /var/log)。











