trap: 接收到特定信号时要执行的命令

发布时间: 更新时间: 总字数:2609 阅读时间:6m 作者:IP:上海 网址

在 Linux 中,trap 命令是一个非常强大的内置命令,它允许你捕获并处理信号。简单来说,它的作用是指定当 shell 接收到特定信号时要执行的命令

什么是信号?

信号(Signal)是操作系统用来通知进程发生了某个事件的机制。这些事件可以是用户行为(例如按下 Ctrl+C 中断程序)、硬件异常(例如除零错误)、或者其他进程的通知。

常见的信号包括:

  • SIGINT (2):当用户按下 Ctrl+C 时发送,通常用于中断程序。
  • SIGTERM (15):终止信号,程序可以捕获并优雅地退出。这是 kill 命令默认发送的信号。
  • SIGHUP (1):当控制终端关闭时发送,或者父进程终止时发送给子进程。常用于重新加载配置文件。
  • SIGKILL (9):强制终止信号,无法被捕获或忽略
  • SIGQUIT (3):当用户按下 Ctrl+\ 时发送,通常用于退出程序并生成核心转储文件。
  • EXIT (0):这是一个特殊的伪信号,当 shell 脚本退出时(无论是正常退出还是异常退出)都会触发。

trap 命令的语法

trap 命令的基本语法如下:

bash
trap 'commands' signals
  • commands:当接收到指定的信号时要执行的命令或一系列命令。这些命令通常用单引号或双引号括起来。
  • signals:一个或多个信号名称或信号编号。

trap 命令的实际用途

trap 命令在编写健壮的 shell 脚本时非常有用,主要用于以下场景:

  1. 清理临时文件或资源:这是 trap 最常见的用途之一。你可以设置一个 trap,在脚本退出时(EXIT 信号)自动删除脚本创建的临时文件,防止它们残留在系统中。

    bash
    #!/bin/bash
    
    TEMP_FILE=$(mktemp)
    echo "This is a temporary file." > "$TEMP_FILE"
    
    # 在脚本退出时删除临时文件
    trap "rm -f $TEMP_FILE; echo '临时文件已清理。'" EXIT
    
    echo "脚本正在运行..."
    sleep 5
    echo "脚本即将退出。"
    # 当脚本正常或非正常退出时,都会触发 EXIT 信号并执行 trap 中的命令
  2. 优雅地处理中断:当用户按下 Ctrl+C 时,脚本可以捕获 SIGINT 信号,执行一些清理操作或者提示用户,而不是立即退出。

    bash
    #!/bin/bash
    
    # 捕获 SIGINT 信号,并提示用户
    trap "echo '收到中断信号,正在退出...'; exit 1" SIGINT
    
    echo "按 Ctrl+C 尝试中断我。"
    while true; do
        sleep 1
    done
  3. 重新加载配置:对于一些后台服务脚本,可以捕获 SIGHUP 信号来触发服务的配置重新加载,而不需要重启整个服务。

    bash
    #!/bin/bash
    
    CONFIG_FILE="/etc/my_service/config.conf"
    
    reload_config() {
        echo "重新加载配置文件: $CONFIG_FILE"
        # 这里放置重新加载配置的逻辑,例如解析配置文件等
    }
    
    # 捕获 SIGHUP 信号来重新加载配置
    trap "reload_config" SIGHUP
    
    echo "服务正在运行,等待 SIGHUP 信号重新加载配置..."
    while true; do
        sleep 60 # 模拟服务运行
    done
  4. 调试:可以使用 trap 来捕获 DEBUG 伪信号,在每条命令执行前打印信息,帮助调试脚本。

    bash
    #!/bin/bash
    
    # 在每条命令执行前打印命令
    trap 'echo "Executing: $BASH_COMMAND"' DEBUG
    
    echo "Hello"
    ls -l
    echo "World"

移除或重置陷阱

  • 移除特定的陷阱:使用 - 选项可以移除之前设置的陷阱。

    bash
    trap - SIGINT # 移除 SIGINT 的陷阱
  • 重置为默认行为:使用 -- 选项可以重置信号的处理方式为系统默认行为。

    bash
    trap -- SIGINT # 重置 SIGINT 的处理为默认行为
  • 显示当前设置的陷阱:直接运行 trap 命令不带任何参数,可以显示当前 shell 中设置的所有陷阱。

    bash
    trap

在 Linux Shell 脚本编程中,trap 是一个非常强大且常用的内置命令。它的主要作用是捕捉系统信号(Signals),并在接收到这些信号时执行自定义的命令或操作

使用场景

通常我们用它来做这些事:

  1. 脚本退出时清理临时文件(无论脚本是正常结束还是被意外中止)。
  2. 屏蔽某些信号(例如忽略 Ctrl+C)。
  3. 捕获错误并打印调试信息

trap 命令基础语法

bash
trap '执行的命令或函数' 信号列表

