Bash trap 清理临时目录:保留原始退出码,让失败仍然可见
清理完成,不代表业务成功
一个导出脚本先写临时文件,再生成交付结果。中间命令失败后,清理函数顺利删除目录,如果最后却返回成功,调度系统就会把失败任务记成绿色。可靠清理需要同时回答两个问题:工作目录是否收回,以及调用者最终收到哪个退出码。这两个结果应分别验证,不能用“目录没了”替代业务验收。
GNU Bash 的 EXIT trap 会在 shell 退出时执行。下面把删除动作集中在这个出口,普通结束和显式失败共用一份清理逻辑。临时目录由 mktemp 直接创建,不先猜一个名字再创建文件;GNU 文档说明,后者会留下名称被其他进程抢占的窗口。示例面向 Bash 与 GNU 常用工具。
AI生成概念示意图,非真实界面
先保存状态,再执行任何清理命令
把代码保存为脚本,用 bash 执行即可,默认运行成功分支。清理函数第一条命令保存进入时的状态,随后撤下退出钩子,并在短暂清理期间忽略常见终止信号。目录变量先置空,再安装钩子,创建失败也能安全退出。所有目录参数都有引号,删除命令的双连字符用于结束选项解析。
#!/usr/bin/env bash
set -u
workdir=''
cleanup() {
local status=$?
trap - EXIT
trap '' HUP INT TERM
if [[ -n "$workdir" && -d "$workdir" ]]; then
if ! rm -rf -- "$workdir"; then
printf 'cleanup failed: %s\n' "$workdir" >&2
fi
fi
exit "$status"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
workdir=$(mktemp -d -- "${TMPDIR:-/tmp}/trap-demo.XXXXXXXX") || exit 70
printf 'workdir=%s\n' "$workdir"
printf 'temporary result\n' > "$workdir/result.txt" || exit 74
case "${1:-ok}" in
ok) printf 'work finished\n' ;;
fail) bash -c 'exit 23' || exit "$?" ;;
term) kill -TERM "$$" ;;
int) kill -INT "$$" ;;
hup) kill -HUP "$$" ;;
*) printf 'use: ok|fail|term|int|hup\n' >&2; exit 64 ;;
esac默认分支打印临时目录位置与完成提示,退出码应为零,打印出的目录应已不存在。再次运行时传入 fail,子命令返回二十三,脚本明确沿失败分支退出;目录仍应删除,退出码必须仍是二十三。请在运行后立即读取退出状态,先执行其他检查命令会覆盖你想观察的数值。
清理失败怎样报告,要提前约定
这里选择保留业务状态:即使删除失败,也只向标准错误报告路径,不覆盖最初退出码。因此业务成功但清理失败时仍返回零,这是一项明确策略,不是所有系统都应照搬。若临时材料必须删除才算完成,应另外制定清理失败的状态规则,同时保留原业务错误,避免只剩最后一个错误而丢失真正原因。
脚本没有依靠自动退出选项猜测失败是否会传播,而是在创建、写入和业务命令旁边明确处理。把它移入真实流水线时,要继续检查每个关键步骤,不能以为装好退出钩子就能发现所有失败。钩子负责退出后的收尾;某个失败被调用者忽略、程序继续运行时,它不会自动替业务决定何时终止。
信号测试和不可保证的情况
传入 term、int 或 hup,会向当前脚本自身发送对应信号,再通过指定的非零状态走统一出口。这些数字是示例选择的退出约定;显式退出与进程真的被信号终止,在父进程眼中并非完全相同。信号分支应分别核对目录消失和退出码,不要只检查打印文字是否出现。
GNU 手册还指出,shell 等待前台命令时,已设置信号的处理可能延后到命令结束。本例没有长期子进程;加入下载器或后台工作后,应另行设计停止与等待顺序,不能先删掉它们仍在写的目录。强制杀死与机器断电也不能靠钩子保证收尾。实际验收应包含异常残留检查,并只清理由本任务拥有的目录。
还可让写入步骤主动失败,检查脚本返回约定的写入错误码,并确认临时目录仍被统一出口收回。这样才能覆盖工作尚未真正开始就失败的情况。


