Linux trap 命令

Linux trap 命令 前言在编写 Shell 脚本时我们经常会遇到这样的问题脚本被意外中断如按 CtrlC后启动的后台进程仍在运行变成了孤儿进程残留系统中。trap 命令正是解决这类问题的关键工具——它允许脚本捕获特定的系统信号并在信号发生时执行自定义操作从而实现资源的优雅清理。本文从 trap 的基本语法入手逐步深入到信号捕获的常见场景和高级用法最后通过三个进阶示例展示如何在实际脚本中利用 trap 管理后台进程的生命周期。无论你是想为脚本增加健壮性还是需要实现复杂的进程管理这份指南都能提供实用的参考。一、基本语法trap[动作][信号列表]语法说明部分 说明 ------------------------------------------------------- 动作 收到信号时要执行的命令或函数 ------------------------------------------------------- 信号列表 要捕获的信号名称或编号多个信号用空格分隔trap命令命令 说明 -------------------------------------------------------trap-p查看所有已注册的trap-------------------------------------------------------trap-pSIGINT 查看特定信号的trap-------------------------------------------------------trap-l查看所有信号列表常用信号列表含 Shell 伪信号信号名称 编号 触发方式 能否捕获 说明 ---------------------------------------------------------------- SIGINT2CtrlC 可以 中断信号最常用 ---------------------------------------------------------------- SIGTERM15killPID 可以 终止信号优雅退出 ---------------------------------------------------------------- SIGHUP1终端关闭 可以 重载配置 ---------------------------------------------------------------- SIGQUIT3Ctrl\可以 退出并生成 core dump ---------------------------------------------------------------- SIGUSR130kill-USR1PID 可以 用户自定义信号1 ---------------------------------------------------------------- SIGUSR231kill-USR2PID 可以 用户自定义信号2 ---------------------------------------------------------------- SIGALRM14alarm()超时 可以 定时器信号 ---------------------------------------------------------------- SIGCHLD17子进程退出 可以 子进程状态改变# 注在日常使用中可以省略 SIG 前缀使用简写如 INT、TERM# 注意不能混写如 SIGINT 和 TERM 混用保持风格统一# 正常或异常退出都捕捉kill -9 不触发EXIT - 脚本退出 可以 shell 伪信号可用0 ----------------------------------------------------------------# ERR 只捕获命令失败exit 1 不触发ERR - 命令返回非0 可以 shell 伪信号错误处理 ---------------------------------------------------------------- DEBUG - 每条命令执行前 可以 shell 伪信号调试用二、三种基础用法1. 执行命令或函数执行单条命令(CtrlC退出) main.sh#!/bin/bashecho示例1trapecho 捕获到 CtrlC 退出信号SIGINTecho运行中...sleep5执行多条命令用分号分隔main.sh#!/bin/bashecho示例2trapecho 捕获到信号; echo 清理中...; exit 0SIGINTecho运行中...sleep5调用单个函数(捕获到 CtrlC 信号执行 cleanup 函数。必须先定义函数再注册 trap) main.sh#!/bin/bashcleanup(){echo收到信号, 执行清理}trapcleanup SIGINTecho运行中...sleep5调用多个函数 main.sh#!/bin/bashcleanup2(){echo程序正常或异常退出信号, 执行清理}cleanup3(){echoCtrlC 退出, 执行清理}trapcleanup2 EXITtrapcleanup3 SIGINTecho运行中...sleep52. 忽略信号不执行任何操作# 忽略 SIGINT按 CtrlC 无效trapSIGINT# 忽略多个信号trapSIGINT SIGTERM3. 恢复默认行为# 恢复信号的默认处理trap- SIGINT# 恢复多个信号trap- SIGINT SIGTERM忽略信号和恢复信号示例 main.sh#!/bin/bash# 忽略 CtrlCtrapSIGINTecho开始数据库备份请勿中断...echo-n备份进度# 模拟备份过程带进度条foriin{1..10};doecho-n█sleep1doneecho 100%# 恢复 CtrlCtrap- SIGINTecho备份完成, 现在按 CtrlC 可以中断sleep5echo正常退出三、常用信号示例1. SIGTERM (kill)#!/bin/bash# 保存为 test_sigterm.shtrapecho 捕获到 SIGTERM优雅退出; exitSIGTERMecho进程 PID:$$echo执行: kill$$或 kill -15$$测试...whiletrue;dosleep1done2. SIGHUP (终端关闭/重载配置)#!/bin/bash# 保存为 test_sighup.shtrapecho 捕获到 SIGHUP重新加载配置...SIGHUPecho进程 PID:$$echo执行: kill -1$$测试...whiletrue;dosleep1done3. SIGQUIT (Ctrl)#!/bin/bash# 保存为 test_sigquit.shtrapecho 捕获到 SIGQUIT退出并生成 core; exitSIGQUITecho进程 PID:$$echo按 Ctrl\ 测试...whiletrue;dosleep1done4. SIGUSR1 (用户自定义1)#!/bin/bash# 保存为 test_sigusr1.shtrapecho 捕获到 SIGUSR1执行自定义操作 ASIGUSR1echo进程 PID:$$echo执行: kill -USR1$$测试...whiletrue;dosleep1done5. SIGUSR2 (用户自定义2)#!/bin/bash# 保存为 test_sigusr2.shtrapecho 捕获到 SIGUSR2执行自定义操作 BSIGUSR2echo进程 PID:$$echo执行: kill -USR2$$测试...whiletrue;dosleep1done6. SIGALRM (定时器超时)#!/bin/bash# 保存为 test_sigalrm.sh# 超时执行的函数timeout_func(){echo 超时了执行清理操作...}trapecho 超时信号触发; timeout_func; exit 1SIGALRMecho进程 PID:$$echo3秒后 SIGALRM 将自动触发...(sleep3;kill-ALRM$$)# 3秒后发送 SIGALRMsleep5# 模拟耗时任务echo任务完成# 如需要定时器超时功能推荐用 timeout7. SIGCHLD (子进程退出)#!/bin/bash# 保存为 test_sigchld.shtrapecho 捕获到 SIGCHLD子进程状态改变SIGCHLDecho进程 PID:$$echo启动子进程...(sleep2;echo子进程退出)echo等待子进程...wait8. ERR (命令返回非0)#!/bin/bash# 保存为 test_err.shtrapecho ERR 触发命令执行失败 (退出码: $?)ERRecho执行一个存在的命令...ls/tmpecho执行一个不存在的命令...ls/nonexist# 触发 ERRecho测试 exit 1 是否触发 ERR...exit19. DEBUG (每条命令执行前)#!/bin/bash# 保存为 test_debug.shtrapecho DEBUG 触发即将执行: $BASH_COMMANDDEBUGecho命令1sleep1echo命令2ls/tmp四、进阶示例1. 退出时自动清理后台任务脚本退出时通过 trap 捕获信号并执行清理函数终止由脚本启动的后台进程。采用先 TERM 后 KILL的策略给进程留出保存状态的时间。#!/bin/bash# 1. 定义你的长期运行函数my_daemon(){localcount0whiletrue;do((count))echo函数进程 PID:$BASHPID: 后台任务运行中... 循环次数:$countsleep2done}# 2. 启动后台进程并记录 PIDmy_daemonPID$!# 3. 定义清理函数cleanup(){echo检测到主程序退出正在停止后台进程 (PID:$PID)...kill-TERM$PID2/dev/null# 先尝试礼貌终止sleep1kill-KILL$PID2/dev/null# 如果没死强制杀死echo已清理完毕}# 4. 使用 trap 捕获退出信号trapcleanup EXIT INTTERM# 5. 主程序逻辑 (假设这里运行 10 秒后自动退出)echo主程序运行中按 CtrlC 或等待结束...sleep10echo主程序正常结束# 脚本退出时trap 会自动触发 cleanu2. 批量管理多个后台任务当后台任务增多时用数组统一记录所有 PID退出时批量终止。相比示例一这种方式更易扩展和维护新增任务只需追加到数组即可。#!/bin/bash# 声明数组存储所有后台进程PIDdeclare-aPIDS()# 定义你的长期运行函数my_daemon(){localid$1# 用于区分不同实例localcount0whiletrue;do((count))echo${id}-函数进程 PID:${BASHPID}: 循环次数:${count}sleep2done}# 启动所有后台进程并将PID存入数组start_all(){my_daemonAPIDS($!)my_daemonBPIDS($!)echo启动后台进程:${PIDS[*]}}cleanup(){echo检测到主程序退出正在停止所有后台进程...# 遍历所有后台进程并终止forpidin${PIDS[]};doecho终止进程:$pidkill-TERM$pid2/dev/nulldonesleep1# 强制终止仍在运行的进程forpidin${PIDS[]};doifkill-0$pid2/dev/null;thenecho强制终止进程:$pidkill-KILL$pid2/dev/nullfidoneecho清理完成}# 使用 trap 捕获退出信号trapcleanup EXIT INTTERMstart_allecho主程序运行中按 CtrlC 或等待结束...sleep10echo主程序正常结束3. 彻底清理进程树后台进程可能派生出子进程仅终止父进程会导致子进程成为孤儿继续运行。本示例通过终止整个进程组确保父进程及其所有子进程被一并清理。#!/bin/bash# 存储直接启动的后台进程PID不包含派生进程declare-aPIDS()# 长期运行的守护进程my_daemon(){localid$1localcount0# 派生一个长期运行的子进程sleep 100(echo函数进程派生子进程:${BASHPID};sleep100)whiletrue;do((count))# $BASHPID 显示当前bash进程的实际PID子shell中会变化echo${id}-函数进程 PID:${BASHPID}: 循环次数:${count}sleep2done}# 模拟新终端run_top(){# --disable-factory 让终端在当前进程组运行gnome-terminal --disable-factory--geometry100x25200800 --bash-ctop; exec bash}start_all(){# 启用作业控制确保进程组隔离set-mmy_daemonAPIDS($!)run_topPIDS($!)}cleanup(){echo检测到主程序退出正在停止所有后台进程...# 使用负PID杀死整个进程组包括子进程forpidin${PIDS[]};doecho终止进程组:$pidkill-TERM-$pid2/dev/null# 负号表示进程组donesleep1# 强制终止仍未退出的进程forpidin${PIDS[]};doifkill-0-$pid2/dev/null;thenecho强制终止进程组:$pidkill-KILL-$pid2/dev/nullfidoneecho清理完成}# 捕获退出信号自动清理trapcleanup EXIT INTTERMstart_allecho主程序运行中按 CtrlC 或等待结束...sleep10echo主程序正常结束