Bash/Zsh中按Ctrl+C终止脚本时如何杀死所有后台子进程
兼容bash/zsh的并行脚本Ctrl+C全子进程终止方案
原有方案失效核心原因
- bash和zsh默认非交互模式下的作业控制逻辑、
jobs -p返回格式、信号传递范围存在差异,不加兼容处理直接用很容易漏杀进程 - 原脚本最后一个
giza_6目录下的runAgent.sh为前台运行,之前手动记录PID的方案仅捕获了7个后台进程PID,天然漏了最后一个进程 - 仅记录直接启动的sh进程PID无法覆盖子脚本派生的嵌套孙子进程,杀进程时会残留孤儿进程
- 未做优雅退出处理,直接强杀容易导致代理进程写入的数据损坏
最终可用实现代码
#!/bin/bash # 强制开启作业控制,统一bash/zsh下的进程组行为 set -m # 统一清理逻辑:收到终止信号时清理所有关联进程 cleanup() { echo -e "\n接收到终止信号,正在关闭所有giza代理进程..." # 先发送TERM信号允许进程优雅退出 kill -TERM 0 2>/dev/null sleep 1 # 对未退出的残留进程发送KILL信号强制终止 kill -KILL 0 2>/dev/null exit 0 } # 绑定信号:Ctrl+C(INT)、外部终止(TERM)、脚本退出(EXIT)时均触发清理 trap cleanup INT TERM EXIT echo "This runs 8 similar versions of giza" # 用子shell启动各目录下的代理,无需手动来回切换目录,避免路径错误 ( cd giza_google && sh runAgent.sh ) & ( cd giza && sh runAgent.sh ) & ( cd giza_1 && sh runAgent.sh ) & ( cd giza_2 && sh runAgent.sh ) & ( cd giza_3 && sh runAgent.sh ) & ( cd giza_4 && sh runAgent.sh ) & ( cd giza_5 && sh runAgent.sh ) & ( cd giza_6 && sh runAgent.sh ) & # 阻塞等待所有后台进程运行结束 wait
关键实现说明
set -m:强制开启作业控制,让当前脚本启动的所有子进程归属同一个独立进程组,后续kill 0只会杀掉本脚本派生的所有关联进程,不会误杀同终端下的其他程序,该参数在bash、zsh下行为完全一致- 子shell启动写法:
( cd 目标目录 && 执行命令 )的写法会在独立子shell中完成目录切换和命令执行,执行完自动回到原目录,不会出现原脚本中cd失败导致后续路径错误的问题 - 全后台启动+
wait:原脚本最后一个代理为前台运行,会导致信号传递范围异常,把8个代理全部放后台启动后用wait阻塞主脚本,既保证8个进程完全并行,也能让信号捕获逻辑正常生效 - 两阶段杀进程:先发
TERM信号给进程留1秒的优雅退出时间,允许代理完成正在进行的写入、连接关闭操作,避免数据损坏,之后再用KILL信号清理残留的卡住进程
之前尝试方案的具体问题
trap 'kill $(jobs -p)' EXIT:zsh下jobs -p返回的作业PID格式和bash存在差异,部分场景会拿不全直接子进程PID,同时无法覆盖子脚本派生的孙子进程trap "exit" INT TERM ERR; trap "kill 0" EXIT:未开启作业控制时,非交互shell下kill 0的作用范围是整个终端进程组,会误杀终端下其他无关程序;且收到INT信号直接触发exit,会导致清理逻辑没执行完主脚本就退出- 手动记录7个PID的方案:除了漏了最后一个前台进程的PID外,同样无法覆盖子脚本派生的嵌套进程,且部分老版本zsh下
$!返回值存在兼容问题,容易拿错PID
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

