向进程组的所有成员发送信号的最佳方式是什么?

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

421485 次浏览

如果你知道传递父进程的pid,下面这个shell脚本应该可以工作:

for child in $(ps -o pid,ppid -ax | \
awk "{ if ( \$2 == $pid ) { print \$1 }}")
do
echo "Killing child process $child because ppid = $pid"
kill $child
done

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

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

你没有说你想要杀死的树是否是一个单独的进程组。(如果树是从服务器启动或shell命令行派生出来的,通常会出现这种情况。)您可以使用GNU ps发现进程组,如下所示:

 ps x -o  "%p %r %y %x %c "

如果它是您想要终止的进程组,只需使用kill(1)命令,但不给它一个进程号,而是给它组号的否定。例如,要终止5112组中的每个进程,使用kill -TERM -- -5112

谢谢你们的智慧。我的脚本在退出时留下了一些子进程,而否定提示使事情变得更容易。我写这个函数是为了在其他脚本中使用:

# kill my group's subprocesses:          killGroup
# kill also myself:                      killGroup -x
# kill another group's subprocesses:     killGroup N
# kill that group all:                   killGroup -x N
# N: PID of the main process (= process group ID).


function killGroup () {
local prid mainpid
case $1 in
-x) [ -n "$2" ] && kill -9 -$2 || kill -9 -$$ ;;
"") mainpid=$$ ;;
*) mainpid=$1 ;;
esac
prid=$(ps ax -o pid,pgid | grep $mainpid)
prid=${prid//$mainpid/}
kill -9 $prid 2>/dev/null
return
}

欢呼。

ps -o pid= --ppid $PPID | xargs kill -9

要递归地杀死进程树,请使用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 $@

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

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

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

我的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

如果你知道你想要杀死的东西的pid,你通常可以从会话id开始,和同一会话中的所有东西。我会仔细检查,但我使用这个脚本在循环中启动rsync,我想要死亡,而不是启动另一个(因为循环),因为如果我只是杀死所有的rsync。

kill $(ps -o pid= -s $(ps -o sess --no-heading --pid 21709))

如果你不知道pid,你仍然可以嵌套更多

kill $(ps -o pid= -s $(ps -o sess --no-heading --pid $(pgrep rsync )))
pkill -TERM -P 27888

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

或者更有力:

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

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

参见这个答案终止所有子代。

为了补充Norman Ramsey的答案,如果您想创建一个进程组,可能值得查看setsid http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html < / p >
setsid()函数将创建一个 新建会话,如果调用进程为 不是流程组组长。在 返回调用过程应 这个新的会话领导者 会话,应为进程组 一个新的过程组的领导者,以及 没有控制终端。 主叫的进程组ID 工艺应设等于 process调用进程号。的 呼叫过程必须是唯一的 新流程组中的流程和 新会话中唯一的进程

我的意思是你可以从头开始创建一个组。我在php中使用这个功能是为了能够在启动整个进程树后杀死它。

这可能是个坏主意。我对你们的评论很感兴趣。

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

使用

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//,/ }

我使用了这里描述的方法的一个稍微修改的版本: https://stackoverflow.com/a/5311362/563175 < / p >

它看起来是这样的:

kill `pstree -p 24901 | sed 's/(/\n(/g' | grep '(' | sed 's/(\(.*\)).*/\1/' | tr "\n" " "`

其中24901是父PID。

它看起来很丑,但它的工作很完美。

修改后的志刚回答:

#!/usr/bin/env bash
set -eu


killtree() {
local pid
for pid; do
kill -stop $pid
local cpid
for cpid in $(pgrep -P $pid); do
killtree $cpid
done
kill $pid
kill -cont $pid
wait $pid 2>/dev/null || true
done
}


cpids() {
local pid=$1 options=${2:-} space=${3:-}
local cpid
for cpid in $(pgrep -P $pid); do
echo "$space$cpid"
if [[ "${options/a/}" != "$options" ]]; then
cpids $cpid "$options" "$space  "
fi
done
}


while true; do sleep 1; done &
cpid=$!
for i in $(seq 1 2); do
cpids $$ a
sleep 1
done
killtree $cpid
echo ---
cpids $$ a

灵感来自< >强ysth评论< / >强

kill -- -PGID

与其给它一个进程号,不如给它一个组的负数 号码。像往常一样,几乎任何命令,如果你想要一个正常的参数 以-开始,不被解释为开关,在它前面加上--

