How to kill all subprocesses of shell?

BashShellProcessForkKill

Bash Problem Overview


I'm writing a bash script, which does several things.

In the beginning it starts several monitor scripts, each of them runs some other tools.

At the end of my main script, I would like to kill all things that were spawned from my shell.

So, it might looks like this:

#!/bin/bash

some_monitor1.sh &
some_monitor2.sh &
some_monitor3.sh &

do_some_work
...

kill_subprocesses

The thing is that most of these monitors spawn their own subprocesses, so doing (for example): killall some_monitor1.sh will not always help.

Any other way to handle this situation?

Bash Solutions


Solution 1 - Bash

pkill -P $$

will fit (just kills it's own descendants)

EDIT: I got a downvote, don't know why. Anyway here is the help of -P

   -P, --parent ppid,...
          Only match processes whose parent process ID is listed.

and $$ is the process id of the script itself

Solution 2 - Bash

After starting each child process, you can get its id with

ID=$!

Then you can use the stored PIDs to find and kill all grandchild etc. processes as described here or here.

Solution 3 - Bash

If you use a negative PID with kill it will kill a process group. Example:

kill -- -1234

Solution 4 - Bash

Extending pihentagy's answer to recursively kill all descendants (not just children):

kill_descendant_processes() {
    local pid="$1"
    local and_self="${2:-false}"
    if children="$(pgrep -P "$pid")"; then
        for child in $children; do
            kill_descendant_processes "$child" true
        done
    fi
    if [[ "$and_self" == true ]]; then
        kill -9 "$pid"
    fi
}

Now

kill_descendant_processes $$

will kill descedants of the current script/shell.

(Tested on Mac OS 10.9.5. Only depends on pgrep and kill)

Solution 5 - Bash

kill $(jobs -p)

Rhys Ulerich's suggestion:

> Caveat a race condition, using [code below] accomplishes what Jürgen suggested without causing an error when no jobs exist

[[ -z "$(jobs -p)" ]] || kill $(jobs -p)

Solution 6 - Bash

pkill with optioin "-P" should help:

pkill -P $(pgrep some_monitor1.sh)

from man page:

   -P ppid,...
          Only match processes whose parent process ID is listed.

There are some discussions on linuxquests.org, please check:

http://www.linuxquestions.org/questions/programming-9/use-only-one-kill-to-kill-father-and-child-processes-665753/

Solution 7 - Bash

I like the following straightforward approach: start the subprocesses with an environment variable with some name/value and use this to kill the subprocesses later. Most convenient is to use the process-id of the running bash script i.e. $$. This also works when subprocesses starts another subprocesses as the environment is inherited.

So start the subprocesses like this:

MY_SCRIPT_TOKEN=$$ some_monitor1.sh &
MY_SCRIPT_TOKEN=$$ some_monitor2.sh &

And afterwards kill them like this:

ps -Eef | grep "MY_SCRIPT_TOKEN=$$" | awk '{print $2}' | xargs kill

Solution 8 - Bash

Similar to above, just a minor tweak to kill all processes indicated by ps:

ps -o pid= | tail -n +2 | xargs kill -9

Perhaps sloppy / fragile, but seemed to work at first blush. Relies on fact that current process ($$) tends to be first line.

Description of commands, in order:

  1. Print PIDs for processes in current terminal, excl. header column
  2. Start from Line 2 (excl. current terminal's shell)
  3. Kill those procs

Solution 9 - Bash

I've incorporated a bunch of the suggestions from the answers here into a single function. It gives time for processes to exit, murders them if they take too long, and doesn't have to grep through output (eg, via ps)

#!/bin/bash
# This function will kill all sub jobs.
function KillJobs() {
  [[ -z "$(jobs -p)" ]] && return # no jobs to kill
  local SIG="INT" # default to a gentle goodbye
  [[ ! -z "$1" ]] && SIG="$1" # optionally send a different signal
  # my version of 'kill' doesn't seem to understand `kill -- -${PID}`
  #jobs -p | xargs -I%% kill -s "$SIG" -- -%% # kill each job's processes group
  jobs -p | xargs kill -s "$SIG" # kill each job's processes group
  
  ## give the processes a moment to die, before forcing them to.
  [[ "$SIG" != "KILL" ]] && {
    sleep 0.2
    KillJobs "KILL"
  }
}

I also tried to get a variation working with pkill, but on my system (xubuntu 21.10) it does absolutely nothing.

#!/bin/bash
# This function doesn't seem to work.
function KillChildren() {
  local SIG="INT" # default to a gentle goodbye
  [[ ! -z "$1" ]] && SIG="$1" # optionally send a different signal
  pkill --signal "$SIG" -P $$ # kill descendent's and their processes groups
  [[ "$SIG" != "KILL" ]] && {
    # give them a moment to die before we force them to.
    sleep 0.2
    KillChildren "KILL" ;
  }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser80168View Question on Stackoverflow
Solution 1 - BashpihentagyView Answer on Stackoverflow
Solution 2 - BashPéter TörökView Answer on Stackoverflow
Solution 3 - BashDennis WilliamsonView Answer on Stackoverflow
Solution 4 - BashAdam CathView Answer on Stackoverflow
Solution 5 - BashJürgen HötzelView Answer on Stackoverflow
Solution 6 - BashybyyguView Answer on Stackoverflow
Solution 7 - BashcyberbirdView Answer on Stackoverflow
Solution 8 - BashEric CousineauView Answer on Stackoverflow
Solution 9 - BashBrandonView Answer on Stackoverflow