48·Shell 脚本进阶高级

trap 信号处理

trapsignals

第 48 课 - trap 信号处理

所属模块:Shell 脚本进阶 难度:Advanced 标签:trap, signals 上一课:at 与 batch 一次性任务 下一课:Shell 脚本调试技巧


1. 学习目标

完成本课学习后,你将能够:

  • 理解Linux系统中**信号(Signal)**的基本概念及其在脚本中的作用。
  • 掌握使用 trap 命令捕获和拦截常见信号(如 SIGINT, SIGTERM)的方法。
  • 学会利用 trap 命令在脚本退出或收到中断信号时,执行清理操作(如删除临时文件)。
  • 编写出能够应对用户中断、系统终止等场景的健壮的Shell脚本

2. 核心概念

什么是信号?

想象一下,你正在厨房煮一锅汤。你设置了一个闹钟(信号)来提醒自己,汤煮好了。当闹钟响(信号被触发)时,你需要执行一个动作(处理信号),比如关火。 在Linux中,信号是一种进程间通信的方式。系统、用户或另一个进程可以向一个正在运行的进程(如你的Shell脚本)发送一个信号,通知它发生了某个特定事件,例如:

  • 用户按下 Ctrl+C(发送 SIGINT 信号,要求中断)。
  • 用户按下 Ctrl+Z(发送 SIGTSTP 信号,要求暂停)。
  • 系统要关闭(发送 SIGTERM 信号,要求终止)。
  • 脚本执行了非法指令(内核发送 SIGKILL 信号,强制终止)。

trap 命令的作用

trap 是你的脚本的“信号监听员和事件处理器”。你可以用它来“设置陷阱”,当脚本收到指定的信号时,立即执行一段你预定义的命令或函数。 其基本语法是:

trap ‘COMMAND’ SIGNAL1 SIGNAL2 ...

常见的应用场景包括:

  1. 优雅退出:当脚本被中断时,清理掉它创建的临时文件。
  2. 忽略信号:让脚本对某些信号“免疫”,继续执行。
  3. 定义退出动作:无论脚本如何结束(正常结束或被信号终止),都执行一些收尾工作。

常见信号列表

信号名信号编号默认动作描述常用触发方式
SIGHUP1终止挂起,终端断开终端关闭
SIGINT2终止中断Ctrl+C
SIGQUIT3终止并转储核心退出Ctrl+\
SIGTERM15终止软件终止信号(默认)kill <PID>
SIGTSTP20暂停进程终端停止Ctrl+Z
SIGKILL9强制终止不能被捕获或忽略kill -9 <PID>
SIGSTOP19强制暂停不能被捕获或忽略kill -STOP <PID>
EXIT0-脚本退出时触发脚本正常结束或被大多数信号终止

3. 代码示例

示例1:捕获 SIGINT (Ctrl+C) 并退出

这个脚本展示如何捕获用户中断,并输出一条消息,而不是立即终止。

#!/bin/bash
# script1_trap_basic.sh
# 演示捕获 Ctrl+C 信号

echo “脚本开始运行,PID: $$”
echo “按 Ctrl+C 尝试中断脚本...”

# 设置陷阱:当收到 SIGINT 时,执行花括号内的命令
trapecho “\n捕获到 Ctrl+C 信号!脚本将安全退出。”; exit 1’ SIGINT

# 模拟一个需要一段时间的循环
for i in {1..10}; do
    echo “处理任务 $i/10 ...”
    sleep 2
done

echo “脚本正常结束。”

运行与测试

chmod +x script1_trap_basic.sh
./script1_trap_basic.sh
# 在脚本运行期间,按下 Ctrl+C,观察输出变化。

示例2:使用 trap 清理临时文件(实战重点)

这是 trap 最重要的应用之一。确保脚本在任何情况下(正常退出、被中断)都能清理自己创建的垃圾文件。

#!/bin/bash
# script2_cleanup_temp.sh
# 演示使用 trap 进行临时文件清理

# 定义一个唯一的临时文件名
TEMP_FILE=$(mktemp /tmp/my_script.XXXXXX)
echo “创建临时文件: $TEMP_FILE# 定义清理函数
cleanup() {
    echo “正在执行清理操作...”
    if [ -f “$TEMP_FILE” ]; then
        rm -f “$TEMP_FILEecho “已删除临时文件: $TEMP_FILEfi
}

# 设置陷阱:当脚本收到 EXIT 信号时,调用 cleanup 函数。
# EXIT 信号在脚本以任何方式退出时都会被触发(正常结束、被 SIGINT/SIGTERM 终止等)。
trap cleanup EXIT

echo “开始重要工作,临时文件用于存储数据...”
# 模拟向临时文件写入数据
echo “一些中间数据...” > “$TEMP_FILEsleep 5 # 模拟长时间运行

echo “工作完成。”
# 脚本正常结束时,会自动触发 EXIT 信号,从而调用 cleanup 函数

