You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Bash中追踪并终止脚本衍生的所有未知子进程?

问题

我有一个名为pipeline_runner.sh的Bash包装脚本,它会启动一个复杂的建模脚本,该脚本又会自行启动多个子进程和脚本。我希望能追踪该建模脚本单次运行所衍生的所有进程,以便在满足特定条件时将它们全部终止。

示例pipeline_runner.sh脚本内容如下:

#!/bin/bash

# Some set up of the script ...

./monitor_job.sh ... arguments TBD ... &

script_path="path/to/bash/script"

chmod u+x "$script_path"
"$script_path"

# ...

每次运行pipeline_runner.sh时,都会在后台启动一个monitor_job.sh实例,用于监控本次运行所启动的path/to/bash/script进程。当monitor_job.sh中定义的任意条件满足时,需要能够终止该次运行的path/to/bash/script进程及其直接或间接衍生的所有进程。

由path/to/bash/script运行所启动的其他进程数量众多且不固定,因此我需要找到一种方法将所有衍生脚本归入某个组或列表,以便在需要时全部终止。仅终止初始的$script_path进程是不够的,因为该脚本的所有子进程会继续运行。

重要的次要目标:

  • 实现动态适配,不依赖于$script_path指定的具体脚本,即不能硬编码特定命令名称进行查找;
  • 监控操作需在独立脚本monitor_job.sh中执行,而非直接在pipeline_runner.sh中进行。

请问如何追踪建模脚本启动的所有进程,以便在需要时将它们全部终止?


方法1:利用进程组(Process Group)批量终止

这是Bash原生的轻量方案,无需额外工具,核心是让目标脚本及其所有子进程归入同一个进程组。

步骤1:修改pipeline_runner.sh传递进程组ID

修改启动逻辑,记录目标脚本的进程组ID并传递给监控脚本:

#!/bin/bash

# Some set up of the script ...

script_path="path/to/bash/script"
chmod u+x "$script_path"

# 启动目标脚本并获取其PID与进程组ID
"$script_path" &
TARGET_PID=$!
# 提取进程组ID并去除空格
TARGET_PGID=$(ps -o pgid= "$TARGET_PID" | tr -d ' ')

# 将进程组ID作为参数传入monitor_job.sh
./monitor_job.sh "$TARGET_PGID" &

# 等待目标脚本执行完成
wait "$TARGET_PID"

步骤2:在monitor_job.sh中终止整个进程组

触发终止条件时,直接向进程组发送信号(-前缀表示目标是进程组而非单个PID):

#!/bin/bash
TARGET_PGID="$1"

# 示例监控逻辑:当触发文件存在时终止进程组
while true; do
    if [ -f "/tmp/terminate_trigger" ]; then
        # 先发送SIGTERM让进程优雅退出
        kill -TERM -"$TARGET_PGID"
        # 5秒后强制终止未退出的进程
        sleep 5
        kill -KILL -"$TARGET_PGID" 2>/dev/null
        exit 0
    fi
    sleep 1
done

注意:如果目标脚本主动调用setsid脱离进程组,需要调整脚本避免此操作,否则子进程会脱离监控范围。

方法2:使用cgroup(Linux专属,适合精细控制)

cgroup是Linux内核提供的进程分组管理机制,所有衍生进程会自动归入同一cgroup,适合需要同时管控资源的场景。

步骤1:修改pipeline_runner.sh创建临时cgroup

#!/bin/bash

# 创建以当前脚本PID命名的临时cgroup(需root权限或配置用户cgroup权限)
CGROUP_NAME="pipeline_$$"
mkdir -p /sys/fs/cgroup/cpu/"$CGROUP_NAME"
mkdir -p /sys/fs/cgroup/pids/"$CGROUP_NAME"

# 将当前脚本(pipeline_runner.sh)加入cgroup,避免误杀自身
echo "$$" > /sys/fs/cgroup/cpu/"$CGROUP_NAME"/cgroup.procs
echo "$$" > /sys/fs/cgroup/pids/"$CGROUP_NAME"/cgroup.procs

script_path="path/to/bash/script"
chmod u+x "$script_path"

# 启动目标脚本并加入cgroup
"$script_path" &
TARGET_PID=$!
echo "$TARGET_PID" > /sys/fs/cgroup/cpu/"$CGROUP_NAME"/cgroup.procs
echo "$TARGET_PID" > /sys/fs/cgroup/pids/"$CGROUP_NAME"/cgroup.procs

# 传递cgroup名称给监控脚本
./monitor_job.sh "$CGROUP_NAME" &

wait "$TARGET_PID"

# 清理临时cgroup
rmdir /sys/fs/cgroup/cpu/"$CGROUP_NAME"
rmdir /sys/fs/cgroup/pids/"$CGROUP_NAME"

步骤2:在monitor_job.sh中通过cgroup终止进程

#!/bin/bash
CGROUP_NAME="$1"

while true; do
    if [ -f "/tmp/terminate_trigger" ]; then
        # 获取cgroup内所有进程PID并发送终止信号
        ALL_PIDS=$(cat /sys/fs/cgroup/cpu/"$CGROUP_NAME"/cgroup.procs)
        kill -TERM $ALL_PIDS
        sleep 5
        kill -KILL $ALL_PIDS 2>/dev/null
        exit 0
    fi
    sleep 1
done

方法3:递归遍历进程树(无权限场景备选)

通过PID递归查找所有子进程,适合无法使用cgroup或进程组的场景,但存在微小的漏杀风险(遍历过程中新增的子进程可能未被捕获)。

步骤1:修改pipeline_runner.sh传递目标PID

#!/bin/bash

script_path="path/to/bash/script"
chmod u+x "$script_path"

"$script_path" &
TARGET_PID=$!

# 传递目标PID给监控脚本
./monitor_job.sh "$TARGET_PID" &

wait "$TARGET_PID"

步骤2:在monitor_job.sh中实现进程树遍历与终止

#!/bin/bash
TARGET_PID="$1"

# 递归获取所有子进程PID
get_child_pids() {
    local parent_pid="$1"
    # 获取父进程的直接子进程
    local child_pids=$(ps -o pid= --ppid "$parent_pid" | tr -d ' ')
    echo "$child_pids"
    # 递归查找子进程的子进程
    for pid in $child_pids; do
        get_child_pids "$pid"
    done
}

while true; do
    if [ -f "/tmp/terminate_trigger" ]; then
        # 收集目标PID及其所有子进程
        ALL_PIDS="$TARGET_PID $(get_child_pids "$TARGET_PID")"
        kill -TERM $ALL_PIDS
        sleep 5
        kill -KILL $ALL_PIDS 2>/dev/null
        exit 0
    fi
    sleep 1
done

内容的提问来源于stack exchange,提问作者Chephen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 06:17:10