Shell脚本安全信号捕获的关键是确保所有退出路径(INT/TERM/EXIT)均执行完整cleanup:终止子进程、删临时文件、恢复终端、记日志,并避开作业控制缺失、子shell不继承trap等陷阱。

在 Shell 脚本中编写安全的信号捕获,核心是让脚本在收到中断(如 Ctrl+C)、终止(kill)或自然退出时,仍能可靠清理资源、不遗留子进程、不破坏终端状态。关键不在“能不能捕获”,而在“捕获后是否真能干净收场”。
必须提前定义 cleanup 函数并注册 trap
trap 命令本身不执行逻辑,它只是“登记一个回调”。所以必须先写好 cleanup 函数,再用 trap 绑定信号:
- 函数定义要放在 trap 语句之前,否则运行时会报 command not found
- 推荐用单引号包裹 trap 参数,避免变量过早展开:trap 'cleanup' INT TERM
- 多个信号可一次注册:trap 'cleanup; exit 130' INT TERM QUIT,减少重复
- INT 对应 Ctrl+C,TERM 是 kill 默认发的信号,QUIT 是 Ctrl+\,三者常一起处理
cleanup 函数里该做哪些事
一个真正安全的 cleanup 不只是 echo 一句“正在退出”,而是覆盖常见残留风险:
- 显式终止后台子进程:[[ -n "$SERVER_PID" ]] && kill "$SERVER_PID" 2>/dev/null;更稳妥可用 pkill -P $$ 清理整个子进程树
- 删除锁文件、临时目录、PID 文件:rm -f /var/run/myscript.pid /tmp/myapp_$$
- 恢复终端设置:stty sane 2>/dev/null(尤其脚本修改过 echo -n 或关闭了回显时)
- 记录日志:echo "$(date): graceful exit" >> /var/log/myscript.log
- 避免调用可能阻塞的命令:不要在 cleanup 里用未设超时的 curl、ssh 或 sleep
加 EXIT trap 作为兜底保障
INT/TERM 可能被用户触发,但脚本也可能因错误、主动 exit 或执行完自然结束而退出。只靠信号 trap 会漏掉这些路径:
- 用 trap 'cleanup' EXIT 确保所有退出场景都走一遍清理逻辑
- EXIT trap 在脚本即将退出前触发,包括正常结束、exit 调用、甚至被其他 trap 中的 exit 终止后
- 注意:EXIT trap 不会继承到子 shell,所以不要把主逻辑包在 () 里
- 若同时用了 INT 和 EXIT,建议 INT trap 里也调用 cleanup 并 exit,避免重复执行但逻辑一致
避开几个高频陷阱
很多脚本看似加了 trap,一压 Ctrl+C 就出问题,往往栽在这几处:
- 没启用作业控制:脚本开头加 set -o monitor,否则后台进程(&)启动的子进程可能不响应父进程的信号
- 子 shell 不继承 trap:比如在 while 循环里用管道或 ( ) 执行命令,trap 会失效;应改用主流程控制或显式传递 PID
- 忽略 SIGKILL(kill -9)和 SIGSTOP:这两个信号不可捕获,任何 trap 都无效,设计时就要接受“强杀即无清理”
- 变量作用域问题:cleanup 中用到的变量(如 $SERVER_PID)需在全局作用域赋值,不能只在函数内定义


















