[linux] What's the best way to send a signal to all members of a process group?

I want to kill a whole process tree. What is the best way to do this using any common scripting languages? I am looking for a simple solution.

This question is related to linux shell process signals

The answer is


If you want to kill a process by name:

killall -9 -g someprocessname

or

pgrep someprocessname | xargs pkill -9 -g

It's super easy to do this with python using psutil. Just install psutil with pip and then you have a full suite of process manipulation tools:

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

If you know the pid of the thing you want to kill, you can usually go from the session id, and everything in the same session. I'd double check, but I used this for scripts starting rsyncs in loops that I want to die, and not start another (because of the loop) as it would if I'd just killall'd rsync.

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

If you don't know the pid you can still nest more

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

Thanks for your wisdom, folks. My script was leaving some child processes on exit and the negation tip made things easier. I wrote this function to be used in other scripts if necessary:

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

Cheers.


It is probably better to kill the parent before the children; otherwise the parent may likely spawn new children again before he is killed himself. These will survive the killing.

My version of ps is different from that above; maybe too old, therefore the strange grepping...

To use a shell script instead of a shell function has many advantages...

However, it is basically zhigangs idea


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

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

I can't comment (not enough reputation), so I am forced to add a new answer, even though this is not really an answer.

There is a slight problem with the otherwise very nice and thorough answer given by @olibre on Feb 28. The output of ps opgid= $PID will contain leading spaces for a PID shorter than five digits because ps is justifying the column (rigth align the numbers). Within the entire command line, this results in a negative sign, followed by space(s), followed by the group PID. Simple solution is to pipe ps to tr to remove spaces:

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

rkill command from pslist package sends given signal (or SIGTERM by default) to specified process and all its descendants:

rkill [-SIG] pid/name...

brad's answer is what I'd recommend too, except that you can do away with awk altogether if you use the --ppid option to ps.

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

I know that is old, but that is the better solution that i found:

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

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

This is my version of killing all the child processes using bash script. It does not use recursion and depends on pgrep command.

Use

killtree.sh PID SIGNAL

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

In sh the jobs command will list the background processes. In some cases it might be better to kill the newest process first, e.g. the older one created a shared socket. In those cases sort the PIDs in reverse order. Sometimes you want to wait moment for the jobs to write something on disk or stuff like that before they stop.

And don't kill if you don't have to!

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

The following shell function is similar to many of the other answers, but it works both on Linux and BSD (OS X, etc) without external dependencies like 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
}

rkill command from pslist package sends given signal (or SIGTERM by default) to specified process and all its descendants:

rkill [-SIG] pid/name...

type ps -ef check the process id. Kill the process by typing kill -9 <pid>


if you have pstree and perl on your system, you can try this:

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

It's super easy to do this with python using psutil. Just install psutil with pip and then you have a full suite of process manipulation tools:

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

Thanks for your wisdom, folks. My script was leaving some child processes on exit and the negation tip made things easier. I wrote this function to be used in other scripts if necessary:

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

Cheers.


I use a little bit modified version of a method described here: https://stackoverflow.com/a/5311362/563175

So it looks like that:

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

where 24901 is parent's PID.

It looks pretty ugly but does it's job perfectly.


Based on zhigang's answer, this avoids self-killing:

init_killtree() {
    local pid=$1 child

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

This script also work:

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


Kill The Group Using Only Name of Process Which Belongs to Group:

kill -- -$(ps -ae o pid,pgrp,cmd | grep "[e]xample.py" | awk '{print $2}' | tail -1)

This is a modification of olibre's answer but you don't need to know the PID, just the name of a member of the group.

Explanation:

To get the group id you do the ps command using the arguments as shown, grep it for your command, but formatting example.py with quotes and using the bracket for the first letter (this filters out the grep command itself) then filter it through awk to get the second field which is the group id. The tail -1 gets rid of duplicate group ids. You put all of that in a variable using the $() syntax and voila – you get the group id. So you substitute that $(mess) for that -groupid above.


Thanks for your wisdom, folks. My script was leaving some child processes on exit and the negation tip made things easier. I wrote this function to be used in other scripts if necessary:

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

Cheers.


Modified version of zhigang's answer:

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

Thanks for your wisdom, folks. My script was leaving some child processes on exit and the negation tip made things easier. I wrote this function to be used in other scripts if necessary:

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

Cheers.


I develop the solution of zhigang, xyuri and solidsneck further:

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

This version will avoid killing its ancestry - which causes a flood of child processes in the previous solutions.

Processes are properly stopped before the child list is determined, so that no new children are created or disappear.

After being killed, the stopped jobs have to be continued to disappear from the system.


To add to Norman Ramsey's answer, it may be worth looking at at setsid if you want to create a process group.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

The setsid() function shall create a new session, if the calling process is not a process group leader. Upon return the calling process shall be the session leader of this new session, shall be the process group leader of a new process group, and shall have no controlling terminal. The process group ID of the calling process shall be set equal to the process ID of the calling process. The calling process shall be the only process in the new process group and the only process in the new session.

Which I take to mean that you can create a group from the starting process. I used this in php in order to be able to kill a whole process tree after starting it.

This may be a bad idea. I'd be interested in comments.


brad's answer is what I'd recommend too, except that you can do away with awk altogether if you use the --ppid option to ps.

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

Killing child process in shell script:

Many time we need to kill child process which are hanged or block for some reason. eg. FTP connection issue.

There are two approaches,

1) To create separate new parent for each child which will monitor and kill child process once timeout reached.

