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

Ada*_*eck 409 linux shell signals process

我想杀死整个进程树.使用任何常用脚本语言执行此操作的最佳方法是什么?我正在寻找一个简单的解决方案.

Nor*_*sey 295

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

 ps x -o  "%p %r %y %x %c "
Run Code Online (Sandbox Code Playgroud)

如果它是您要杀死的进程组,只需使用该kill(1)命令,而不是给它一个进程号,给它取消组号.例如,要杀死组5112中的每个进程,请使用kill -TERM -- -5112.

  • 像往常几乎任何命令一样,如果你想要一个以 - 开头的正常参数 - 不被解释为开关,在它前面加上 - :kill - -GPID (51认同)
  • `pgrep`可以提供一种更简单的方法来查找进程组ID.例如,要杀死my-script.sh的进程组,请运行`kill -TERM - $(pgrep -o my-script.sh)`. (9认同)
  • 更好地了解http://stackoverflow.com/questions/392022/best-way-to-kill-all-child-processes/6481337#6481337它是迄今为止更优雅的解决方案,如果你需要列出孩子的pids然后使用:`ps -o pid --no-headers --ppid $ PARENT_PID` (9认同)
  • 如果您稍微修改格式并进行排序,您可以看到所有进程都很好地分组,并从(可能)每组中的组父组开始:`ps x -o"%r%p%y%x%c"| sort -nk1,2` (4认同)
  • kill -74313 -bash:kill:74313:无效的信号规范如果我添加kill -15 -GPID它完美无缺. (3认同)
  • 执行诸如`prog1&prog2&wait`之类的shell脚本往往不会在Ctrl + C上杀死prog1/prog2.您可以通过将其放在脚本的顶部来解决这个问题:`trap"kill - -0"EXIT`,它将SIGTERM发送到该脚本的整个进程组. (3认同)
  • +1谢谢,我从你的回答中得到了启发[我的回答](http://stackoverflow.com/a/15139734/938111).我已经决定写另一个答案,因为你没有解释如何获得_Process组ID(PGID)_.当我们使用_Parent进程ID(PPID)_在shell中尝试您的答案时,它可以工作,因为两者(PGID和PPID)都是等于.但是当树从另一个已终止的进程生成时,它就会失败.干杯;-) (2认同)
  • Bash的杀戮没有用.我必须运行`/ bin/kill -9 - - $ GPID` (2认同)

oli*_*bre 193

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

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

您可以PGID从同一进程树的任何Process-ID(PID)中检索

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

特别感谢唐加拉雀Speakus的捐款$PID剩余空间和OSX的兼容性.

说明

  • kill -9 -"$PGID"=> KILL向所有孩子和孙子发送信号9()...
  • PGID=$(ps opgid= "$PID")=> 从树的任何进程ID中检索Process-Group-ID,而不仅仅是Process-Parent-ID.的变型就是其中可以被替换.但: ps opgid= $PIDps -o pgid --no-headers $PIDpgidpgrp
    • psPID小于五位数时插入前导空格,并在唐纳德注意到时右对齐.您可以使用:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • ps从OSX始终打印标题,因此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)
Run Code Online (Sandbox Code Playgroud)

局限性

  • 正如davideHubert 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 Code Online (Sandbox Code Playgroud)

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

> ./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
Run Code Online (Sandbox Code Playgroud)

该命令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
Run Code Online (Sandbox Code Playgroud)

该命令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
Run Code Online (Sandbox Code Playgroud)

结论

我注意到在这个例子中PIDPGID是相等的(28957).
这就是我原本认为kill -- -$PID足够的原因.但是,如果进程是在进程内生成的,Makefile进程ID组ID不同.

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

  • 这仅在子命令本身不成为组长时才有效.甚至像"man"这样的简单工具也能做到这一点.另一方面,如果你想从子进程中杀死gandchild进程,`kill - - $ pid`将不起作用.所以它不是通用的解决方案. (3认同)

Onl*_*job 165

pkill -TERM -P 27888
Run Code Online (Sandbox Code Playgroud)

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

或者更强大:

CPIDS=$(pgrep -P 27888); (sleep 33 && kill -KILL $CPIDS &); kill -TERM $CPIDS
Run Code Online (Sandbox Code Playgroud)

计划在33秒后杀人并礼貌地要求程序终止.

