Antworten:
trap
Kann verwendet werden, um etwas Chaos zu beseitigen. Es kann eine Liste von Dingen bereitstellen, die ausgeführt werden, wenn ein bestimmtes Signal eintrifft:
trap "echo hello" SIGINT
kann aber auch verwendet werden, um etwas auszuführen, wenn die Shell beendet wird:
trap "killall background" EXIT
Es ist ein eingebautes Gerät, help trap
das Ihnen Informationen gibt (funktioniert mit Bash). Wenn Sie nur Hintergrundjobs beenden möchten, können Sie dies tun
trap 'kill $(jobs -p)' EXIT
Achten Sie darauf, Single zu verwenden '
, um zu verhindern, dass die Shell die $()
sofort ersetzt.
kill $(jobs -p)
funktioniert nicht in Bindestrich, da es die Befehlssubstitution in einer Subshell ausführt (siehe Befehlssubstitution im Man-Bindestrich)
killall background
angeblich ein Platzhalter sein? background
ist nicht in der Manpage ...
Das funktioniert bei mir (verbessert dank der Kommentatoren):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
4.3.30(1)-release
unter OSX darauf gestoßen, und es ist auch unter Ubuntu bestätigt . Es gibt jedoch einen Obvoius Wokaround :)
-$$
. Es ergibt '- <PID> `zB -1234
. In der eingebauten Manpage kill manpage // gibt ein führender Strich das zu sendende Signal an. Allerdings - blockiert das wahrscheinlich, aber dann ist der führende Bindestrich ansonsten nicht dokumentiert. Irgendeine Hilfe?
man 2 kill
, ob bei einer negativen PID das Signal mit der angegebenen ID ( en.wikipedia.org/wiki/Process_group ) an alle Prozesse in der Prozessgruppe gesendet wird . Es ist verwirrend, dass dies in man 1 kill
oder nicht erwähnt man bash
wird und als Fehler in der Dokumentation angesehen werden kann.
Update: https://stackoverflow.com/a/53714583/302079 verbessert dies durch Hinzufügen eines Exit-Status und einer Bereinigungsfunktion.
trap "exit" INT TERM
trap "kill 0" EXIT
Warum konvertieren INT
und TERM
beenden? Denn beide sollten das auslösen, kill 0
ohne in eine Endlosschleife einzutreten.
Warum Trigger kill 0
auf EXIT
? Weil auch normale Skript-Exits ausgelöst werden sollten kill 0
.
Warum kill 0
? Weil verschachtelte Unterschalen ebenfalls getötet werden müssen. Dadurch wird der gesamte Prozessbaum heruntergefahren .
kill 0
bedeutet / tut?
Die trap 'kill 0' SIGINT SIGTERM EXIT
in der Antwort von @ tokland beschriebene Lösung ist wirklich nett, aber der neueste Bash stürzt bei der Verwendung mit einem Segmantationsfehler ab . Das liegt daran, dass Bash ab Version 4.3 eine Trap-Rekursion zulässt, die in diesem Fall unendlich wird:
SIGINT
oder SIGTERM
oder EXIT
;kill 0
abgefangen und SIGTERM
an alle Prozesse in der Gruppe gesendet , einschließlich der Shell selbst.Dies kann umgangen werden, indem die Falle manuell abgemeldet wird:
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
Die ausgefallenere Methode, mit der das empfangene Signal gedruckt werden kann und "Terminated:" - Nachrichten vermieden werden:
#!/usr/bin/env bash
trap_with_arg() { # from https://stackoverflow.com/a/2183063/804678
local func="$1"; shift
for sig in "$@"; do
trap "$func $sig" "$sig"
done
}
stop() {
trap - SIGINT EXIT
printf '\n%s\n' "recieved $1, killing children"
kill -s SIGINT 0
}
trap_with_arg 'stop' EXIT SIGINT SIGTERM SIGHUP
{ i=0; while (( ++i )); do sleep 0.5 && echo "a: $i"; done } &
{ i=0; while (( ++i )); do sleep 0.6 && echo "b: $i"; done } &
while true; do read; done
UPD : minimales Beispiel hinzugefügt; Verbesserte stop
Funktion, um unnötige Signale nicht mehr abzufangen und "Terminated:" - Meldungen vor dem Ausgang zu verbergen. Vielen Dank an Trevor Boyd Smith für die Vorschläge!
stop()
Sie bieten das erste Argument als die Signalnummer , aber dann codieren Sie , welche Signale deregistriert werden. Anstatt die abgemeldeten Signale fest zu codieren, können Sie das erste Argument verwenden, um die Registrierung in der stop()
Funktion aufzuheben (dies würde möglicherweise andere rekursive Signale stoppen (außer den 3 fest codierten Signalen)).
SIGINT
, kill 0
sendet aber SIGTERM
, was erneut gefangen wird. Dies führt jedoch nicht zu einer unendlichen Rekursion, da SIGTERM
sie beim zweiten stop
Aufruf entfernt wird.
trap - $1 && kill -s $1 0
besser funktionieren. Ich werde diese Antwort testen und aktualisieren. Danke für die schöne Idee! :)
trap - $1 && kill -s $1 0
würde auch nicht funktionieren, da wir nicht mit töten können EXIT
. Aber es ist wirklich ausreichend, die Falle zu lösen TERM
, da kill
dieses Signal standardmäßig gesendet wird.
EXIT
, der trap
Signal-Handler wird immer nur einmal ausgeführt.
Um auf der sicheren Seite zu sein, finde ich es besser, eine Bereinigungsfunktion zu definieren und sie aus der Falle aufzurufen:
cleanup() {
local pids=$(jobs -pr)
[ -n "$pids" ] && kill $pids
}
trap "cleanup" INT QUIT TERM EXIT [...]
oder die Funktion insgesamt vermeiden:
trap '[ -n "$(jobs -pr)" ] && kill $(jobs -pr)' INT QUIT TERM EXIT [...]
Warum? Denn durch einfache Verwendung wird trap 'kill $(jobs -pr)' [...]
davon ausgegangen, dass Hintergrundjobs ausgeführt werden, wenn der Trap-Zustand signalisiert wird. Wenn keine Jobs vorhanden sind, wird die folgende (oder eine ähnliche) Meldung angezeigt:
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
weil jobs -pr
es leer ist - ich endete in dieser 'Falle' (Wortspiel beabsichtigt).
[ -n "$(jobs -pr)" ]
funktioniert bei meiner Bash nicht. Ich benutze GNU Bash, Version 4.2.46 (2) -release (x86_64-redhat-linux-gnu). Die Meldung "kill: usage" wird immer wieder angezeigt.
jobs -pr
die PIDs der Kinder der Hintergrundprozesse nicht zurückgegeben werden. Es reißt nicht den gesamten Prozessbaum ab, sondern schneidet nur die Wurzeln ab.
Eine schöne Version, die unter Linux, BSD und MacOS X funktioniert. Zuerst wird versucht, SIGTERM zu senden. Wenn dies nicht erfolgreich ist, wird der Vorgang nach 10 Sekunden abgebrochen.
KillJobs() {
for job in $(jobs -p); do
kill -s SIGTERM $job > /dev/null 2>&1 || (sleep 10 && kill -9 $job > /dev/null 2>&1 &)
done
}
TrapQuit() {
# Whatever you need to clean here
KillJobs
}
trap TrapQuit EXIT
Bitte beachten Sie, dass Jobs keine Prozesse für Enkelkinder beinhalten.
function cleanup_func {
sleep 0.5
echo cleanup
}
trap "exit \$exit_code" INT TERM
trap "exit_code=\$?; cleanup_func; kill 0" EXIT
# exit 1
# exit 0
Wie https://stackoverflow.com/a/22644006/10082476 , jedoch mit hinzugefügtem Exit-Code
jobs -p funktioniert nicht in allen Shells, wenn es in einer Sub-Shell aufgerufen wird, möglicherweise nur, wenn seine Ausgabe in eine Datei, aber nicht in eine Pipe umgeleitet wird. (Ich nehme an, es war ursprünglich nur für den interaktiven Gebrauch gedacht.)
Was ist mit Folgendem:
trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]
Der Aufruf von "jobs" wird mit der Dash-Shell von Debian benötigt, die den aktuellen Job ("%%") nicht aktualisiert, wenn er fehlt.
Ich habe die Antwort von @ tokland in Kombination mit dem Wissen von http://veithen.github.io/2014/11/16/sigterm-propagation.html angepasst, als ich bemerkte, dass trap
dies nicht ausgelöst wird, wenn ich einen Vordergrundprozess ausführe (nicht mit Hintergrund &
):
#!/bin/bash
# killable-shell.sh: Kills itself and all children (the whole process group) when killed.
# Adapted from http://stackoverflow.com/a/2173421 and http://veithen.github.io/2014/11/16/sigterm-propagation.html
# Note: Does not work (and cannot work) when the shell itself is killed with SIGKILL, for then the trap is not triggered.
trap "trap - SIGTERM && echo 'Caught SIGTERM, sending SIGTERM to process group' && kill -- -$$" SIGINT SIGTERM EXIT
echo $@
"$@" &
PID=$!
wait $PID
trap - SIGINT SIGTERM EXIT
wait $PID
Beispiel dafür:
$ bash killable-shell.sh sleep 100
sleep 100
^Z
[1] + 31568 suspended bash killable-shell.sh sleep 100
$ ps aux | grep "sleep"
niklas 31568 0.0 0.0 19640 1440 pts/18 T 01:30 0:00 bash killable-shell.sh sleep 100
niklas 31569 0.0 0.0 14404 616 pts/18 T 01:30 0:00 sleep 100
niklas 31605 0.0 0.0 18956 936 pts/18 S+ 01:30 0:00 grep --color=auto sleep
$ bg
[1] + 31568 continued bash killable-shell.sh sleep 100
$ kill 31568
Caught SIGTERM, sending SIGTERM to process group
[1] + 31568 terminated bash killable-shell.sh sleep 100
$ ps aux | grep "sleep"
niklas 31717 0.0 0.0 18956 936 pts/18 S+ 01:31 0:00 grep --color=auto sleep
Nur aus Gründen der Vielfalt werde ich eine Variation von https://stackoverflow.com/a/2173421/102484 veröffentlichen , da diese Lösung in meiner Umgebung zur Meldung "Beendet" führt:
trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT