杀死所有subprocess的最好方法

我想杀死整个进程树。 使用任何通用脚本语言来做这件事的最好方法是什么? 我正在寻找一个简单的解决scheme。

你不会说如果你想杀死的树是一个进程组。 (如果树是从服务器启动或shell命令行分叉的结果,那么通常就是这种情况。)您可以使用GNU ps发现进程组,如下所示:

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

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

使用进程组IDPGID )杀死属于同一进程树的所有进程

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

您可以从同一个进程树的任何进程IDPID )中检索PGID

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

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

说明

  • kill -9 -"$PGID" =>发送信号9( KILL )给所有的孩子和孙子…
  • PGID=$(ps opgid= "$PID") =>从树的任何Process-ID中检索Process-Group-ID ,而不仅仅是Process-Parent-IDps opgid= $PID变体是ps -o pgid --no-headers $PID ,其中pgid可以被pgrpreplace。
    但:
    • PID小于5位时, ps插入前导空格,并且如tanager所注意的那样右alignment。 您可以使用:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • 来自OSX的ps总是打印标题,因此Speakus提出:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • 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) 

局限性

  • 正如davide和Hubert Kario所注意到的那样,当一个属于同一棵树的进程调用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 )。
这就是为什么我原本以为kill -- -$PID就够了。 但是,如果进程在Makefile产生,则进程ID组ID不同。

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

 pkill -TERM -P 27888 

这将终止具有父进程ID 27888的所有进程。

或更健壮:

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

该计划杀死33秒后,礼貌地要求进程终止。

看到这个答案终止所有的后代。

要recursion杀死进程树,使用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 $@ 

布拉德的回答是,我也build议,除了你可以完全不使用awk如果你使用--ppid选项到ps

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

如果你知道传递父进程的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 

我使用这里描述的方法的一点点修改版本: https : //stackoverflow.com/a/5311362/563175

所以看起来像这样:

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

其中24901是父母的PID。

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

zhigang的修改版本的答案是:

 #!/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 

要添加到Norman Ramsey的答案,如果您想创build一个进程组,可能值得在setsid处查看。
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

如果调用进程不是进程组的领导,setsid()函数将创build一个新的会话。 返回时,调用进程为本次会话的会话负责人,为新进程组的进程组组长,无控制terminal。 调用进程的进程组ID必须设置为与调用进程的进程ID相同。 调用进程应该是新进程组中的唯一进程,也是新进程中唯一的进程。

我认为你可以从启动过程创build一个组。 我在PHP中使用这个,以便能够在启动后杀死整个进程树。

这可能是一个坏主意。 我会对评论感兴趣。

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

在2月28日@olibre给出的答案中有一个小问题ps opgid= $PID的输出将包含一个比5位小的PID的前导空格,因为ps是alignment列(rigth align号)。 在整个命令行中,这将导致负号,然后是空格,然后是组PID。 简单的解决办法是pipe道pstr删除空格:

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

来自pslist软件包的rkill命令将给定的信号(或者SIGTERM默认)发送给指定的进程及其所有的subprocess:

 rkill [-SIG] pid/name... 

ysth的评论启发

 kill -- -PGID 

而不是给它一个进程号码,给它的组号码的否定。 像往常一样,几乎所有的命令,如果你想要一个正常的参数,以-开头,而不是解释为一个开关,在它之前--

下面的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() 

基于志刚的回答,这样可以避免自杀:

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

这是我使用bash脚本查杀所有subprocess的版本。 它不使用recursion并依赖于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//,/ } 

如果你想通过名字杀死一个进程:

 killall -9 -g someprocessname 

要么

 pgrep someprocessname | xargs pkill -9 -g 

这里是@ zhigang的答案的一个变种,它没有AWK,只依赖于Bash的原生parsing可能性:

 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上似乎都能正常工作。 在不能pipe理进程组的情况下(比如编写脚本来testing一个必须在多个环境中构build的软件)时,这种行走技术肯定是有帮助的。

在孩子面前杀死父母可能会更好; 否则父母可能会在自己被杀之前再次产卵。 这些将在谋杀中幸存下来。

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

以下已经在FreeBSD,Linux和MacOS X上进行了testing,只依赖于pgrep和kill(ps -o版本在BSD下不工作)。 第一个论据是父母的子女必须被终止。 第二个参数是一个布尔值,以确定父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 