请参阅此答案以终止所有后代.

  • 在我的快速测试中,pgrep只报告直接的孩子,所以这可能不会杀死整个层次结构. (19认同)
  • 我同意@haridsv:`pkill -P`只向孩子发送信号=>孙子没有收到信号=>因此我已经知道了[另一个答案](http://stackoverflow.com/a/15139734/ 938111)解释一下.干杯;-) (10认同)
  • @Onlyjob,睡觉然后杀死是不是很危险?在此期间,操作系统可能会重复使用进程ID:您可能正在杀死不再是您孩子的进程.我怀疑必须再次拨打pkill,以确保不会这样做. (3认同)
  • @Onlyjob仅供参考,我能够在不到19秒的时间内在计算机上生成32768个单线程进程,并具有轻量的I / O访问:`{1..32768}中$ i的时间;做(echo $ BASHPID >> pids); 完成实际的0m18.860s` (2认同)

zhi*_*ang 100

要以递归方式终止进程树,请使用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 $@
Run Code Online (Sandbox Code Playgroud)

  • 使用SIGTERM不会杀死已停止的进程.见我的[回答](http://stackoverflow.com/a/13481601/52499) (5认同)
  • `ps`的` - `参数在OS X上不起作用.为了使它工作,用`ps ax -o'替换`ps`命令pid = ppid ="| grep -E"$ {_ regex}"| sed -E"s/$ {_ regex} /\1/g`其中`_regex`在`for`循环之前定义:`local _regex ="[]*([0-9] +)[] + $ { _pid}"` (3认同)
  • 如果`ps`不支持`--ppid`,可以使用`pgrep -P $ {_ pid}`代替 (3认同)

Onl*_*job 16

来自 pslist包的 rkill命令将给定信号(或SIGTERM默认情况下)发送到指定进程及其所有后代:

rkill [-SIG] pid/name...
Run Code Online (Sandbox Code Playgroud)

  • 最后,真正有效的解决方案:杀死整个后代树(而“kill”和“pkill”没有) (3认同)

Kim*_*bel 11

布拉德的答案也是我推荐的,除非你可以awk完全取消,如果你使用--ppid选项ps.

for child in $(ps -o pid -ax --ppid $PPID) do ....... done
Run Code Online (Sandbox Code Playgroud)


小智 9

如果你知道传递父进程的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
Run Code Online (Sandbox Code Playgroud)


小智 9

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

所以看起来像这样:

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

其中24901是父母的PID.

它看起来很丑陋,但它的工作完美无缺.

  • 你应该将`-l`添加到`pstree`,这样长行不会被截断; 使用`kill \`pstree -l -p 24901 | grep"([[:digit:]]*)" - o | tr -d'()'\``可以使它更简单易读(不需要将`\n`转换为空格,因为它可以正常工作),thx! (2认同)

x-y*_*uri 9

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
Run Code Online (Sandbox Code Playgroud)


tan*_*ger 9

我无法评论(声誉不够),所以我被迫添加一个新的答案,即使这不是一个真正的答案.

@olibre在2月28日给出的非常好的和彻底的答案存在一些问题.输出ps opgid= $PID将包含一个短于五位数的PID的前导空格,因为这样ps可以证明该列的合理性(严格对齐数字).在整个命令行中,这会产生一个负号,后跟空格,然后是组PID.简单的解决方法是管道ps,以tr去除空间:

kill -- -$( ps opgid= $PID | tr -d ' ' )
Run Code Online (Sandbox Code Playgroud)


haj*_*mie 8

要添加Norman Ramsey的答案,如果要创建进程组,可能值得查看setsid.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

如果调用进程不是进程组领导者,则setsid()函数将创建新会话.返回时,调用进程应该是该新进程的会话负责人,应该是新进程组的进程组负责人,并且没有控制终端.调用进程的进程组ID应设置为等于调用进程的进程ID.调用进程应该是新进程组中的唯一进程,也是新进程中唯一的进程.

我认为这意味着您可以从启动过程创建一个组.我在php中使用它,以便能够在启动后杀死整个进程树.

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


小智 7

受到ysth评论的启发

kill -- -PGID
Run Code Online (Sandbox Code Playgroud)

而不是给它一个进程号,给它否定组号.像往常几乎任何命令一样,如果你想要一个以a开头的正常参数,-而不是被解释为一个开关,那么在它之前--


Dav*_*ger 5

以下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
}
Run Code Online (Sandbox Code Playgroud)


小智 5

使用psutil使用python执行此操作非常容易.只需使用pip安装psutil,然后就可以使用一整套流程操作工具:

def killChildren(pid):
    parent = psutil.Process(pid)
    for child in parent.get_children(True):
        if child.is_running():
            child.terminate()
Run Code Online (Sandbox Code Playgroud)


dav*_*ide 5

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

init_killtree() {
    local pid=$1 child

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


Mik*_*nen 5

如果您想按名称终止进程:

killall -9 -g someprocessname
Run Code Online (Sandbox Code Playgroud)

或者

pgrep someprocessname | xargs pkill -9 -g
Run Code Online (Sandbox Code Playgroud)