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


422

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


3
但是,当系统收割机运行时,僵尸应该消失。我承认我见过僵尸缠绵的系统,但这是不典型的。
Brian Knoblauch

7
有时那些缠绵的僵尸会造成一些恐怖的活动。
User1

使用chronosherodes命令之一。
Michael Le BarbierGrünewald2014年

8
杀死$(pstree <PID> -p -a -l |剪切-d,-f2 |剪切-d''
f1

@MichaelLeBarbierGrünewald您能链接到那些程序吗?
发泡胶飞

Answers:


305

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

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

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


3
kill -74313 -bash:kill:74313:无效的信号规范如果我添加kill -15 -GPID,则效果很好。
亚当·派克

51
与几乎所有命令一样,如果您希望以-开头的普通参数不被解释为开关,请在其前加上-:kill--GPID
ysth

9
pgrep可以提供一种更简单的方法来查找进程组ID。例如,要杀死my-script.sh的进程组,请运行kill -TERM -$(pgrep -o my-script.sh)
乔什·凯利

9
最好看一下stackoverflow.com/questions/392022/…到目前为止,它是一个更优雅的解决方案,如果您需要列出孩子的小费,请使用:ps -o pid --no-headers --ppid $PARENT_PID
SzymonJeż2011年

4
并且,如果您稍微修改格式并进行排序,您会看到所有进程都进行了很好的分组,并从每个组中的父组开始(潜在地):ps x -o "%r %p %y %x %c" | sort -nk1,2
haridsv 2012年

199

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

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

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

  • 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")=> 从树的任何Process-ID中检索Process-Group-ID,而不仅是Process-Parent-ID。的变型就是其中可以被替换。但: ps opgid= $PIDps -o pgid --no-headers $PIDpgidpgrp
    • psPIDtanager所注意,在少于五位数且右对齐时插入前导空格。您可以使用:
      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)

局限性

  • 正如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-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(另一个进程树)调用时,杀死整个进程树是最好的简单技巧。


1
嗨@davide 好问题。我认为kill应该始终在接收到自己的信号之前将信号发送到整个树。但是在某些特定的情况/实现中,kill可能会向自己发送信号,被中断然后接收自己的信号。但是,风险应该足够小,在大多数情况下可以忽略,因为在此之前应该发生其他错误。您可以忽略这种风险吗?此外,其他答案也有此常见错误(kill进程树的一部分被杀死)。希望能有所帮助。。干杯;)
olibre

3
这仅在子命令本身未成为组长的情况下才有效。甚至像这样的简单工具也可以man做到这一点。另一方面,如果要从子进程中杀死gandchild进程,kill -- -$pid将无法正常工作。因此,这不是通用解决方案。
休伯特·卡里奥

1
该示例是试图杀死其子项的“子项”(用户发起命令的子项)。IOW,尝试杀死中的后台进程层次结构child.sh
休伯特·卡里奥

1
> kill -QUIT-“ $ PGID”#与来自键盘的[CRTL + C]相同的信号---- QUIT应该替换为INT为真
Maxim Kholyavkin 2014年

1
OSX选项--no-headers不支持,因此代码应更新为:PGID =“ $(ps -o pgid” $ PID“ | grep [0-9] | tr -d'')”
Maxim Kholyavkin

166
pkill -TERM -P 27888

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

或更强大:

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

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

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


19
在我的快速测试中,pgrep仅报告了直接子节点,因此这可能不会杀死整个层次结构。
haridsv

10
我同意@haridsv:pkill -P仅向孩子发送信号=>孙子没有收到信号=>因此,我写了另一个答案来解释这一点。干杯;-)
olibre 2013年

1
在bash脚本中,使用杀死自己的孩子pkill -TERM -P ${$}
弗朗索瓦博索莱伊

3
@Onlyjob,睡觉然后杀死是不是很危险?进程ID在此期间可能已被OS重用:您可能正在杀死不再是孩子的进程。我怀疑必须再次致电pkill以确保与此相抵触。
弗朗索瓦博索莱伊

