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


当前回答

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

其他回答

pkill -TERM -P 27888

这将杀死父进程ID为27888的所有进程。

或者更有力:

CPIDS=$(pgrep -P 27888); (sleep 33 && kill -KILL $CPIDS &); kill -TERM $CPIDS

它将kill安排在33秒后,并礼貌地要求进程终止。

请参阅终止所有子代的答案。

这是我使用bash脚本杀死所有子进程的版本。 它不使用递归,依赖于pgrep命令。

Use

killtree.sh PID SIGNAL

killtrees.sh的内容

#!/bin/bash
PID=$1
if [ -z $PID ];
then
    echo "No pid specified"
fi

PPLIST=$PID
CHILD_LIST=`pgrep -P $PPLIST -d,`

while [ ! -z "$CHILD_LIST" ]
do
    PPLIST="$PPLIST,$CHILD_LIST"
    CHILD_LIST=`pgrep -P $CHILD_LIST -d,`
done

SIGNAL=$2

if [ -z $SIGNAL ]
then
    SIGNAL="TERM"
fi
#do substring from comma to space
kill -$SIGNAL ${PPLIST//,/ }

在杀死孩子之前杀死父母可能更好;否则,父母可能会在他自杀之前再次繁殖新的孩子。这些会在杀戮中幸存下来。

我的ps版本和上面的不一样;也许太老了,所以奇怪的握手……

使用shell脚本而不是shell函数有很多优点…

然而,这基本上是志刚的想法


#!/bin/bash
if test $# -lt 1 ; then
    echo >&2 "usage: kiltree pid (sig)"
fi ;

_pid=$1
_sig=${2:-TERM}
_children=$(ps j | grep "^[ ]*${_pid} " | cut -c 7-11) ;
echo >&2 kill -${_sig} ${_pid}
kill -${_sig} ${_pid}
for _child in ${_children}; do
    killtree ${_child} ${_sig}
done

现在是一些聪明的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美元),所以学习如何使用它,或者不要使用它。

如果你的系统上有pstrree和perl,你可以尝试这样做:

perl -e 'kill 9, (`pstree -p PID` =~ m/\((\d+)\)/sg)'