zur Übersicht: Linux: meine Software und Konfigurationstipps

Senden eines Signals (kill) an einen ganzen Prozessbaum

08.10.2006

Problem

Manchmal will man nicht nur einen einzelnen Prozess killen, sondern mehrere zusammenhängende. Mein auslösender Praxisfall war ein Script, das vom cron ausgerufen wurde und hängenblieb. Das waren dann für jeden Durchlauf vier oder fünf Kindprozesse. Die manuell zu killen, ist mit Arbeit verbunden. Außerdem kann man sich nicht darauf verlassen, dass durch die Beendigung des Elternprozesses auch die Kinder abgeräumt werden.

Lösung

Deshalb habe ich ein Script geschrieben, das für jede übergebene PID alle "untergeordneten" Prozesse heraussucht und diese von oben (Argumente) nach unten (Kinder der letzten Ebene) an kill übergibt, um zu vermeiden, dass ein Prozess sein Kind neu startet, weil es durch Signal beendet wurde, bevor er selber beendet wird.

Zwei wesentliche Unterschiede bestehen zum Standard-kill:

  1. Negative PIDs zur Auswahl von Prozessgruppen werden nicht unterstützt.

  2. Es werden weniger Aufrufvarianten unterstützt.

Aufruf

killtree [--show|--showonly] [-] pid ...

--show
Die gefundenen PIDs werden vor der Signalisierung in der Reihenfolgen angezeigt, in der sie abgearbeitet werden.
--showonly
Die gefundenen PIDs werden angezeigt; es werden keine Signale geschickt (sinnvoll zum Ausprobieren des Programms).

Informationen über die verfügbaren Signale bekommt man mit /bin/kill -t.

Download


#! /bin/bash
# This software is licensed under the Gnu GPL version 2, see http://www.fsf.org/
# Fuer dieses Script gibt es eine OpenPGP-Signatur (ID 0xECCB5814) auf der Downloadseite (killtree.sig)

version=1.0.2

# Dieses Script wird so aufgerufen wie kill, killt aber nicht nur die uebergebenen PIDs, 
# sondern auch den ganzen Baum der Kindprozesse.

# CHANGELOG
# version 1.0.1, 2006-10-07, Hauke Laging, software@hauke-laging.de

usage () {
	echo Usage: >&2
	echo "'$0' [--show|--showonly] [-<signal>] pid ..." >&2
	echo "'$0' --help" >&2
	echo "'$0' --version" >&2
}

info () {
	usage
	cat <<EOT

This script calls kill for the argument PIDs and all child processes
thus sending the signal to a whole process tree.

This script accepts normal PIDs only (i.e. no negative ones for 
addressing process groups).
EOT
}

if [ "$1" = --help ]
	then
	info
	exit 0
fi

if [ "$1" = --version ]
	then
	echo "killtree version ${version}"
	echo "This software is licensed under the Gnu GPL version 2, see http://www.fsf.org/"
	exit 0
fi

type mktemp &>/dev/null || { echo "Program 'mktemp' is not available; aborting."; exit 1; }

ps_file=$(mktemp /tmp/ps.XXXXXX)
/bin/ps -eo pid,ppid | awk 'NR==1 {next;}; {print $1 " " $2}' > "$ps_file"

# PID-Arrays initialisieren

counter_wrpp=0
while read pid ppid
	do
	pids[$counter_wrpp]=$pid
	ppids[$counter_wrpp]=$ppid
	((counter_wrpp++))
done < "$ps_file"
rm "$ps_file"

show=no
showonly=no
if [ "$1" = --show -o "$1" = --showonly ]
	then
	show=yes
	if [ "$1" = --showonly ]
		then
		showonly=yes
	fi
	shift
fi
if [ "$1" != "${1#-}" ]
	then
	signal="$1"
	shift
else
	signal=
fi
if [ $# -eq 0 ]
	then
	echo "Argument missing; aborting" >&2
	usage
	exit 2
fi
kill_pids=()

for((counter_mainargs=1;counter_mainargs<=$#;counter_mainargs++))
	do
	root_pid=${!counter_mainargs}
	if [[ ! "$root_pid" =~ '^[1-9][0-9]+$' ]]
		then
		echo "Illegal argument '${root_pid}'; aborting" >&2
		exit 1 # bei kill sollte man bei fehlerhaftem Aufruf lieber beenden als ueberspringen...
	fi
	kill_pids=(${kill_pids[@]} $root_pid)
	search_pids=($root_pid)
	pids_found=yes
	while [ "$pids_found" = yes ]
		do
		ppid_matches=()
		for search_pid in ${search_pids[@]}
			do
			for((counter_pidarray=0;counter_pidarray<counter_wrpp;counter_pidarray++))
				do
				if [ $search_pid -eq  ${ppids[$counter_pidarray]} ]
					then
					tmp_i=${#kill_pids[@]}
					kill_pids[$tmp_i]=${pids[$counter_pidarray]}
					tmp_i=${#ppid_matches[@]}
					ppid_matches[$tmp_i]=${pids[$counter_pidarray]}
				fi
			done
		done
		search_pids=(${ppid_matches[*]})
		if [ ${#ppid_matches[*]} -gt 0 ]
			then
			pids_found=yes
		else
			pids_found=no
		fi
	done
done
count_kill=${#kill_pids[@]}

echo "Going to call 'kill ${signal}' for ${count_kill} PIDs."
if [ "$show" = yes ]
	then
	echo ${kill_pids[@]}
	if [ "$showonly" = yes ]
		then
		echo "Option --showonly given; exiting without sending signals." >&2
		exit 0
	fi
fi
count_success=0
count_failure=0
# Den Baum von der Wurzel zu den Blaettern abarbeiten, um zu vermeiden, dass Prozesse ihre beendeten Kinder neu starten, 
#	bevor sie selber beendet werden.
for((counter_kill=0;counter_kill<count_kill;counter_kill++))
	do
	if [ -n "$signal" ]
		then
		kill "$signal" ${kill_pids[counter_kill]} &>/dev/null
	else
		kill ${kill_pids[counter_kill]} &>/dev/null
	fi
	if [ $? -eq 0 ]
		then
		((count_success++))
	else
		((count_failure++))
	fi
done
echo "${count_success} processes received the signal."
echo "${count_failure} errors occurred."