使用进程组ID (PGID)终止属于同一个流程树的所有进程

  • kill -- -$PGID,,使用默认信号(TERM = 15)
  • kill -9 -$PGID,,使用信号KILL (9)

你可以从相同流程树的任意进程id (PID)中检索PGID

  • kill -- -$(ps -o pgid= $PID | grep -o '[0-9]*'),(信号TERM)
  • kill -9 -$(ps -o pgid= $PID | grep -o '[0-9]*'),(信号KILL)

特别感谢tanagerSpeakus$PID剩余空间和OSX兼容性的贡献。

解释

  • kill -9 -"$PGID" =>发送信号9 (KILL)给所有的孩子和孙子…
  • 从树的任意进程id中检索Process-Group-ID,而不仅仅是Process-Parent-IDps opgid= $PID的一个变体是ps opgid= $PID0,其中ps opgid= $PID1可以被ps opgid= $PID2取代。< br >但:
    • PID小于5位并且如唐纳雀所注意到的那样右对齐时,ps插入前导空格。你可以使用:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • 来自OSX的ps总是打印头文件,因此Speakus建议:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
    • 李< / ul > < / >
    • grep -o [0-9]*只打印连续的数字(不打印空格或字母头)。

    更多命令行

    PGID=$(ps -o pgid= $PID | grep -o [0-9]*)
    kill -TERM -"$PGID"  # kill -15
    kill -INT  -"$PGID"  # correspond to [CRTL+C] from keyboard
    kill -QUIT -"$PGID"  # correspond to [CRTL+\] from keyboard
    kill -CONT -"$PGID"  # restart a stopped process (above signals do not kill it)
    sleep 2              # wait terminate process (more time if required)
    kill -KILL -"$PGID"  # kill -9 if it does not intercept signals (or buggy)
    

    限制

    • 正如大卫。休伯特Kario所注意到的,当属于同一棵树的进程调用kill时,kill在终止整个树的杀死之前有杀死自己的风险。
    • 因此,请确保使用具有不同Process-Group-ID的进程运行该命令。

    很长的故事

    > cat run-many-processes.sh
    #!/bin/sh
    echo "ProcessID=$$ begins ($0)"
    ./child.sh background &
    ./child.sh foreground
    echo "ProcessID=$$ ends ($0)"
    
    
    > cat child.sh
    #!/bin/sh
    echo "ProcessID=$$ begins ($0)"
    ./grandchild.sh background &
    ./grandchild.sh foreground
    echo "ProcessID=$$ ends ($0)"
    
    
    > cat grandchild.sh
    #!/bin/sh
    echo "ProcessID=$$ begins ($0)"
    sleep 9999
    echo "ProcessID=$$ ends ($0)"
    

    在后台使用'&'运行进程树

    > ./run-many-processes.sh &
    ProcessID=28957 begins (./run-many-processes.sh)
    ProcessID=28959 begins (./child.sh)
    ProcessID=28958 begins (./child.sh)
    ProcessID=28960 begins (./grandchild.sh)
    ProcessID=28961 begins (./grandchild.sh)
    ProcessID=28962 begins (./grandchild.sh)
    ProcessID=28963 begins (./grandchild.sh)
    
    
    > PID=$!                    # get the Parent Process ID
    > PGID=$(ps opgid= "$PID")  # get the Process Group ID
    
    
    > ps fj
    PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    28348 28349 28349 28349 pts/3    28969 Ss   33021   0:00 -bash
    28349 28957 28957 28349 pts/3    28969 S    33021   0:00  \_ /bin/sh ./run-many-processes.sh
    28957 28958 28957 28349 pts/3    28969 S    33021   0:00  |   \_ /bin/sh ./child.sh background
    28958 28961 28957 28349 pts/3    28969 S    33021   0:00  |   |   \_ /bin/sh ./grandchild.sh background
    28961 28965 28957 28349 pts/3    28969 S    33021   0:00  |   |   |   \_ sleep 9999
    28958 28963 28957 28349 pts/3    28969 S    33021   0:00  |   |   \_ /bin/sh ./grandchild.sh foreground
    28963 28967 28957 28349 pts/3    28969 S    33021   0:00  |   |       \_ sleep 9999
    28957 28959 28957 28349 pts/3    28969 S    33021   0:00  |   \_ /bin/sh ./child.sh foreground
    28959 28960 28957 28349 pts/3    28969 S    33021   0:00  |       \_ /bin/sh ./grandchild.sh background
    28960 28964 28957 28349 pts/3    28969 S    33021   0:00  |       |   \_ sleep 9999
    28959 28962 28957 28349 pts/3    28969 S    33021   0:00  |       \_ /bin/sh ./grandchild.sh foreground
    28962 28966 28957 28349 pts/3    28969 S    33021   0:00  |           \_ sleep 9999
    28349 28969 28969 28349 pts/3    28969 R+   33021   0:00  \_ ps fj
    

    命令pkill -P $PID不会杀死孙子:

    > pkill -P "$PID"
    ./run-many-processes.sh: line 4: 28958 Terminated              ./child.sh background
    ./run-many-processes.sh: line 4: 28959 Terminated              ./child.sh foreground
    ProcessID=28957 ends (./run-many-processes.sh)
    [1]+  Done                    ./run-many-processes.sh
    
    
    > ps fj
    PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    28348 28349 28349 28349 pts/3    28987 Ss   33021   0:00 -bash
    28349 28987 28987 28349 pts/3    28987 R+   33021   0:00  \_ ps fj
    1 28963 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh foreground
    28963 28967 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28962 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh foreground
    28962 28966 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28961 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh background
    28961 28965 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28960 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh background
    28960 28964 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    

    命令kill -- -$PGID杀死所有进程,包括孙子进程。

    > kill --    -"$PGID"  # default signal is TERM (kill -15)
    > kill -CONT -"$PGID"  # awake stopped processes
    > kill -KILL -"$PGID"  # kill -9 to be sure
    
    
    > ps fj
    PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    28348 28349 28349 28349 pts/3    29039 Ss   33021   0:00 -bash
    28349 29039 29039 28349 pts/3    29039 R+   33021   0:00  \_ ps fj
    

    结论

    我注意到在这个例子中PIDPGID等于(28957)。< br > 这就是为什么我最初认为kill -- -$PID就足够了。但如果进程是在Makefile中生成的,则进程ID组ID不同。< br > < / p >

    我认为kill -- -$(ps -o pgid= $PID | grep -o [0-9]*)是当从不同的组ID(另一个进程树)调用时杀死整个进程树的最好的简单技巧。

