Bash:用 trap 统一清理临时目录并保留退出状态

9 次浏览1 条回复

脚本创建临时目录后,如果中途命令失败或收到终止信号,散落在各分支里的清理代码很容易漏执行。可以把清理集中到 EXIT trap,并让信号处理先转换成约定的退出码。

环境:Bash 4.4+;Linux,或已安装新版 Bash 的 macOS;需要 mktemprm

创建 cleanup-demo.sh

#!/usr/bin/env bash
set -Eeuo pipefail

workdir="$(mktemp -d)"

cleanup() {
  local status=$?
  trap - EXIT
  rm -rf -- "$workdir"
  printf 'cleaned %s (exit=%d)\n' "$workdir" "$status" >&2
  exit "$status"
}

trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

printf 'temporary data\n' > "$workdir/result.txt"
printf 'working in %s\n' "$workdir"

# 用 false 模拟中途失败;改成 true 可观察成功退出。
false

运行并检查退出码:

chmod +x cleanup-demo.sh
./cleanup-demo.sh
printf 'status=%d\n' "$?"

false 使脚本以 1 退出,cleanup 中进入函数时的 $? 就是该状态;先保存它,再删除临时目录,最后原样退出。trap - EXIT 用于避免函数内的 exit 再次触发同一个 trap。

false 攻成 true 后,清理仍会执行,但退出码为 0。收到 SIGINTSIGTERM 时则分别转换为常见的 130143,随后仍由 EXIT trap 完成同一套清理。实际脚本如果还要释放挂载点、停止子进程或删除多个资源,可以继续放进 cleanup,但应先保存状态码,并避免让某个清理失败覆盖原始结果。

这个模式很实用,不过示例中还有一个与“保留退出状态”直接相关的边界:脚本启用了 set -ecleanup 里的 rm 或后续 printf 一旦失败,函数可能在执行 exit "$status" 前终止,最终状态就不再是最初保存的值。若目标是无条件保留业务退出码,可以在保存后关闭 errexit,并单独报告清理错误:

cleanup() {
  local status=$?
  local cleanup_status

  trap - EXIT
  set +e

  rm -rf -- "$workdir"
  cleanup_status=$?
  if (( cleanup_status != 0 )); then
    printf 'cleanup failed for %s (status=%d)\n' \
      "$workdir" "$cleanup_status" >&2
  fi

  exit "$status"
}

环境仍是正文的 Bash 4.4+。另一种策略是只在原状态为 0 时把清理失败作为最终失败:(( status == 0 && cleanup_status != 0 )) && status=$cleanup_status。这样业务已经失败时保留业务状态,业务成功但资源未清理时也不会误报成功。