69

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?

codeforester
  • 39,467
  • 16
  • 112
  • 140
  • A great thread about this: http://stackoverflow.com/questions/392022/best-way-to-kill-all-child-processes/15139734#15139734 – nandilugio Jul 22 '16 at 17:01

9 Answers9

133
pkill -P $$

will fit (just kills its own descendants)

And 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

E_net4
  • 27,810
  • 13
  • 101
  • 139
pihentagy
  • 5,975
  • 9
  • 39
  • 58
  • Thanks for the positive feedbacks – pihentagy Feb 12 '14 at 12:26
  • 12
    Does not kill descendants passed the immediate child processes. – Exponent Apr 29 '14 at 23:24
  • I haven't downvoted it, but pkill is not installed in my bash. So I voted up the J:urgen one. – Chexpir Jul 29 '14 at 14:14
  • 3
    @Chexpir: It's not part of bash. However, if you system does not have it, you are in deep5h1t. It's part of the `procps` package under Ubuntu, and this package contains other core utilities, like `free`, `kill` and `ps`. – pihentagy Jul 29 '14 at 16:12
  • @pihentagy, I was using gitBash under Windows, I can imagine they try to light the package as much as possible, as the kill command worked as a charm. – Chexpir Jul 30 '14 at 13:38
  • 1
    Upvoting. Yet in my case the processes were pretty stubborn so I had to kill them with signal 9 as in `pkill -P $$ --signal 9` – Highstaker Feb 15 '15 at 08:36
  • 4
    @pihentagy Isn't there a race condition? pkill itself is a child of current shell. Therefor, will "pkill -P $$" kill the "pkill" process before all other processes being killed? – Lungang Fang Nov 24 '15 at 07:07
  • @lgfang: I honestly don't know. But I'm afraid we won't know the answer, since no one posted a solution for that :( – pihentagy Mar 03 '16 at 17:27
  • 19
    No, there is no race condition here. pkill is a symlink to pgrep and in pgrep.c on line 441 you can see that pkill will skip itself when checking for processes to kill. So after killing every other child, the pkill command will terminate regularly. – fxtentacle Jun 16 '16 at 09:09
34

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.

Joman68
  • 2,248
  • 3
  • 34
  • 36
Péter Török
  • 114,404
  • 31
  • 268
  • 329
29

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

kill -- -1234

Nowaker
  • 12,154
  • 4
  • 56
  • 62
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
17

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)

Adam Cath
  • 317
  • 3
  • 4
  • 8
    Ok, but please don't use `kill -9`"by default", give a change the processes to exit! http://serverfault.com/a/415744/55046 – pihentagy May 12 '15 at 16:40
  • And if you have pgrep, you have pkill, then why not simply use my answer with `pkill`? https://stackoverflow.com/a/17615178/26494 – pihentagy Feb 27 '22 at 20:33
  • as mentoned before, pkill won't kill grandchildren. there is my minified version of this approach: `k(){ if c=$(pgrep -P $1);then for p in $c;do k $p;done;fi;kill $1; }; k PID` – duo Nov 11 '22 at 08:34
13
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)
Community
  • 1
  • 1
Jürgen Hötzel
  • 18,997
  • 3
  • 42
  • 58
  • 4
    Caveat a race condition, using 'test -z "\`jobs -p\`" || kill \`jobs -p\`' accomplishes what Jürgen suggested without causing an error when no jobs exist – Rhys Ulerich Jul 13 '11 at 19:37
  • 1
    `jobs="$(jobs -p)"; [ -n "$jobs" ] && kill $jobs` avoids the race condition of a process exiting between the two calls to `jobs`, is POSIX compliant, and is (IMO) less confusing than checking for an empty string and or'ing to `kill` – Adrian Günter Sep 22 '22 at 15:08
5

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/

ybyygu
  • 137
  • 6
  • What if you have had started it 2 times? One will kill the others subprocesses! – pihentagy Jul 12 '13 at 12:41
  • @pihentagy Each *unique, running process* (vs tool) on the system has a unique PID. If you open two terminals then a process monitor, the monitor will show two different "sh" processes, each with their own PID. However, you're right that you *can* clobber things, by getting processes by *name*, which need not be (and are often not) unique. – Beejor Mar 19 '19 at 18:57
2

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
cyberbird
  • 111
  • 1
  • 3
0

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
Eric Cousineau
  • 1,944
  • 14
  • 23
0

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" ;
  }
}
Brandon
  • 483
  • 3
  • 12