下面的shell函数与许多其他答案类似,但它在Linux和BSD (OS X等)上都可以工作,没有像pgrep这样的外部依赖:

killtree() {
local parent=$1 child
for child in $(ps -o ppid= -o pid= | awk "\$1==$parent {print \$2}"); do
killtree $child
done
kill $parent
}

在python中使用psutil非常容易做到这一点。只要用pip安装psutil,你就有了一整套进程操作工具:

def killChildren(pid):
parent = psutil.Process(pid)
for child in parent.get_children(True):
if child.is_running():
child.terminate()

我不能评论(没有足够的声誉),所以我被迫添加一个新的回答,即使这不是一个真正的答案。

@olibre在2月28日给出了一个非常好的、彻底的回答,但有一个小问题。ps opgid= $PID的输出将包含小于5位PID的前导空格,因为ps正在对列进行对齐(将数字右对齐)。在整个命令行中,这会导致一个负号,后面跟着空格(s),然后是组PID。简单的解决方案是通过管道pstr来删除空格:

kill -- -$( ps opgid= $PID | tr -d ' ' )

根据志刚的回答,这可以避免自我毁灭:

init_killtree() {
local pid=$1 child


for child in $(pgrep -P $pid); do
init_killtree $child
done
[ $pid -ne $$ ] && kill -kill $pid
}

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

如果你想按名称杀死一个进程:

killall -9 -g someprocessname

pgrep someprocessname | xargs pkill -9 -g

在sh中,jobs命令将列出后台进程。在某些情况下,最好先杀死最新的进程,例如旧进程创建了一个共享套接字。在这些情况下,将pid按相反的顺序排序。有时您希望在作业停止之前等待作业在磁盘上写入一些内容或类似的东西。

如果没必要就不要杀人!

for SIGNAL in TERM KILL; do
for CHILD in $(jobs -s|sort -r); do
kill -s $SIGNAL $CHILD
sleep $MOMENT
done
done

type ps -ef检查进程id。 通过输入kill -9 <pid>

来终止进程

在shell脚本中杀死子进程:

很多时候我们需要杀死因为某些原因被挂起或阻塞的子进程。如。FTP连接问题。

有两种方法,

1)为每个子进程创建独立的新父进程,该父进程将在超时时监视并终止子进程。

创建test.sh,如下所示:

#!/bin/bash


declare -a CMDs=("AAA" "BBB" "CCC" "DDD")
for CMD in ${CMDs[*]}; do
(sleep 10 & PID=$!; echo "Started $CMD => $PID"; sleep 5; echo "Killing $CMD => $PID"; kill $PID; echo "$CMD Completed.") &
done
exit;

使用以下命令查看其他终端中名称为“test”的进程。

watch -n1 'ps x -o "%p %r %c" | grep "test" '
上面的脚本将创建4个子进程和它们的父进程。每个子进程将运行10秒。但是一旦达到5秒的超时,它们各自的父进程将终止这些子进程。 所以孩子将无法完成执行(10秒)。 围绕这些时间(切换10和5)来玩游戏,看看另一种行为。在这种情况下,子进程将在5秒内完成执行,直到超时10秒

2)让当前父进程监视并在超时时终止子进程。这不会创建单独的父节点来监视每个子节点。此外,您还可以在同一个父进程中正确地管理所有子进程。

创建test.sh,如下所示:

#!/bin/bash


declare -A CPIDs;
declare -a CMDs=("AAA" "BBB" "CCC" "DDD")


CMD_TIME=15;
for CMD in ${CMDs[*]}; do
(echo "Started..$CMD"; sleep $CMD_TIME; echo "$CMD Done";) &
CPIDs[$!]="$RN";
sleep 1;
done


GPID=$(ps -o pgid= $$);
CNT_TIME_OUT=10;
CNT=0;
while (true); do
declare -A TMP_CPIDs;


for PID in "${!CPIDs[@]}"; do
echo "Checking "${CPIDs[$PID]}"=>"$PID;


if ps -p $PID > /dev/null ; then
echo "-->"${CPIDs[$PID]}"=>"$PID" is running..";
TMP_CPIDs[$PID]=${CPIDs[$PID]};
else
echo "-->"${CPIDs[$PID]}"=>"$PID" is completed.";
fi
done


