How to kill all subprocesses of shell?
BashShellProcessForkKillBash 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:
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:
- Print PIDs for processes in current terminal, excl. header column
- Start from Line 2 (excl. current terminal's shell)
- 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" ;
}
}