Odpowiedzi:
Aby posprzątać trochę bałaganu, trap
można użyć. Może dostarczyć listę rzeczy wykonanych, gdy nadejdzie określony sygnał:
trap "echo hello" SIGINT
ale może być również użyty do wykonania czegoś, jeśli powłoka wyjdzie:
trap "killall background" EXIT
Jest wbudowany, więc help trap
poda informacje (działa z bash). Jeśli chcesz tylko zabijać zadania w tle, możesz to zrobić
trap 'kill $(jobs -p)' EXIT
Uważaj, aby użyć pojedynczego '
, aby zapobiec $()
natychmiastowemu podstawieniu powłoki .
kill $(jobs -p)
nie działa w desce rozdzielczej, ponieważ wykonuje podstawianie poleceń w podpowłoce (zobacz Zastępowanie poleceń w myślniku)
killall background
ma być zastępczy? background
nie ma na stronie podręcznika ...
Działa to dla mnie (poprawione dzięki komentatorom):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
4.3.30(1)-release
OSX i jest to potwierdzone również na Ubuntu . Jest jednak obvoius wokaround :)
-$$
. Zwraca wartość do „- <PID>„ np -1234
. Na wbudowanej stronie zabijania // wiodący myślnik określa sygnał do wysłania. Jednak - prawdopodobnie blokuje to, ale w przeciwnym razie wiodący myślnik nie jest udokumentowany. Jakaś pomoc?
man 2 kill
, co wyjaśnia, że gdy PID jest ujemny, sygnał jest wysyłany do wszystkich procesów w grupie procesów o podanym identyfikatorze ( en.wikipedia.org/wiki/Process_group ). To mylące, że nie jest to wymienione w man 1 kill
lub man bash
, i może być uznane za błąd w dokumentacji.
Aktualizacja: https://stackoverflow.com/a/53714583/302079 poprawia to, dodając status wyjścia i funkcję czyszczenia.
trap "exit" INT TERM
trap "kill 0" EXIT
Po co konwertować INT
i TERM
wyjść? Ponieważ oba powinny wyzwalać kill 0
nie wchodząc w nieskończoną pętlę.
Dlaczego spust kill 0
naEXIT
? Ponieważ normalne wyjścia ze skryptów również powinny się uruchamiać kill 0
.
Dlaczego kill 0
? Ponieważ zagnieżdżone podpowłoki również muszą zostać zabite. Spowoduje to usunięcie całego drzewa procesów .
kill 0
oznacza / robi?
pułapka „kill $ (jobs -p)” EXIT
Dokonałbym tylko drobnych zmian w odpowiedzi Johannesa i używał zadania -pr, aby ograniczyć zabijanie do uruchomionych procesów i dodać kilka dodatkowych sygnałów do listy:
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
trap 'kill 0' SIGINT SIGTERM EXIT
Rozwiązanie opisane w użytkownika @ tokland odpowiedź jest naprawdę ładne, ale ostatni Bash awarii z winy segmentacja podczas korzystania z niego. Jest tak, ponieważ Bash, począwszy od wersji 4.3, pozwala na rekurencję pułapki, która w tym przypadku staje się nieskończona:
SIGINT
lub SIGTERM
lub EXIT
;kill 0
, co wysyła SIGTERM
do wszystkich procesów w grupie, w tym do samej powłoki;Można to obejść, ręcznie wyrejestrowując pułapkę:
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
Bardziej wymyślny sposób, który pozwala wydrukować otrzymany sygnał i pozwala uniknąć komunikatów „Zakończone”:
#!/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 : dodano minimalny przykład; ulepszona stop
funkcja aviod-pułapkowania niepotrzebnych sygnałów i ukrywania komunikatów „Terminated:” na wyjściu. Dzięki Trevor Boyd Smith za sugestie!
stop()
podać pierwszy argument jako numer sygnału ale wtedy hardcode jakie sygnały są wyrejestrowane. zamiast na stałe wyrejestrowywać sygnały, możesz użyć pierwszego argumentu do wyrejestrowania stop()
funkcji (może to potencjalnie zatrzymać inne sygnały rekurencyjne (inne niż 3 zakodowane na stałe)).
SIGINT
, ale kill 0
wysyła SIGTERM
, co ponownie zostanie uwięzione. Nie spowoduje to jednak nieskończonej rekurencji, ponieważ SIGTERM
podczas drugiego stop
połączenia zostanie zablokowany .
trap - $1 && kill -s $1 0
powinien działać lepiej. Przetestuję i zaktualizuję tę odpowiedź. Dziękuję za fajny pomysł! :)
trap - $1 && kill -s $1 0
też nie zadziała, bo nie możemy zabijać EXIT
. Ale to naprawdę wystarczy zrobić pułapkę TERM
, ponieważ kill
domyślnie wysyła ten sygnał.
EXIT
, trap
procedura obsługi sygnału jest zawsze wykonywana tylko raz.
Dla pewności lepiej jest zdefiniować funkcję czyszczenia i wywołać ją z pułapki:
cleanup() {
local pids=$(jobs -pr)
[ -n "$pids" ] && kill $pids
}
trap "cleanup" INT QUIT TERM EXIT [...]
lub całkowicie unikając funkcji:
trap '[ -n "$(jobs -pr)" ] && kill $(jobs -pr)' INT QUIT TERM EXIT [...]
Czemu? Ponieważ po prostu używając trap 'kill $(jobs -pr)' [...]
jednego zakłada się, że będą uruchomione zadania w tle, gdy zasygnalizowany zostanie stan pułapki. Gdy nie ma żadnych zadań, zobaczysz następujący (lub podobny) komunikat:
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
ponieważ jobs -pr
jest pusty - skończyłem w tej „pułapce” (gra słów zamierzona).
[ -n "$(jobs -pr)" ]
nie działa na moim bashu. Używam GNU bash, wersja 4.2.46 (2) -release (x86_64-redhat-linux-gnu). Pojawia się komunikat „zabij: użycie”.
jobs -pr
nie zwraca PID dzieci procesów w tle. Nie rozrywa całego drzewa procesu, jedynie przycina korzenie.
Ładna wersja, która działa pod Linuksem, BSD i MacOS X. Najpierw próbuje wysłać SIGTERM, a jeśli to się nie powiedzie, zabija proces po 10 sekundach.
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
Pamiętaj, że zadania nie obejmują procesów grand dzieci.
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
Jak https://stackoverflow.com/a/22644006/10082476 , ale z dodanym kodem wyjścia
exit_code
bierze INT TERM
pułapka?
zadania -p nie działa we wszystkich powłokach, jeśli są wywoływane w podpowłoce, chyba że jego dane wyjściowe zostaną przekierowane do pliku, ale nie do potoku. (Zakładam, że pierwotnie był przeznaczony wyłącznie do użytku interaktywnego).
Co z następującymi kwestiami:
trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]
Wezwanie do „zadań” jest potrzebne w powłoce Debiana, która nie aktualizuje bieżącego zadania („%%”), jeśli go brakuje.
trap 'echo in trap; set -x; trap - TERM EXIT; while kill %% 2>/dev/null; do jobs > /dev/null; done; set +x' INT TERM EXIT; sleep 100 & while true; do printf .; sleep 1; done
Jeśli uruchomisz go w Bash (5.0.3) i spróbujesz zakończyć, wydaje się, że istnieje nieskończona pętla. Jeśli jednak zakończysz to ponownie, zadziała. Nawet przez Dash (0.5.10.2-6) musisz go dwukrotnie zakończyć.
Dokonałem adaptacji odpowiedzi @ tokland w połączeniu z wiedzą z http://veithen.github.io/2014/11/16/sigterm-propagation.html, gdy zauważyłem, że trap
nie uruchamia się, jeśli uruchomię proces pierwszego planu (bez tła &
):
#!/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
Przykład jego działania:
$ 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
Dla urozmaicenia opublikuję odmianę https://stackoverflow.com/a/2173421/102484 , ponieważ to rozwiązanie prowadzi do komunikatu „Zakończono” w moim środowisku:
trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT