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 done

4.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 2

4.5 使用 ERR 陷阱捕获错误

当脚本中任何命令执行失败时,触发自定义错误处理。

bash

运行

#!/bin/bash set -e # 出现错误时退出脚本 trap 'echo “错误发生在第 $LINENO 行”' ERR ls not_exist_file.txt

4.6 列出所有可用信号

plaintext

trap -l

4.7 显示当前已设置的陷阱

plaintext

# 显示所有已设置的陷阱 trap -p # 仅显示特定信号的陷阱设置 trap -p INT EXIT

4.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获取