常见的系统信号:

  • EXIT (或 0):脚本正常结束或退出时触发(最常用)。
  • INT (或 2):中断信号,通常是用户按下了 Ctrl+C
  • TERM (或 15):终止信号,通常是 kill 命令默认发送的信号。
  • ERR:当任何一条命令执行失败(返回非 0 退出码)时触发。

其他特殊用法:

  • trap - 信号trap default 信号:恢复该信号的默认行为。
  • trap '' 信号:忽略该信号(什么都不做)。

可以直接运行的示例脚本

这里提供 4 个不同场景的实用脚本。

运行准备: 将下面的任意一段代码保存为 .sh 文件(例如 test.sh),然后赋予执行权限并运行:

bash
chmod +x test.sh
./test.sh

示例 1:最经典的用法 —— 退出时自动清理临时文件

无论脚本是执行完毕,还是中途被你按 Ctrl+C 强行终止,只要脚本退出,就会触发 EXIT 信号,从而删除临时文件。

bash
#!/bin/bash

# 创建一个临时文件
TEMP_FILE=`/tmp/my_temp_data_$$.txt`
echo `初始数据` > `$TEMP_FILE`
echo `1. 临时文件 $TEMP_FILE 已创建。`

# 使用 trap 捕捉 EXIT 信号
# 当脚本退出时,执行 rm -f 删除临时文件
trap 'echo -e `\n[Trap触发] 脚本即将退出,正在清理临时文件...`; rm -f `$TEMP_FILE`; echo `清理完成!`' EXIT

echo `2. 脚本正在运行中,你可以新开一个终端查看该文件是否存在。`
echo `3. 倒计时 5 秒。在此期间你可以按 Ctrl+C 强制中断脚本,看看文件是否会被清理...`

for i in {5..1}; do
    echo -n `$i `
    sleep 1
done
echo ``

echo `4. 脚本正常执行完毕。`
# 脚本结束后,会自动触发 trap 里的清理命令

示例 2:拦截 Ctrl+C (SIGINT) 并给出提示

这个脚本会拦截你按下的 Ctrl+C,不仅不会退出,还会给你一个弹框提示。

bash
#!/bin/bash

# 捕捉 INT 信号 (Ctrl+C)
trap 'echo -e `\n警告: 捕捉到了 Ctrl+C 操作,但我不会退出!输入 \`exit\` 才能退出程序。`' INT

echo `脚本已启动。尝试按下 Ctrl+C 看看会发生什么。`

# 进入死循环,等待用户输入
while true; do
    read -p `请输入命令 (输入 exit 退出): ` user_input
    if [[ `$user_input` == `exit` ]]; then
        echo `收到 exit 指令,程序退出。`
        break
    fi
    echo `你输入了: $user_input`
done

示例 3:忽略信号(实现“无法被 Ctrl+C 中断”的脚本)

如果把 trap 后面的命令留空(写成两个单引号 ''),就可以直接忽略对应的信号。

bash
#!/bin/bash

# 忽略 INT (Ctrl+C) 和 TSTP (Ctrl+Z) 信号
trap '' INT TSTP

echo `这是一个不能被打断的关键任务...`
echo `按 Ctrl+C 或 Ctrl+Z 都对我无效!`

for i in {1..5}; do
    echo `关键任务执行中... $i/5`
    sleep 1
done

echo `关键任务执行完毕!`

# 恢复 Ctrl+C 的默认功能
trap - INT TSTP
echo `现在 Ctrl+C 恢复正常了。`
sleep 5 # 你可以在这 5 秒内按 Ctrl+C 试试,此时脚本会被中断

示例 4:捕获脚本错误 (ERR) 实现自动报错

在编写复杂脚本时,可以用 trap ERR 来捕获发生错误的命令所在的行号,非常利于调试。

bash
#!/bin/bash

# 捕捉 ERR 信号,打印出错的行号 ($LINENO) 和出错的命令 ($BASH_COMMAND)
trap 'echo `[错误] 脚本在第 $LINENO 行发生错误!出错命令: $BASH_COMMAND`' ERR

echo `执行正确的命令:ls -l / > /dev/null`
ls -l / > /dev/null
echo `正确命令执行完毕。`

echo `-------------------`

echo `故意执行一个错误的命令:尝试列出一个不存在的目录`
ls /not_exist_directory_12345

echo `-------------------`
echo `虽然上面报错了,但因为没有 set -e,脚本会继续执行到这里。`

总结建议

在实际的运维或开发工作中,强烈建议在创建了临时文件、修改了系统状态、或者开启了后台进程的脚本开头,加上 trap ... EXIT。这是保证系统干净、防止僵尸进程的最佳实践。

trap 命令是 Linux shell 编程中一个不可或缺的工具,它提供了对信号处理的强大控制,使得脚本能够更健壮、更可靠地运行,并能够优雅地处理各种异常情况。掌握 trap 的使用对于编写高质量的 shell 脚本至关重要。