Create test.sh as follows,

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

and watch processes which are having name as 'test' in other terminal using following command.

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

Above script will create 4 new child processes and their parents. Each child process will run for 10sec. But once timeout of 5sec reach, thier respective parent processes will kill those childs. So child won't be able to complete execution(10sec). Play around those timings(switch 10 and 5) to see another behaviour. In that case child will finish execution in 5sec before it reaches timeout of 10sec.

2) Let the current parent monitor and kill child process once timeout reached. This won't create separate parent to monitor each child. Also you can manage all child processes properly within same parent.

Create test.sh as follows,

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

and watch processes which are having name as 'test' in other terminal using following command.

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

Above script will create 4 new child processes. We are storing pids of all child process and looping over them to check if they are finished their execution or still running. Child process will execution till CMD_TIME time. But if CNT_TIME_OUT timeout reach , All children will get killed by parent process. You can switch timing and play around with script to see behavior. One drawback of this approach is , it is using group id for killing all child tree. But parent process itself belong to same group so it will also get killed.

You may need to assign other group id to parent process if you don’t want parent to be killed.

More details can be found here,

Killing child process in shell script


pkill -TERM -P 27888

This will kill all processes that have the parent process ID 27888.

Or more robust:

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

which schedule killing 33 second later and politely ask processes to terminate.

See this answer for terminating all descendants.


To kill a process tree recursively, use 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 $@

The following has been tested on FreeBSD, Linux and MacOS X and only depends on pgrep and kill (the ps -o versions don't work under BSD). First argument is parent pid of which children have to be terminated. second argument is a boolean to determine whether the parent pid has to be terminated too.

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

This will send SIGTERM to any child / grandchild process within a shell script and if SIGTERM doesn't succeed, it will wait 10 seconds and then send kill.


Earlier answer:

The following also works but will kill the shell itself on BSD.

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

The following shell function is similar to many of the other answers, but it works both on Linux and BSD (OS X, etc) without external dependencies like 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
}

This script also work:

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


brad's answer is what I'd recommend too, except that you can do away with awk altogether if you use the --ppid option to ps.

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

To kill a process tree recursively, use 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 $@

And now for some clever shell programming.

There is a cost with this solution, but at least it is based on everyday iteration and recursion. This can be converted to bash by paying careful attention to the typeset commands and converting them to declare or local where appropriate.

Discussion

When killing a process, one must deal with the realty that it could be a parent to many children, and that each child could be the parent of even more children, and on, and on, an on.

What to do?

If only there was a function to test if a process had children, and another function to return the child PIDs of a parent process.

Then, the game would be much simpler, because you could create a loop to iterate over a list of PIDs, checking each one for children before killing it. If there are no children, just kill the process. If there are children, call the driving function recursively and pass it the results of a function that gets the PIDs of a parent's children.

The base case action (process does not have children).

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

The general case action (process has children).

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 $?
}

Library of supporting functions.

#!/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}')
}

In this way, you know for sure that the process tree is being destroyed from the branches, moving up to the root. This helps avoid the potential of creating zombies and other undesirable situations.

Now, that you have seen the most expensive way to do this (killing one process at a time), investigate how you could alter this solution to use the PGID (process group ID). The getProcesses () function already prints the PGID ($4 in awk), so learn how to use it, or don't.


I develop the solution of zhigang, xyuri and solidsneck further:

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

This version will avoid killing its ancestry - which causes a flood of child processes in the previous solutions.

Processes are properly stopped before the child list is determined, so that no new children are created or disappear.

After being killed, the stopped jobs have to be continued to disappear from the system.


Based on zhigang's answer, this avoids self-killing:

