trap 'cleanup' EXIT 必须放在脚本开头,确保变量声明和参数解析后立即注册,覆盖 HUP、INT、TERM、EXIT 信号;cleanup 中需 export 所用变量并加存在性判断,避免静默失败或误删,且须用真实信号验证生效。

trap 'cleanup' EXIT 必须放在脚本最开头
脚本意外退出(比如被 kill -TERM、SSH 断开触发 SIGHUP、或内部命令失败)时,只有已注册的 trap 才会执行。如果 trap 写在创建临时目录之后,那在它之前发生的中断就完全不会触发清理。
常见错误是把 trap 放在 mkdir 或 cp 后面——这等于没设。
- 正确做法:变量声明完、参数解析后立刻写
trap 'cleanup' EXIT -
EXIT不是真实信号,而是 shell 退出前的兜底事件,无论exit 0、exit 1还是被信号终止都会触发 - 别只绑
EXIT,高并发或服务类脚本建议加HUP INT TERM:trap 'cleanup' HUP INT TERM EXIT
cleanup 函数里不能用局部变量,必须 export
trap 触发时,cleanup 是在子 shell 中执行的,父 shell 的局部变量(比如 tmpdir)在子 shell 里为空。
不导出就删错路径、杀错进程,甚至静默失败。
- 所有 cleanup 需用的变量,如
TMPDIR、PID、LOCKFILE,定义后立刻export TMPDIR PID - 清理动作要带存在性判断:
[ -n "$TMPDIR" ] && rm -rf "$TMPDIR",避免rm -rf ""砸掉根目录(虽然现代 shell 有保护,但别赌) - 关子进程用
kill -TERM "$PID" 2>/dev/null || :,防止因 PID 不存在而让 cleanup 失败退出(set -e下会直接中止)
别在 cleanup 里做耗时或不可控操作
信号到达后,shell 会等 cleanup 返回才真正退出。如果 cleanup 卡住(比如无超时的 curl、ssh 或 wait),整个脚本就“假死”,无法被 kill -9 之外的任何方式终结。
- 上报状态改用
timeout 3s curl -s http://log/finish?pid=$$ || : - 避免
wait $PID,改用kill -0 $PID 2>/dev/null && kill -TERM $PID+ 短延时检查 - 删除大目录前可先
find "$TMPDIR" -type f -delete分步清空,降低阻塞风险
验证 trap 是否真生效,只能用真实信号测试
只跑一遍脚本看 echo "cleanup run" 没用。很多 trap 看似写了,但信号没传到、变量没导出、或子进程抢了信号,实际根本没执行。
上线前必须模拟生产中断场景:
- 后台启动:
./script.sh & pid=$! - 分别执行:
kill -INT $pid(Ctrl+C)、kill -TERM $pid(systemd stop)、kill -HUP $pid(SSH 断开) - 检查
ls /tmp/是否残留、ps -o pid,ppid,args | grep $pid是否还有子进程
最容易漏的是 SIGHUP —— 它不来自用户按键,却高频出现在运维场景中,不测等于没设。