2
@Onlyjob仅供参考,我能够在不到19秒的时间$ time for i in {1..32768}; do ( echo $BASHPID >> pids ); done real 0m18.860s
内在

102

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

3
--参数ps做OS X.到不行使其工作有替换ps的命令:ps ax -o "pid= ppid=" | grep -E "${_regex}" | sed -E "s/${_regex}/\1/g这里_regex是前定义for循环:local _regex="[ ]*([0-9]+)[ ]+${_pid}"
阿图尔

5
停止的进程不会被SIGTERM杀死。看到我的答案
x-yuri 2012年

1
-1使用#!/ bin / bash代替#!/ usr / bin / env bash(或者更好的是POSIX仅构造和/ bin / sh)
好人

发送SIGKILL代替SIGTERM是否足以保证其killtree()可靠运行就足够了吗?
davide

3
如果ps不支持--ppid,则可以pgrep -P ${_pid}改用
Hubert Kario

16

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

rkill [-SIG] pid/name...

11

布拉德的答案也是我的建议,但awk如果您使用的--ppid选项,则可以完全取消ps

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

除非出于某种原因我取出-ax,否则这对我不起作用(Centos5)。否则,这太棒了!
xitrium

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

如果使用“ -ax”而不是“ ax”,则某些版本的ps会引发警告。因此:对于$(ps -o pid,ppid ax | \ awk“ {if(\ $ 2 == $ pid){print \ $ 1}}”)中的孩子
Cory R. King

9

我使用此处描述的方法的一些修改版本:https : //stackoverflow.com/a/5311362/563175

所以看起来像这样:

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

其中24901是父母的PID。

它看起来很丑,但是做得很好。


1
用grep而不是sed进行简化...pstree -p 24901 | grep -oP '(?<=\()[0-9]+(?=\))'
anishsane

2
您应该添加-lpstree,以免长行被截断;它也可以变得更易于阅读kill `pstree -l -p 24901 |grep "([[:digit:]]*)" -o |tr -d '()'`(不需要转换\n为空格,因为它可以正常工作),谢谢!
Aquarius Power

9

志刚答案的修改版:

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

wait $pid只能处理已启动的流程,而不是所有流程,因此这不是通用解决方案
Hubert Kario 2014年

@Hubert Kario在这种情况下,wait将以非零状态退出并继续执行脚本。我错了吗?但是如果是小孩,wait它将抑制Terminated消息。
x-yuri 2014年

9

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

@olibre在2月28日给出的否则,可能会得到一个非常好的和彻底的回答,这有一个小问题。PID的输出ps opgid= $PID将包含一个少于5位数字的PID的前导空格,因为这ps是为了使列对齐(数字对齐数字)。在整个命令行中,这将导致一个负号,后跟空格,然后是组PID。简单的办法就是管pstr以去除空间:

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

谢谢塔纳格。我会解决我的问题;)干杯
olibre 2013年

8

要增加Norman Ramsey的答案,如果您想创建一个流程组,可能值得看一下setid。
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

如果调用进程不是进程组负责人,则setsid()函数应创建一个新会话。返回时,呼叫进程将是此新会话的会话负责人,应是新进程组的进程组负责人,并且没有控制终端。调用进程的进程组ID必须设置为等于调用进程的进程ID。调用进程应是新进程组中的唯一进程,并且是新会话中的唯一进程。

我的意思是,您可以从开始过程中创建一个组。我在php中使用了它,以便能够在启动它之后杀死整个进程树。

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


实际上,这是个好主意,效果很好。我可以在将流程放入同一个流程组(或者它们已经在同一个组中)的情况下使用它。
生病了

7

ysth的评论启发

kill -- -PGID

与其给它一个进程号,不给它组号。与几乎所有命令一样,如果您希望将以a开头的普通参数-不解释为开关,请在其前面加上--


糟糕,我刚刚意识到我给的答案与您=> +1相同。但是,此外,我解释了如何简单地从中获得PGID收益PID。你怎么看?干杯
olibre 2013年

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
}

