Here are a couple of useful functions I wrote to kill a process tree. It’s useful when dealing with tcpserver, which refuses to disconnect a client for as long as a process it started, or a child of that process, still runs. It’s also good for tackling runaway unintentional fork bombs, rogue daemons and other mishaps.
The following script provides two functions:
| sf_killtree |
sends SIGSTOP to a process, kills all its children recursively, kills the process, sends SIGCONT and waits for it, to avoid “Terminated:” messages |
|---|---|
| sf_killchildren |
kills all children of the current script without killing the script itself |
Script:
function sf_killtree
{
local ppid=$1 sig=${2:-"TERM"}
if ! kill -0 "$ppid"; then
builtin echo "killtree: ($ppid) - No such process" >&2
return 1
fi
(( ppid == $$ )) || kill -STOP "$ppid" 2>/dev/null || :
local pids=( $({ pgrep -P $ppid || ps -o pid= --ppid $ppid || :; } 2>/dev/null ) )
if [[ -n "${pids[@]:-}" ]]; then
for pid in "${pids[@]}"; do
sf_killtree "$pid" "$sig" 2>/dev/null || :
done
fi
kill "-${sig}" "$ppid" 2>/dev/null || :
(( ppid == $$ )) || kill -CONT "$ppid" 2>/dev/null || :
wait "$ppid" 2>/dev/null || :
}
function sf_killchildren
{
local _term=$(builtin trap -p TERM)
builtin trap : TERM
sf_killtree $$ TERM;
builtin trap - TERM
builtin eval "$_term"
}
Addendum
Please also note the following from the KILL (2) man page:
“If pid equals 0, then sig is sent to every process in the process group of the calling process.”
While this does kill the script itself, unlike sf_killchildren, it can be a useful thing to end a script with, or to put in an EXIT trap.