Linux Shell 进阶必备!trap 信号捕获完全指南(脚本异常处理 + 优雅退出)
1. 命令简介
trap 是 Bash 及其他类 Unix shell 的一个内置命令,用于在脚本或交互式 shell 会话中捕获指定的信号或事件。当这些事件发生时,shell 将执行用户预先定义的命令或函数。该命令是实现脚本优雅终止、资源清理和异常处理的 核心工具。
2. 语法格式
plaintext
trap [-lp] [command] [signal_spec ...]参数与格式说明:
- command:当捕获到指定信号时要执行的命令字符串或函数名。
- 若为 空字符串 '',表示忽略该信号。
- 若省略,则表示将信号恢复为其默认处理方式。
- signal_spec:一个或多个信号标识,可以是信号名称(如 SIGINT)或信号数字(如 2)。通常 SIG 前缀可以省略(例如 INT)。
结构图示:
plaintext
trap [选项] ‘执行的指令或函数’ 信号1 信号2 …3. 常用选项及说明
命令选项
表格
| 选项 | 说明 |
|---|---|
| -l 或 --list-signals | 列出系统支持的所有信号名称及其对应的数字编号。此列表与 kill -l 命令的输出相同。 |
| -p 或 --print | 显示当前 shell 中已为每个信号设置的 trap 命令。若指定了具体信号,则只显示这些信号的 trap 设置。 |
特殊 “伪信号”(事件)说明
trap 除了能捕获常规的进程信号,还能捕获一些 shell 特有的 “事件”,它们并非真正的操作系统信号:
- EXIT (或 0):在 shell 退出时触发(无论是正常结束还是因信号而终止)。常用于资源清理。
- ERR:当任何命令返回非零退出状态(即执行失败)时触发。与 set -e (errexit)选项结合使用效果显著。
- DEBUG:在每个简单命令、for、case、select 命令、算术 for 命令以及 shell 函数的第一条命令执行前触发。用于调试。
- RETURN:在 shell 函数执行完毕,或通过。或 source 执行的脚本结束时触发。
4. 示例用法
4.1 基本信号捕获与清理
捕获 Ctrl+C (SIGINT) 和终止信号 (SIGTERM),执行清理命令后退出。
bash
运行
#!/bin/bash trap "echo '正在清理临时文件...'; rm -f /tmp/mytemp.*; exit 1" INT TERM # 脚本主体 while true; do echo “运行中... 按 Ctrl+C 测试 trap” sleep 1 done4.2 使用 EXIT 陷阱实现可靠清理
无论脚本以何种方式退出,都确保执行清理操作。
bash
运行
#!/bin/bash WORKDIR=/tmp/test_ mkdir -p "$WORKDIR" # 设置 EXIT 陷阱 trap "echo '清理临时目录...'; rm -rf $WORKDIR" EXIT # 脚本主体操作 touch "$WORKDIR/data.log"4.3 忽略信号
使脚本在执行期间忽略 Ctrl+C (SIGINT) 中断。
bash
运行
#!/bin/bash trap '' INT echo “Ctrl+C 已被禁用。请等待5秒或使用 kill 命令终止。” sleep 5 echo “完成。”4.4 重置信号为默认行为
移除对信号的定制处理,恢复其默认动作。
bash
运行
#!/bin/bash trap '' INT # 先忽略 SIGINT echo “Ctrl+C 被忽略。” sleep 2 trap INT # 重置 SIGINT 为默认行为(终止进程) echo “Ctrl+C 已恢复可用。” sleep 24.5 使用 ERR 陷阱捕获错误
当脚本中任何命令执行失败时,触发自定义错误处理。
bash
运行
#!/bin/bash set -e # 出现错误时退出脚本 trap 'echo “错误发生在第 $LINENO 行”' ERR ls not_exist_file.txt4.6 列出所有可用信号
plaintext
trap -l4.7 显示当前已设置的陷阱
plaintext
# 显示所有已设置的陷阱 trap -p # 仅显示特定信号的陷阱设置 trap -p INT EXIT4.8 在函数中设置局部陷阱
通过在子 shell 中设置陷阱,可以限制其作用域仅在函数内部。
bash
运行
#!/bin/bash cleanup() { echo “执行函数内的清理” } foo() { # 在子shell中设置陷阱,使其成为局部的 ( trap cleanup EXIT echo “在函数 foo 中” false # 触发错误,导致子shell退出,从而触发 EXIT 陷阱 ) echo “函数 foo 内的陷阱不会影响主脚本” } foo echo “脚本继续执行”5. 注意事项
- 作用域与继承:在函数中直接设置的 trap 是全局的,会影响整个脚本进程。若要限制作用域,需在子 shell(如 ( ))内设置。子 shell 中的 trap 设置不会影响父 shell。
- 强制信号:SIGKILL (9) 和 SIGSTOP (19) 不能被任何进程捕获、阻塞或忽略,这是由操作系统内核强制实施的。
- 竞争条件:在 trap 处理程序执行期间,如果再次收到信号,可能引发未定义行为。对于关键的清理操作,应尽量保持处理程序简单、快速、幂等。
- 信号重置:在大多数 shell 中,当进入信号处理程序时,该信号的处理方式会被重置为默认。如果希望在处理程序执行期间能再次捕获同一信号,需要在处理程序内部重新设置 trap。
- 多命令执行:若要在 trap 中执行多条命令,应将其包裹在引号中或定义为一个函数,以确保正确解析。
plaintext
# 方式一:使用引号包裹多条命令 trap ‘cmd1; cmd2; cmd3’ SIGNAL # 方式二(推荐):使用函数 my_handler() { cmd1; cmd2; cmd3; } trap my_handler SIGNAL - 可移植性:虽然大多数现代 shell 允许省略 SIG 前缀,但为了脚本的最大可移植性,建议查阅目标 shell 的具体手册。
获取更多Linux学习资料请关注“阿成学长工具包”,对话框中输入2647获取