我认为第二行的末尾还有多余的单词“ child”。
mato

@mato –没什么。它限制了$child该函数的范围,以免打扰具有相同名称的其他(非局部)变量,并确保函数结束后清除局部变量的值。
亚当·卡兹

哦,现在知道了,我认为这是作业的一部分。我想我应该在写评论之前重新阅读(慢一点)。:) 谢谢。
马托

顺便说一句,创建一个孩子列表然后从上层(父母)开始杀死会更好吗?..因此,我们可以避免父母重新创建孩子或继续运行下一个可能会改变预期行为的代码的情况。
马托

5

使用psutil使用python做到这一点非常容易。只需将psutil与pip一起安装,即可获得一整套的流程处理工具:

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

5

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

init_killtree() {
    local pid=$1 child

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


3

这是我使用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//,/ }

1
如果在创建子列表之后创建新进程,则此操作将失败。
天花板

3

这是@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上似乎都可以正常工作。在您不能依靠能够管理流程组的情况下(例如,编写脚本来测试必须在多个环境中构建的软件时),这种遍历树技术无疑会很有帮助。


1

谢谢你的智慧,伙计们。我的脚本在退出时留下了一些子进程,否定提示使事情变得更容易。如有必要,我编写了此函数以供其他脚本使用:

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

干杯。


1

如果您的系统上有pstree和perl,则可以尝试以下操作:

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

1

最好先杀掉父母,再杀掉孩子。否则,父母可能会在杀死自己之前再次生下新孩子。这些将幸免于难。

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

请注意,@ zhighang脚本SIGSTOP会停止父进程并向停止的进程传递信号,因此这(AFAIK)不会在创建子进程与信号传递之间造成竞争。但是,您的版本在获取子级列表和向父级发送信号之间存在竞争。
休伯特·卡里奥

1

以下内容已在FreeBSD,Linux和MacOS X上进行了测试,仅取决于pgrep和kill(ps -o版本在BSD下不起作用)。第一个参数是必须终止其子级的父级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

这会将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

1

我将进一步开发zhigang,xyuri和solidsneck的解决方案:

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

此版本将避免杀死其祖先-在先前的解决方案中导致大量子进程。

在确定子级列表之前,进程已正确停止,因此不会创建或消失任何新的子级。

在被杀死之后,停止的作业必须继续从系统中消失。


1

我知道这是一个老问题,但是所有的回答似乎都一直在呼唤ps,我不喜欢它。

这种基于awk的解决方案不需要递归,只需调用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)}'

基本上,我们的想法是建立一个由parent:child条目组成的数组(a),然后在该数组中循环查找匹配的父级的子级,然后将它们添加到我们的父级列表(p)中。

如果您不想终止顶层进程,那么可以这样做

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

就在system()行将其从kill set中删除之前。

请记住,这里有一个竞争条件,但就所有解决方案而言,这都是事实(据我所知)。它满足了我的需要,因为我需要的脚本不会创建很多短暂的孩子。

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


0

如果您知道要杀死的对象的pid,通常可以使用会话ID以及同一会话中的所有内容。我会仔细检查,但是我将其用于在我想死的循环中启动rsyncs的脚本,而不是像我只是杀死所有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 )))


0

在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

0

在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)上播放,以查看其他行为。在这种情况下,子级将在达到10秒超时之前的5秒内完成执行。

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超时到达,则所有子进程都将被父进程杀死。您可以切换时间并使用脚本来查看行为。这种方法的一个缺点是,它使用组ID杀死所有子树。但是父进程本身属于同一组,因此也会被杀死。

如果您不想杀死父进程,则可能需要将其他组ID分配给父进程。

更多详情可在这找到,

在Shell脚本中杀死子进程


0

该脚本也可以工作:

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


0

我知道那是旧的,但这是我发现的更好的解决方案:

killtree() { 
    for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
        echo Terminating: $p 
        kill $p
    done
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.