init_killtree() {
    local pid=$1 child

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

Kill The Group Using Only Name of Process Which Belongs to Group:

kill -- -$(ps -ae o pid,pgrp,cmd | grep "[e]xample.py" | awk '{print $2}' | tail -1)

This is a modification of olibre's answer but you don't need to know the PID, just the name of a member of the group.

Explanation:

To get the group id you do the ps command using the arguments as shown, grep it for your command, but formatting example.py with quotes and using the bracket for the first letter (this filters out the grep command itself) then filter it through awk to get the second field which is the group id. The tail -1 gets rid of duplicate group ids. You put all of that in a variable using the $() syntax and voila – you get the group id. So you substitute that $(mess) for that -groupid above.


Inspired by ysth’s comment

kill -- -PGID

instead of giving it a process number, give it the negation of the group number. As usual with almost any command, if you want a normal argument that starts with a - to not be interpreted as a switch, precede it with --


I know that is old, but that is the better solution that i found:

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

pkill -TERM -P 27888

This will kill all processes that have the parent process ID 27888.

Or more robust:

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

which schedule killing 33 second later and politely ask processes to terminate.

See this answer for terminating all descendants.


if you have pstree and perl on your system, you can try this:

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

Kill all the processes belonging to the same process tree using the Process Group ID (PGID)

  • kill -- -$PGID     Use default signal (TERM = 15)
  • kill -9 -$PGID     Use the signal KILL (9)

You can retrieve the PGID from any Process-ID (PID) of the same process tree

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

Special thanks to tanager and Speakus for contributions on $PID remaining spaces and OSX compatibility.

Explanation

  • kill -9 -"$PGID" => Send signal 9 (KILL) to all child and grandchild...
  • PGID=$(ps opgid= "$PID") => Retrieve the Process-Group-ID from any Process-ID of the tree, not only the Process-Parent-ID. A variation of ps opgid= $PID is ps -o pgid --no-headers $PID where pgid can be replaced by pgrp.
    But:
    • ps inserts leading spaces when PID is less than five digits and right aligned as noticed by tanager. You can use:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • ps from OSX always print the header, therefore Speakus proposes:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* prints successive digits only (does not print spaces or alphabetical headers).

Further command lines

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)

Limitation

  • As noticed by davide and Hubert Kario, when kill is invoked by a process belonging to the same tree, kill risks to kill itself before terminating the whole tree killing.
  • Therefore, be sure to run the command using a process having a different Process-Group-ID.

Long story

> 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 the process tree in background using '&'

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

The command pkill -P $PID does not kill the grandchild:

> 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

The command kill -- -$PGID kills all processes including the grandchild.

> 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

Conclusion

I notice in this example PID and PGID are equal (28957).
This is why I originally thought kill -- -$PID was enough. But in the case the process is spawn within a Makefile the Process ID is different from the Group ID.

I think kill -- -$(ps -o pgid= $PID | grep -o [0-9]*) is the best simple trick to kill a whole process tree when called from a different Group ID (another process tree).


In sh the jobs command will list the background processes. In some cases it might be better to kill the newest process first, e.g. the older one created a shared socket. In those cases sort the PIDs in reverse order. Sometimes you want to wait moment for the jobs to write something on disk or stuff like that before they stop.

And don't kill if you don't have to!

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

Here is a variation of @zhigang's answer which does without AWK, relying only on Bash's native parsing possibilities:

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

It seems to work fine on both Macs and Linux. In situations where you can't rely on being able to manage process groups -- like when writing scripts for testing a piece of software which must be built in multiple environments -- this tree-walking technique is definitely helpful.


type ps -ef check the process id. Kill the process by typing kill -9 <pid>


Inspired by ysth’s comment

kill -- -PGID

instead of giving it a process number, give it the negation of the group number. As usual with almost any command, if you want a normal argument that starts with a - to not be interpreted as a switch, precede it with --


if you know pass the pid of the parent process, here's a shell script that should work:

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

Killing child process in shell script:

Many time we need to kill child process which are hanged or block for some reason. eg. FTP connection issue.

There are two approaches,

1) To create separate new parent for each child which will monitor and kill child process once timeout reached.

Create test.sh as follows,

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

and watch processes which are having name as 'test' in other terminal using following command.

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

Above script will create 4 new child processes and their parents. Each child process will run for 10sec. But once timeout of 5sec reach, thier respective parent processes will kill those childs. So child won't be able to complete execution(10sec). Play around those timings(switch 10 and 5) to see another behaviour. In that case child will finish execution in 5sec before it reaches timeout of 10sec.

2) Let the current parent monitor and kill child process once timeout reached. This won't create separate parent to monitor each child. Also you can manage all child processes properly within same parent.

Create test.sh as follows,

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

