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.