Linux守护脚本需用trap捕获SIGTERM、SIGHUP等信号,在cleanup函数中执行清理并exit;启动时检查/写入PID文件,退出时删除;主循环须带超时以响应信号;推荐结合systemd管理。

在 Linux 中编写能响应优雅关闭信号的守护脚本,核心是正确捕获 SIGTERM(默认 kill 信号)和 SIGHUP(常用于重载配置),并在收到信号后执行清理逻辑(如关闭网络连接、保存状态、释放资源),再安全退出。不能直接忽略或让进程被强制终止。
使用 trap 捕获并处理关键信号
trap 是 shell 脚本中响应信号的标准机制。需在脚本开头就设置,确保从启动起就能响应:
- 用
trap 'cleanup_func' TERM HUP INT捕获常见信号;TERM是优雅停止的关键,HUP常用于重载,INT(Ctrl+C)便于本地调试 - 清理函数内应避免耗时操作(如长 sleep、阻塞 I/O),否则可能超时被系统强杀;必要时用超时控制(如
timeout 5s your_cleanup_cmd) - 清理完成后必须显式调用
exit,否则脚本会继续运行,失去“优雅退出”意义
守护进程需脱离终端并管理 PID 文件
真正的守护脚本需在后台长期运行,同时支持标准服务管理(如 systemd 或 sysvinit):
- 启动时检查
/var/run/myscript.pid是否存在,若存在且对应进程仍在运行,拒绝重复启动 - 成功启动后写入当前
$$(主 shell 进程 ID)到 PID 文件;注意权限(通常属 root:root,644) - 退出前删除 PID 文件,避免下次启动误判;该动作应在 trap 函数中完成
- 建议用
nohup ./myscript.sh &或交由 systemd 管理,而非简单&—— 后者未完全脱离会话,仍可能受 SIGHUP 影响
保持主循环可中断,避免死锁或挂起
守护脚本通常含 while 循环持续工作(如轮询、监听 socket)。若循环体阻塞(如无超时的 read 或 sleep),信号可能无法及时触发 trap:
- 用带超时的等待:例如
sleep 10替代sleep infinity;或对read使用-t 5参数 - 避免在循环中执行不可中断的外部命令(如没设 timeout 的
curl);可改用timeout 30s curl ... || true - 若使用
inotifywait或systemd-notify等工具,确认其支持信号中断;否则需在每次迭代开始前检查退出标志
配合 systemd 实现更健壮的生命周期管理
现代 Linux 发行版推荐用 systemd 替代传统 init 脚本。此时 shell 脚本只需专注业务逻辑,优雅关闭由 systemd 协同保障:
- 在 service 文件中设置
Type=simple(脚本自行 daemonize)或Type=forking(脚本 fork 后返回,需配PIDFile=) - 关键配置:
KillSignal=SIGTERM(默认)、TimeoutStopSec=30(给 trap 清理留足时间)、RestartPreventExitStatus=0(正常 exit 不重启) - 脚本里仍要保留 trap,因为 systemd 在超时前会先发 SIGTERM;若清理超时,systemd 才发 SIGKILL 强制终止
- 可通过
systemctl kill -s SIGTERM myscript.service手动测试信号响应是否生效
不复杂但容易忽略的是:信号处理必须尽早注册、PID 文件读写要加锁或校验、主循环要有明确的中断点。只要 trap 设置得当,配合合理的等待策略和 systemd 配置,就能实现真正可靠的优雅关闭。


















