第 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 ...
常见的应用场景包括:
- 优雅退出:当脚本被中断时,清理掉它创建的临时文件。
- 忽略信号:让脚本对某些信号“免疫”,继续执行。
- 定义退出动作:无论脚本如何结束(正常结束或被信号终止),都执行一些收尾工作。
常见信号列表
| 信号名 | 信号编号 | 默认动作 | 描述 | 常用触发方式 |
|---|---|---|---|---|
SIGHUP | 1 | 终止 | 挂起,终端断开 | 终端关闭 |
SIGINT | 2 | 终止 | 中断 | Ctrl+C |
SIGQUIT | 3 | 终止并转储核心 | 退出 | Ctrl+\ |
SIGTERM | 15 | 终止 | 软件终止信号(默认) | kill <PID> |
SIGTSTP | 20 | 暂停进程 | 终端停止 | Ctrl+Z |
SIGKILL | 9 | 强制终止 | 不能被捕获或忽略 | kill -9 <PID> |
SIGSTOP | 19 | 强制暂停 | 不能被捕获或忽略 | kill -STOP <PID> |
EXIT | 0 | - | 脚本退出时触发 | 脚本正常结束或被大多数信号终止 |
3. 代码示例
示例1:捕获 SIGINT (Ctrl+C) 并退出
这个脚本展示如何捕获用户中断,并输出一条消息,而不是立即终止。
#!/bin/bash
# script1_trap_basic.sh
# 演示捕获 Ctrl+C 信号
echo “脚本开始运行,PID: $$”
echo “按 Ctrl+C 尝试中断脚本...”
# 设置陷阱:当收到 SIGINT 时,执行花括号内的命令
trap ‘echo “\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_FILE”
echo “已删除临时文件: $TEMP_FILE”
fi
}
# 设置陷阱:当脚本收到 EXIT 信号时,调用 cleanup 函数。
# EXIT 信号在脚本以任何方式退出时都会被触发(正常结束、被 SIGINT/SIGTERM 终止等)。
trap cleanup EXIT
echo “开始重要工作,临时文件用于存储数据...”
# 模拟向临时文件写入数据
echo “一些中间数据...” > “$TEMP_FILE”
sleep 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 将会退出。”
trap ‘echo “捕获信号,准备退出...”; 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)。
- 检查:如果文件不存在,报错退出。
- 备份:在处理前,为原文件创建一个备份
原文件名.bak。 - 处理:模拟一个耗时操作(如
sleep 10)。 - 清理:在任何情况下(正常结束或被
Ctrl+C中断),如果备份文件原文件名.bak存在,就将其删除。 - 陷阱:如果脚本在“处理”阶段被中断,打印一条警告:“处理被中断!备份文件已被清理。”
要求:使用 trap 来实现健壮的清理逻辑。
5. 常见错误
-
在
trap命令中使用变量的时机:FILE="temp.txt" # 错误:单引号内的变量不会被展开,$FILE 会被当作字面量。 trap ‘rm -f $FILE’ EXIT # 正确:使用双引号,或者确保在 trap 之前变量已定义,且 trap 命令在设置时解析变量。 trap ‘rm -f “$FILE”’ EXIT提示:如果希望在
trap执行时动态获取变量值,通常推荐在trap后面直接使用双引号。 -
清理函数不检查文件是否存在: 在清理函数中直接执行
rm -f $TEMP_FILE是安全的,因为-f选项可以忽略不存在的文件。但更清晰的做法是先用[ -f “$TEMP_FILE” ] && rm “$TEMP_FILE”进行检查。 -
混淆
EXIT和具体信号: 只设置了trap ... SIGINT,但脚本因为运行错误(非信号)而退出时,你的清理代码不会被执行。为确保万无一失,通常建议同时捕获EXIT信号。 -
忘记让脚本可执行: 写完脚本后,直接运行
./script.sh会提示权限错误。记得使用chmod +x script.sh赋予执行权限。
6. 小结
- 信号是Linux进程间通信的机制,
trap是Shell脚本处理信号的核心命令。 - 核心用法:
trap ‘COMMAND’ SIGNAL [SIGNAL ...],用于在捕获到指定信号时执行命令。 - 最佳实践:始终为脚本创建的临时文件设置基于
EXIT信号的清理陷阱。这是编写健壮、专业脚本的标志。 - 组合陷阱:可以为同一个动作捕获多个信号(如
SIGINT和SIGTERM),也可以使用空字符串‘’来忽略信号。 - 调试技巧:在开发复杂脚本时,可以在
trap的处理命令中加入echo语句,来确认信号是否被正确捕获以及脚本的执行流程。
通过掌握 trap,你将能编写出更加稳定可靠、用户体验更好的Shell脚本,为系统运维和自动化任务增添一份保障。