我想杀死整个进程树。使用通用脚本语言实现这一目标的最佳方法是什么?我在寻找一个简单的解决办法。


当前回答

要递归地杀死进程树,请使用killtree():

#!/bin/bash

killtree() {
    local _pid=$1
    local _sig=${2:--TERM}
    kill -stop ${_pid} # needed to stop quickly forking parent from producing children between child killing and parent killing
    for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
        killtree ${_child} ${_sig}
    done
    kill -${_sig} ${_pid}
}

if [ $# -eq 0 -o $# -gt 2 ]; then
    echo "Usage: $(basename $0) <pid> [signal]"
    exit 1
fi

killtree $@

其他回答

下面是@zhigang的答案的一个变种,它没有AWK,只依赖于Bash的本地解析可能性:

function killtree {
  kill -STOP "$1"
  ps -e -o pid= -o ppid= | while read -r pid ppid
                           do
                             [[ $ppid = $1 ]] || continue
                             killtree "$pid"  || true # Skip over failures
                           done
  kill -CONT "$1"          
  kill -TERM "$1"
}

它在mac和Linux上都运行得很好。在您不能依赖于管理流程组的情况下——比如在编写测试必须在多个环境中构建的软件的脚本时——这种遍历树技术绝对是有用的。

只使用属于该组的进程名删除该组:

kill -- -$(ps -ae o pid,pgrp,cmd | grep "[e]xample.py" | awk '{print $2}' | tail -1)

这是对olibre的答案的修改,但你不需要知道PID,只需要知道组中成员的名字。

解释:

要获得组id,您可以使用如下所示的参数执行ps命令,为您的命令grep它,但是使用引号格式化example.py,并使用括号作为第一个字母(这将过滤掉grep命令本身),然后通过awk过滤它以获得第二个字段,即组id。尾部的-1可以去除重复的组id。您使用$()语法将所有这些放到一个变量中,瞧——您得到了组id。用$(mess)替换上面的-groupid。

brad的答案也是我推荐的,除了如果你使用——ppid选项ps,你可以完全放弃awk。

for child in $(ps -o pid -ax --ppid $PPID) do ....... done

要递归地杀死进程树,请使用killtree():

#!/bin/bash

killtree() {
    local _pid=$1
    local _sig=${2:--TERM}
    kill -stop ${_pid} # needed to stop quickly forking parent from producing children between child killing and parent killing
    for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
        killtree ${_child} ${_sig}
    done
    kill -${_sig} ${_pid}
}

if [ $# -eq 0 -o $# -gt 2 ]; then
    echo "Usage: $(basename $0) <pid> [signal]"
    exit 1
fi

killtree $@

现在是一些聪明的shell编程。

这种解决方案是有代价的,但至少它是基于日常迭代和递归的。通过仔细注意排版命令并在适当的地方将它们转换为声明或本地,可以将其转换为bash。

讨论

当终止一个进程时,必须面对这样的现实:它可能是许多子进程的父进程,而每个子进程又可能是更多子进程的父进程,等等等等。

怎么办呢?

如果有一个函数来测试进程是否有子进程,以及另一个函数来返回父进程的子pid就好了。

这样,游戏就简单多了,因为你可以创建一个循环来遍历pid列表,在杀死它之前检查每个pid是否有子代。如果没有子进程,则直接终止该进程。如果有子函数,递归地调用驱动函数,并将获得父函数子函数pid的函数的结果传递给它。

基本案例操作(流程没有子过程)。

#!/bin/ksh

function killProcess ()
{
    typeset -r PID=$1

    if [[ ! isProcess $PID ]]
    then
        echo -e "Process $PID cannot be terminated because it does not exist.\n" 1>&2
        return 1
    elif [[ kill -s TERM $PID ]] && [[ ! isProcess $PID ]]
    then
        echo -e "Process $PID was terminated.\n" 1>&2
        return 0
    elif kill -s KILL $PID
        echo -e "Process $PID killed with SIGKILL (9) signal. No time to clean up potential files.\n" 1>&2
        return 0
    elif isZombie $PID
    then
        echo -e "Process $PID in the zombie status.\n" 1>&2 
        return 2
    else
        echo -e "Process $PID is alive. SIGTERM and SIGKILL had no effect. It is not a zombie.\n" 1>&2
    fi

    return 3
}

function attemptToKillPid ()
{
    typeset -r PID=$1

    if killProcess $PID
    then 
        return 0
    fi

    ppid=$(getParentPid $pid)
    echo -e "Process $pid of parent $ppid was not able to be killed.\n" 1>&2
    return 1
}

一般案例操作(流程有子)。

function killPidFamily ()
{
    typeset -r PROCESSES=$*
    typeset -ir NUM_PROCESSES_TO_KILL=$(countLines $PROCESSES)
    typeset -i numKilledProcesses=0
    typeset ppid

    for pid in $PROCESSES
    do
        pid=$(trim $pid)

        if ! hasChildPids $pid
        then
            attemptToKillPid $pid && (( numKilledProcesses++ ))
        else
            killPidFamily $(getChildPids $pid) && attemptToKillPid $pid && (( numKilledProcesses++ ))
        fi
    done

    (( numKilledProcesses == NUM_PROCESSES_TO_KILL ))
    return $?
}

支持函数库。

#!/bin/ksh

function trim ()
{
    echo -n "$1" | tr -d [:space:]
}

function countLines ()
{
    typeset -r $LIST=$*
    trim $(echo $LIST | wc -l | awk {'print $1'})
}

function getProcesses ()
{
    # NOTE: -o pgid below would be $4 in awk.

    ps -e -o comm,pid,ppid,pgid,user,ruid,euid,group,rgid,egid,etime,etimes,stat --no-headers
}

function getProcess ()
{
   typeset -r PID=$1
   ps -p $PID -o comm,pid,ppid,pgid,user,ruid,euid,group,rgid,egid,etime,etimes,stat --no-headers
}

function isProcess ()
{
    typeset -r PID=$1

    ps -p $PID -o pid --no-headers 1>&2
    return $?
}

function getProcessStatus ()
{
    typeset -r PID=$1
    trim $(ps -p $PID -o stat --no-headers)
}

function isZombie ()
{
    typeset -r PID=$1
    typeset processStatus

    processStatus=$(getProcessStatus $PID)

    [[ "$processStatus" == "Z" ]]
    return $?
}

function hasChildPids ()
{
    typeset -r PPID=$1
    echo $(getProcesses) | awk '{print $3}' | sort -n | uniq | grep "^${PPID}$"
    return $?
}

function getChildPids ()
{
    typeset -r PPID=$1
    echo $(getProcesses) | awk '{print $2, $3}' | sort -k 2 | awk "\$2 == $PPID {print \$1}" | sort -n
}

function getParentPid ()
{
    typeset -r PID=$1
    trim $(echo $(getProcess $PID) | awk '{print $3}')
}

通过这种方式,您可以确定流程树正在从分支被销毁,并向上移动到根。这有助于避免潜在的僵尸和其他不受欢迎的情况。

现在,您已经看到了执行此操作的最昂贵的方法(一次杀死一个进程),研究如何更改此解决方案以使用PGID(进程组ID)。getProcesses()函数已经打印了PGID(在awk中为4美元),所以学习如何使用它,或者不要使用它。