and watch processes which are having name as 'test' in other terminal using following command.

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

Above script will create 4 new child processes. We are storing pids of all child process and looping over them to check if they are finished their execution or still running. Child process will execution till CMD_TIME time. But if CNT_TIME_OUT timeout reach , All children will get killed by parent process. You can switch timing and play around with script to see behavior. One drawback of this approach is , it is using group id for killing all child tree. But parent process itself belong to same group so it will also get killed.

You may need to assign other group id to parent process if you don’t want parent to be killed.

More details can be found here,

Killing child process in shell script


To add to Norman Ramsey's answer, it may be worth looking at at setsid if you want to create a process group.
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

The setsid() function shall create a new session, if the calling process is not a process group leader. Upon return the calling process shall be the session leader of this new session, shall be the process group leader of a new process group, and shall have no controlling terminal. The process group ID of the calling process shall be set equal to the process ID of the calling process. The calling process shall be the only process in the new process group and the only process in the new session.

Which I take to mean that you can create a group from the starting process. I used this in php in order to be able to kill a whole process tree after starting it.

This may be a bad idea. I'd be interested in comments.


Modified version of zhigang's answer:

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

I can't comment (not enough reputation), so I am forced to add a new answer, even though this is not really an answer.

There is a slight problem with the otherwise very nice and thorough answer given by @olibre on Feb 28. The output of ps opgid= $PID will contain leading spaces for a PID shorter than five digits because ps is justifying the column (rigth align the numbers). Within the entire command line, this results in a negative sign, followed by space(s), followed by the group PID. Simple solution is to pipe ps to tr to remove spaces:

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

Old question, I know, but all the responses seem to keep calling ps, which I didn't like.

This awk-based solution doesn't require recursion and only calls ps once.

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)
}'

Or on a single-line:

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)}'

Basically the idea is that we build up an array (a) of parent:child entries, then loop around the array finding children for our matching parents, adding them to our parents list (p) as we go.

If you don't want to kill the top-level process, then doing

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

just before the system() line would remove it from the kill set.

Bear in mind that there is a race condition here, but that's true (as far as I can see) of all of the solutions. It does what I needed because the script I needed it for doesn't create lots of short-lived children.

An exercise for the reader would be to make it a 2-pass loop: after the first pass, send SIGSTOP to all processes in the p list, then loop to run ps again and after the second pass send SIGTERM, then SIGCONT. If you don't care about nice endings then second-pass could just be SIGKILL, I suppose.


This is my version of killing all the child processes using bash script. It does not use recursion and depends on pgrep command.

Use

killtree.sh PID SIGNAL

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

And now for some clever shell programming.

There is a cost with this solution, but at least it is based on everyday iteration and recursion. This can be converted to bash by paying careful attention to the typeset commands and converting them to declare or local where appropriate.

Discussion

When killing a process, one must deal with the realty that it could be a parent to many children, and that each child could be the parent of even more children, and on, and on, an on.

What to do?

If only there was a function to test if a process had children, and another function to return the child PIDs of a parent process.

Then, the game would be much simpler, because you could create a loop to iterate over a list of PIDs, checking each one for children before killing it. If there are no children, just kill the process. If there are children, call the driving function recursively and pass it the results of a function that gets the PIDs of a parent's children.

The base case action (process does not have children).

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

The general case action (process has children).

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 $?
}

Library of supporting functions.

#!/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}')
}

In this way, you know for sure that the process tree is being destroyed from the branches, moving up to the root. This helps avoid the potential of creating zombies and other undesirable situations.

Now, that you have seen the most expensive way to do this (killing one process at a time), investigate how you could alter this solution to use the PGID (process group ID). The getProcesses () function already prints the PGID ($4 in awk), so learn how to use it, or don't.


brad's answer is what I'd recommend too, except that you can do away with awk altogether if you use the --ppid option to ps.

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

If you want to kill a process by name:

killall -9 -g someprocessname

or

pgrep someprocessname | xargs pkill -9 -g

if you know pass the pid of the parent process, here's a shell script that should work:

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