这会将SIGTERM发送到shell脚本中的任何子孙进程,如果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 

进一步发展智岗,木uri和固体的解决scheme:

  #!/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 

这个版本将避免杀死它的祖先 – 这在以前的解决scheme中导致大量的subprocess。

在子列表被确定之前,进程被正确地停止,以便没有新的subprocess被创build或消失。

死亡后,停止的工作必须继续从系统中消失。

感谢您的智慧,乡亲们。 我的脚本在退出时留下了一些subprocess, 否定提示使事情变得更容易。 我写了这个函数在必要时用于其他脚本:

 # 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 } 

干杯。

如果你的系统上有pstree和perl,你可以试试这个:

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

如果你知道你想杀的东西的pid,你通常可以从session ID和同一个session中的所有东西中去。 我仔细检查,但我用这个脚本启动rsyncs在我想死循环,而不是启动另一个(因为循环),因为它会,如果我只是killall'd 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 ))) 
 ps -o pid= --ppid $PPID | xargs kill -9 

在shell脚本中杀死subprocess:

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

有两种方法,

1)为每个孩子创build单独的新父母,一旦超时达到,将监视并终止subprocess。

如下创buildtest.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; 

并使用以下命令观察其他terminal中名称为“testing”的进程。

 watch -n1 'ps x -o "%p %r %c" | grep "test" ' 

以上脚本将创build4个新的subprocess及其父母。 每个subprocess将运行10秒。 但是,一旦5秒的超时,他们各自的父进程将杀死这些孩子。 所以孩子将无法完成执行(10秒)。 玩这些时间(开关10和5)看看另一个行为。 在这种情况下,孩子将在5秒内完成执行,然后达到10秒的超时时间。

2)让当前父进程监视并杀死subprocess一旦超时。 这不会创build单独的父母来监视每个孩子。 您也可以在同一个家长内正确pipe理所有subprocess。

如下创buildtest.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; 

并使用以下命令观察其他terminal中名称为“testing”的进程。

 watch -n1 'ps x -o "%p %r %c" | grep "test" ' 

上面的脚本将创build4个新的subprocess。 我们正在存储所有subprocess的pid,并循环它们以检查它们是否完成了执行或仍在运行。 subprocess将执行到CMD_TIME时间。 但是,如果CNT_TIME_OUT超时达到,所有的孩子将被父进程杀死。 您可以切换时间并使用脚本来查看行为。 这种方法的一个缺点是,它使用组ID来杀死所有的子树。 但是父进程本身属于同一个组,所以也会被杀死。

如果不希望父项被杀害,则可能需要将其他组ID分配给父项处理。

更多细节可以在这里find,

在shell脚本中杀死subprocess

这个脚本也起作用:

#/bin/sh while true do echo "Enter parent process id [type quit for exit]" read ppid if [ $ppid -eq "quit" -o $ppid -eq "QUIT" ];then exit 0 fi for i in `ps -ef| awk '$3 == '$ppid' { print $2 }'` do echo killing $i kill $i done done

老问题,我知道,但所有的反应似乎一直在调用ps,我不喜欢。

这个基于awk的解决scheme不需要recursion,只需要调用一次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)}' 

基本上,我们的想法是,我们build立一个数组(a)parent:child条目,然后在数组中循环寻找匹配父母的孩子,并将其添加到我们的父母列表(p)中。

如果你不想杀死顶级进程,那就干吧

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

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

请记住,这里存在竞争条件,但所有解决scheme都是如此(据我所知)。 它做我需要的,因为我需要的脚本不会创造大量的短命的孩子。

读者的一个练习是使其成为一个2遍循环:在第一遍之后,发送SIGSTOP到p列表中的所有进程,然后循环再次运行ps,在第二遍之后发送SIGTERM,然后是SIGCONT。 如果你不关心美好的结局,那么我想第二遍可能就是SIGKILL。

sh中的jobs命令将列出后台进程。 在某些情况下,最好先杀死最新的进程,例如较老的进程创build共享套接字。 在这些情况下,按照相反的顺序对PID进行sorting。 有时候你想等工作在磁盘上写东西的时候,或者在停止之前就写这样的东西。

如果你不需要,也不要杀人!

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

键入ps -ef检查进程ID。 通过键入kill -9 <pid>kill -9 <pid>进程