运行与测试

chmod +x script2_cleanup_temp.sh
./script2_cleanup_temp.sh
# 观察脚本结束时是否输出了清理信息。
# 你也可以在脚本 `sleep 5` 期间按 `Ctrl+C`,看看清理是否依然执行。

示例3:忽略信号与组合使用

你可以忽略某些信号,也可以为多个信号设置同一个处理程序。

#!/bin/bash
# script3_ignore_and_combine.sh
# 演示忽略信号和为多个信号设置陷阱

echo “脚本开始,我将忽略 Ctrl+C 和 Ctrl+Z 3秒钟...”

# 忽略 SIGINT 和 SIGTSTP 信号
trap ‘’ SIGINT SIGTSTP

echo “现在按 Ctrl+C 或 Ctrl+Z 是无效的。”
sleep 3

# 重新设置陷阱:捕获这些信号
echo “恢复信号处理。现在再按 Ctrl+C 将会退出。”
trapecho “捕获信号,准备退出...”; exit 1’ SIGINT SIGTSTP

# 继续运行
echo “等待 10 秒...”
sleep 10
echo “正常结束。”

4. 实践练习

练习1(基础):捕获并列出信号

编写一个脚本 ex1_signal_logger.sh,它会运行一个无限循环。当用户按下 Ctrl+C 时,脚本不要立即退出,而是输出一条消息:“我捕获到了 SIGINT 信号,这是你第 $count 次按 Ctrl+C。”,其中 $count 记录按下的次数。只有当按下 Ctrl+C 3次后,脚本才真正退出,并显示总次数。

预期输出示例

$ ./ex1_signal_logger.sh
脚本运行中...
^C
我捕获到了 SIGINT 信号,这是你第 1 次按 Ctrl+C。
^C
我捕获到了 SIGINT 信号,这是你第 2 次按 Ctrl+C。
^C
我捕获到了 SIGINT 信号,这是你第 3 次按 Ctrl+C。
总共被中断了 3 次,脚本退出。

练习2(进阶):健壮的文件处理脚本

编写一个脚本 ex2_safe_file_processor.sh。它接受一个文件路径作为参数(./ex2_safe_file_processor.sh /path/to/file)。

  1. 检查:如果文件不存在,报错退出。
  2. 备份:在处理前,为原文件创建一个备份 原文件名.bak
  3. 处理:模拟一个耗时操作(如 sleep 10)。
  4. 清理:在任何情况下(正常结束或被 Ctrl+C 中断),如果备份文件 原文件名.bak 存在,就将其删除
  5. 陷阱:如果脚本在“处理”阶段被中断,打印一条警告:“处理被中断!备份文件已被清理。”

要求:使用 trap 来实现健壮的清理逻辑。


5. 常见错误

  1. trap 命令中使用变量的时机

    FILE="temp.txt"
    # 错误:单引号内的变量不会被展开,$FILE 会被当作字面量。
    traprm -f $FILE’ EXIT
    # 正确:使用双引号,或者确保在 trap 之前变量已定义,且 trap 命令在设置时解析变量。
    traprm -f “$FILE”’ EXIT
    

    提示:如果希望在 trap 执行时动态获取变量值,通常推荐在 trap 后面直接使用双引号。

  2. 清理函数不检查文件是否存在: 在清理函数中直接执行 rm -f $TEMP_FILE 是安全的,因为 -f 选项可以忽略不存在的文件。但更清晰的做法是先用 [ -f “$TEMP_FILE” ] && rm “$TEMP_FILE” 进行检查。

  3. 混淆 EXIT 和具体信号: 只设置了 trap ... SIGINT,但脚本因为运行错误(非信号)而退出时,你的清理代码不会被执行。为确保万无一失,通常建议同时捕获 EXIT 信号。

  4. 忘记让脚本可执行: 写完脚本后,直接运行 ./script.sh 会提示权限错误。记得使用 chmod +x script.sh 赋予执行权限。


6. 小结

  • 信号是Linux进程间通信的机制,trap 是Shell脚本处理信号的核心命令。
  • 核心用法trap ‘COMMAND’ SIGNAL [SIGNAL ...],用于在捕获到指定信号时执行命令。
  • 最佳实践始终为脚本创建的临时文件设置基于 EXIT 信号的清理陷阱。这是编写健壮、专业脚本的标志。
  • 组合陷阱:可以为同一个动作捕获多个信号(如 SIGINTSIGTERM),也可以使用空字符串 ‘’ 来忽略信号。
  • 调试技巧:在开发复杂脚本时,可以在 trap 的处理命令中加入 echo 语句,来确认信号是否被正确捕获以及脚本的执行流程。

通过掌握 trap,你将能编写出更加稳定可靠、用户体验更好的Shell脚本,为系统运维和自动化任务增添一份保障。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「Shell 脚本调试技巧」 以巩固所学知识。