⚠This Post was generated by Qianwen3!

在 Shell 脚本中,trap 是一个非常重要的内置命令,用于捕获信号(signals)并执行自定义的处理逻辑。它常用于实现脚本的优雅退出、资源清理、异常处理等。


🔧 什么是信号(Signal)?

信号是操作系统发送给进程的一种通知机制,用于告知进程发生了某种事件。例如:

  • 用户按了 Ctrl+C → 发送 SIGINT 信号
  • 进程被强制终止 → SIGKILL
  • 脚本正常退出 → 触发 EXIT(伪信号)

Shell 脚本可以通过 trap 捕获这些信号,并在收到信号时执行指定的命令。


📌 trap 命令语法

1
trap 'command' SIGNAL [SIGNAL...]
  • command:当捕获到信号时要执行的命令(通常用引号包围)
  • SIGNAL:要捕获的信号名称或编号(如 INT, TERM, EXIT 等)

🚩 常见信号及其含义

信号名 编号 触发方式 / 含义
EXIT —— 脚本正常退出前触发(不是系统信号,是 shell 提供的)
SIGINT (INT) 2 用户按下 Ctrl+C
SIGTERM (TERM) 15 正常终止请求(可被 trap 捕获)
SIGKILL (KILL) 9 强制终止进程(不能被 trap 捕获
SIGSTOP 17/19/23 暂停进程(不能被 trap 捕获
SIGQUIT (QUIT) 3 用户按下 Ctrl+\
ERR —— 当命令执行失败(非零退出码)时触发(需启用)

⚠️ 注意:SIGKILLSIGSTOP 无法被 trap 捕获或忽略,因为它们是系统强制机制。


✅ 常用用法示例

1. 清理临时文件(使用 EXIT

1
2
3
4
5
6
7
8
tempfile=/tmp/mytmp.$$

trap 'rm -f "$tempfile"; echo "临时文件已清理"' EXIT

echo "生成数据..." > "$tempfile"
sleep 3
echo "完成。"
# 脚本结束前会自动执行 trap 中的命令

2. 捕获 Ctrl+CSIGINT

1
2
3
4
5
6
trap 'echo "请不要中断!"; exit 1' INT

for i in {1..10}; do
echo "处理中... $i"
sleep 1
done

运行时按 Ctrl+C,不会立即退出,而是输出提示信息后退出。


3. 忽略信号(防止中断)

1
2
3
4
trap '' INT   # 忽略 Ctrl+C
trap '' TERM # 忽略外部的 kill 命令(SIGTERM)

# 现在脚本不会被 kill 或 Ctrl+C 中断(但 kill -9 仍然有效)

' ' 表示空命令 → 忽略信号
''(两个单引号)表示空字符串 → 不执行任何操作


4. 同时捕获多个信号

1
2
3
4
5
6
trap 'echo "正在退出..."; cleanup; exit 1' INT TERM EXIT

cleanup() {
rm -f /tmp/tempfile
echo "清理完成。"
}

5. 使用 ERR 捕获错误

1
2
3
4
5
set -e  # 遇到错误立即退出(可选)
trap 'echo "命令第 $LINENO 行执行失败!"' ERR

false # 这个命令失败,会触发 trap
echo "这行不会执行"

需要确保没有 set +e 关闭错误检查。


6. 取消防trap:trap - SIGNAL

1
2
3
trap 'echo "退出中..."' EXIT
# ... 中间某些逻辑后
trap - EXIT # 取消之前的 EXIT trap

这会恢复该信号的默认行为(即不执行任何处理)。


🔍 查看当前所有 trap

1
trap

输出示例:

1
2
trap -- 'echo "退出..." ' EXIT
trap -- '' INT

⚠️ 注意事项

  1. 变量展开时机

    1
    2
    trap "echo $PWD" EXIT   # $PWD 在设置 trap 时就被展开了
    trap 'echo $PWD' EXIT # $PWD 在触发时才展开(推荐用单引号)
  2. 子 shell 不继承 trap
    ( ... ) 子 shell 中,trap 不会自动继承,除非显式设置。

  3. SIGKILL 和 SIGSTOP 无法被捕获

    1
    trap 'echo "不能阻止 kill -9"' KILL  # ❌ 无效
  4. 函数中设置 trap 是局部的吗?

    • 在函数中使用 trap 会影响整个脚本。
    • 但可以结合 localtrap 保存/恢复状态(高级用法)。

🧩 实际应用场景

✅ 场景:安全删除临时目录

1
2
3
4
5
6
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"; echo "临时目录已删除"' EXIT

# 使用 TMPDIR
echo "hello" > "$TMPDIR/data.txt"
# 脚本无论怎么退出都会清理 TMPDIR

✅ 场景:防止误中断

1
2
3
4
trap 'echo "安装中,请勿中断!"; exit 1' INT TERM
echo "开始安装..."
sleep 10
echo "安装完成。"

✅ 总结

写法 作用
trap 'cmd' EXIT 脚本退出前执行 cmd
trap 'cmd' INT 捕获 Ctrl+C
trap 'cmd' TERM 捕获 kill 命令
trap 'cmd' ERR 命令失败时执行(需配合 set -e
trap '' INT 忽略中断信号
trap - INT 恢复默认处理(取消 trap)
trap 显示当前所有 trap

📌 最佳实践建议

  • 使用 trap ... EXIT 来清理资源(推荐)
  • 使用单引号避免变量过早展开
  • 重要脚本中启用 ERREXIT trap 提高健壮性
  • 不要试图捕获 SIGKILLSIGSTOP(徒劳)