if [ ${#TMP_CPIDs[@]} == 0 ]; then
echo "All commands completed.";
break;
else
unset CPIDs;
declare -A CPIDs;
for PID in "${!TMP_CPIDs[@]}"; do
CPIDs[$PID]=${TMP_CPIDs[$PID]};
done
unset TMP_CPIDs;


if [ $CNT -gt $CNT_TIME_OUT ]; then
echo ${CPIDs[@]}"PIDs not reponding. Timeout reached $CNT sec. killing all childern with GPID $GPID..";
kill -- -$GPID;
fi
fi


CNT=$((CNT+1));
echo "waiting since $b secs..";
sleep 1;
done


exit;

使用以下命令查看其他终端中名称为“test”的进程。

watch -n1 'ps x -o "%p %r %c" | grep "test" '
上面的脚本将创建4个子进程。我们存储所有子进程的pid,并循环遍历它们,以检查它们是否已完成执行或仍在运行。 子进程将一直执行到CMD_TIME时间。但如果CNT_TIME_OUT超时,所有子进程都将被父进程终止。 您可以切换时间并使用脚本查看行为。 这种方法的一个缺点是,它使用group id来杀死所有子树。但是父进程本身属于同一个组,所以它也会被杀死

如果不希望终止父进程,可能需要为父进程分配其他组id。

更多细节可以在这里找到,

在shell脚本中杀死子进程

下面的代码已经在FreeBSD, Linux和MacOS X上进行了测试,只依赖于pgrep和kill (ps -o版本在BSD下不工作)。第一个参数是父pid,子pid必须被终止。第二个参数是一个布尔值,用于确定父pid是否也必须终止。

KillChilds() {
local pid="${1}"
local self="${2:-false}"


if children="$(pgrep -P "$pid")"; then
for child in $children; do
KillChilds "$child" true
done
fi


if [ "$self" == true ]; then
kill -s SIGTERM "$pid" || (sleep 10 && kill -9 "$pid" &)
fi
}


KillChilds $$ > /dev/null 2>&1

这将向shell脚本中的任何子/孙辈进程发送SIGTERM,如果SIGTERM没有成功,它将等待10秒,然后发送kill。


答:早些时候

下面的代码也可以工作,但是会在BSD上杀死shell本身。

KillSubTree() {
local parent="${1}"
for child in $(ps -o pid=$parent); do
if [ $$ -ne $child ]; then (kill -s SIGTERM $child || (sleep 10 && kill -9 $child & )) > /dev/null 2>&1 ; fi
done
}
# Example lanch from within script
KillSubTree $$ > /dev/null 2>&1

这个脚本也可以工作:

< p > <代码> # / bin / sh 而真正的 做 输入父进程id [type quit for exit] 读ppid if [$ppid -eq "quit" -o $ppid -eq "quit"] 退出0 fi I in ' ps -ef| awk '$3 == '$ppid' {print $2}' ' 做 Echo杀死$i 杀死我美元 完成 完成 < /代码> < / p >

pslist包中的rkill命令向指定进程及其所有子进程发送给定信号(默认为SIGTERM):

rkill [-SIG] pid/name...

我进一步开发了志刚、木uri和固颈的溶液:

 #!/bin/bash


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


_pid=$1
_sig=${2:-TERM}


# echo >&2 "killtree($_pid) mypid = $$"
# ps axwwf | grep -6 "^[ ]*$_pid " >&2 ;


function _killtree () {
local _children
local _child
local _success


if test $1 -eq $2 ; then # this is killtree - don't commit suicide!
echo >&2 "killtree can´t kill it´s own branch - some processes will survive." ;
return 1 ;
fi ;
# this avoids that children are spawned or disappear.
kill -SIGSTOP $2 ;


_children=$(ps -o pid --no-headers --ppid $2) ;
_success=0
for _child in ${_children}; do
_killtree $1 ${_child} $3 ;
_success=$(($_success+$?)) ;
done ;


if test $_success -eq 0 ; then
kill -$3 $2
fi ;
# when a stopped process is killed, it will linger in the system until it is continued
kill -SIGCONT $2
test $_success -eq 0 ;
return $?
}


_killtree $$ $_pid $_sig

这个版本将避免杀死它的祖先进程——在以前的解决方案中,这会导致大量的子进程。

在确定子列表之前适当地停止进程,这样就不会创建或消失新的子列表。

停止的作业被杀死后,必须继续执行,才能从系统中消失。

老问题,我知道,但所有的回答似乎都叫ps,我不喜欢。

这个基于awc的解决方案不需要递归,只调用ps一次。

awk 'BEGIN {
p=1390
while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2
o=1
while (o==1) {
o=0
split(p, q, " ")
for (i in q) if (a[q[i]]!="") {
p=p""a[q[i]]
o=1
a[q[i]]=""
}
}
system("kill -TERM "p)
}'

或单行:

awk 'BEGIN {p=1390;while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2;o=1;while (o==1) {o=0;split(p, q, " ");for (i in q) {if (a[q[i]]!="") {p=p""a[q[i]];o=1;a[q[i]]=""}}}system("kill -TERM "p)}'

基本思想是,我们建立一个父:子条目的数组(a),然后循环数组为匹配的父元素查找子元素,并将它们添加到我们的父元素列表(p)中。

如果您不想终止顶级进程,则执行

sub(/[0-9]*/, "", p)

就在system()行将它从kill集中移除之前。

请记住,这里存在一个竞争条件,但这对所有的解都是正确的(据我所知)。它做了我所需要的,因为我需要它的脚本不会创建大量短暂的子进程。

对于读者来说,一个练习是将其设置为2次循环:在第一次传递之后,将SIGSTOP发送给p列表中的所有进程,然后再次循环运行ps,在第二次传递之后发送SIGTERM,然后是SIGCONT。如果你不关心美好的结局,那么第二遍可能只是SIGKILL,我想。

我知道这很老了,但这是我找到的更好的解决方案:

killtree() {
for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
echo Terminating: $p
kill $p
done
}

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

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。

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

这种解决方案是有代价的,但至少它是基于日常迭代和递归的。这可以通过仔细注意typeset命令并在适当的地方将它们转换为declarelocal来转换为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),所以学习如何使用它,或者不要使用它。

立即终止整个进程组,就像^C所做的那样:

PID="$(pgrep -f unique_command_line_part)"
if [[ -n "$PID" ]]
then
PGID="$(ps --no-headers -p $PID -o pgid)"
kill -SIGINT -- -${PGID// /}
fi

每一行都在这个答案中解释