The following has been tested on FreeBSD, Linux and MacOS X and only depends on pgrep and kill (the ps -o versions don't work under BSD). First argument is parent pid of which children have to be terminated. second argument is a boolean to determine whether the parent pid has to be terminated too.

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

This will send SIGTERM to any child / grandchild process within a shell script and if SIGTERM doesn't succeed, it will wait 10 seconds and then send kill.


Earlier answer:

The following also works but will kill the shell itself on BSD.

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

Kill all the processes belonging to the same process tree using the Process Group ID (PGID)

  • kill -- -$PGID     Use default signal (TERM = 15)
  • kill -9 -$PGID     Use the signal KILL (9)

You can retrieve the PGID from any Process-ID (PID) of the same process tree

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

Special thanks to tanager and Speakus for contributions on $PID remaining spaces and OSX compatibility.

Explanation

  • kill -9 -"$PGID" => Send signal 9 (KILL) to all child and grandchild...
  • PGID=$(ps opgid= "$PID") => Retrieve the Process-Group-ID from any Process-ID of the tree, not only the Process-Parent-ID. A variation of ps opgid= $PID is ps -o pgid --no-headers $PID where pgid can be replaced by pgrp.
    But:
    • ps inserts leading spaces when PID is less than five digits and right aligned as noticed by tanager. You can use:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • ps from OSX always print the header, therefore Speakus proposes:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* prints successive digits only (does not print spaces or alphabetical headers).

Further command lines

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)

Limitation

  • As noticed by davide and Hubert Kario, when kill is invoked by a process belonging to the same tree, kill risks to kill itself before terminating the whole tree killing.
  • Therefore, be sure to run the command using a process having a different Process-Group-ID.

Long story

> 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 the process tree in background using '&'

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

The command pkill -P $PID does not kill the grandchild:

> 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

The command kill -- -$PGID kills all processes including the grandchild.

> 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

Conclusion

I notice in this example PID and PGID are equal (28957).
This is why I originally thought kill -- -$PID was enough. But in the case the process is spawn within a Makefile the Process ID is different from the Group ID.

I think kill -- -$(ps -o pgid= $PID | grep -o [0-9]*) is the best simple trick to kill a whole process tree when called from a different Group ID (another process tree).


if you know pass the pid of the parent process, here's a shell script that should work:

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

It is probably better to kill the parent before the children; otherwise the parent may likely spawn new children again before he is killed himself. These will survive the killing.

My version of ps is different from that above; maybe too old, therefore the strange grepping...

To use a shell script instead of a shell function has many advantages...

However, it is basically zhigangs idea


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

I use a little bit modified version of a method described here: https://stackoverflow.com/a/5311362/563175

So it looks like that:

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

where 24901 is parent's PID.

It looks pretty ugly but does it's job perfectly.


If you know the pid of the thing you want to kill, you can usually go from the session id, and everything in the same session. I'd double check, but I used this for scripts starting rsyncs in loops that I want to die, and not start another (because of the loop) as it would if I'd just killall'd rsync.

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

If you don't know the pid you can still nest more

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

if you know pass the pid of the parent process, here's a shell script that should work:

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

Here is a variation of @zhigang's answer which does without AWK, relying only on Bash's native parsing possibilities:

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

It seems to work fine on both Macs and Linux. In situations where you can't rely on being able to manage process groups -- like when writing scripts for testing a piece of software which must be built in multiple environments -- this tree-walking technique is definitely helpful.


Examples related to linux

grep's at sign caught as whitespace How to prevent Google Colab from disconnecting? "E: Unable to locate package python-pip" on Ubuntu 18.04 How to upgrade Python version to 3.7? Install Qt on Ubuntu Get first line of a shell command's output Cannot connect to the Docker daemon at unix:/var/run/docker.sock. Is the docker daemon running? Run bash command on jenkins pipeline How to uninstall an older PHP version from centOS7 How to update-alternatives to Python 3 without breaking apt?

Examples related to shell

Comparing a variable with a string python not working when redirecting from bash script Get first line of a shell command's output How to run shell script file using nodejs? Run bash command on jenkins pipeline Way to create multiline comments in Bash? How to do multiline shell script in Ansible How to check if a file exists in a shell script How to check if an environment variable exists and get its value? Curl to return http status code along with the response docker entrypoint running bash script gets "permission denied"

Examples related to process

Fork() function in C How to kill a nodejs process in Linux? Xcode process launch failed: Security Understanding PrimeFaces process/update and JSF f:ajax execute/render attributes Linux Script to check if process is running and act on the result CreateProcess error=2, The system cannot find the file specified How to make parent wait for all child processes to finish? How to use [DllImport("")] in C#? Visual Studio "Could not copy" .... during build How to terminate process from Python using pid?

Examples related to signals

Is it possible to capture a Ctrl+C signal and run a cleanup function, in a "defer" fashion? How to suspend/resume a process in Windows? How to trigger SIGUSR1 and SIGUSR2? Catch Ctrl-C in C How can I catch a ctrl-c event? How do I capture SIGINT in Python? Can I send a ctrl-C (SIGINT) to an application on Windows? What killed my process and why? What's the best way to send a signal to all members of a process group? How to prevent SIGPIPEs (or handle them properly)