Risposte:
Per pulire un po 'di casino, trap
può essere usato. Può fornire un elenco di cose eseguite quando arriva un segnale specifico:
trap "echo hello" SIGINT
ma può anche essere usato per eseguire qualcosa se la shell esce:
trap "killall background" EXIT
È integrato, quindi help trap
ti darà informazioni (funziona con bash). Se vuoi solo terminare i lavori in background, puoi farlo
trap 'kill $(jobs -p)' EXIT
Fai attenzione a usare single '
, per evitare che la shell sostituisca $()
immediatamente.
kill $(jobs -p)
non funziona nel trattino, perché esegue la sostituzione dei comandi in una subshell (vedere Sostituzione dei comandi nel trattino man)
killall background
suppone che sia un segnaposto? background
non è nella pagina man ...
Questo funziona per me (migliorato grazie ai commentatori):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
4.3.30(1)-release
OSX, ed è anche confermato su Ubuntu . C'è un ovvio wokaround , però :)
-$$
. Valuta "- <PID>" ad es -1234
. Nella manpage kill // // manin built-in un trattino iniziale specifica il segnale da inviare. Tuttavia, probabilmente lo blocca, ma in caso contrario il trattino iniziale non è documentato. Qualsiasi aiuto?
man 2 kill
, che spiega che quando un PID è negativo, il segnale viene inviato a tutti i processi nel gruppo di processi con l'ID fornito ( en.wikipedia.org/wiki/Process_group ). È confuso che questo non sia menzionato in man 1 kill
o man bash
e possa essere considerato un bug nella documentazione.
Aggiornamento: https://stackoverflow.com/a/53714583/302079 migliora questo aggiungendo lo stato di uscita e una funzione di pulizia.
trap "exit" INT TERM
trap "kill 0" EXIT
Perché convertire INT
e TERM
uscire? Perché entrambi dovrebbero attivare il kill 0
senza entrare in un ciclo infinito.
Perché grilletto kill 0
su EXIT
? Perché anche le normali uscite di script dovrebbero attivarsi kill 0
.
Perché kill 0
? Perché anche le conchiglie nidificate devono essere uccise. Questo eliminerà l'intero albero del processo .
kill 0
significhi / faccia esattamente ?
trap 'kill $ (jobs -p)' EXIT
Vorrei apportare solo lievi modifiche alla risposta di Johannes e utilizzare jobs -pr per limitare l'uccisione ai processi in esecuzione e aggiungere qualche altro segnale all'elenco:
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
La trap 'kill 0' SIGINT SIGTERM EXIT
soluzione descritta nella risposta di @ tokland è davvero bella, ma l'ultimo Bash si arresta in modo anomalo con un errore di segmantazione durante l'utilizzo. Questo perché Bash, a partire dalla versione 4.3, consente la ricorsione delle trap, che in questo caso diventa infinita:
SIGINT
o SIGTERM
o EXIT
;kill 0
, che invia SIGTERM
a tutti i processi nel gruppo, incluso lo shell stesso;Questo può essere risolto annullando la registrazione manuale della trap:
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
Il modo più elegante, che consente di stampare il segnale ricevuto ed evita i messaggi "Terminato:":
#!/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 : aggiunto esempio minimo; stop
funzione migliorata per evitare di eliminare i segnali non necessari e nascondere i messaggi "Terminati:" dall'output. Grazie a Trevor Boyd Smith per i suggerimenti!
stop()
te fornisci il primo argomento come numero di segnale ma poi decodifichi quali segnali vengono cancellati. piuttosto che hardcodificare i segnali che vengono cancellati, è possibile utilizzare il primo argomento per annullare la registrazione nella stop()
funzione (in questo modo si potrebbe potenzialmente arrestare altri segnali ricorsivi (diversi dai 3 hardcoded)).
SIGINT
, ma kill 0
invia SIGTERM
, che rimarrà intrappolata di nuovo. Questo non produrrà una ricorsione infinita, tuttavia, poiché SIGTERM
verrà de-intrappolato durante la seconda stop
chiamata.
trap - $1 && kill -s $1 0
dovrebbe funzionare meglio. Testerò e aggiornerò questa risposta. Grazie per la bella idea! :)
trap - $1 && kill -s $1 0
non funzionerebbe troppo, dato che non possiamo uccidere EXIT
. Ma è davvero sufficiente de-trap TERM
, perché kill
invia questo segnale per impostazione predefinita.
EXIT
, il trap
gestore del segnale viene sempre eseguito solo una volta.
Per essere al sicuro trovo meglio definire una funzione di pulizia e chiamarla da trap:
cleanup() {
local pids=$(jobs -pr)
[ -n "$pids" ] && kill $pids
}
trap "cleanup" INT QUIT TERM EXIT [...]
o evitando del tutto la funzione:
trap '[ -n "$(jobs -pr)" ] && kill $(jobs -pr)' INT QUIT TERM EXIT [...]
Perché? Perché semplicemente usando trap 'kill $(jobs -pr)' [...]
si presuppone che ci saranno lavori in background in esecuzione quando viene segnalata la condizione trap. Quando non ci sono lavori, verrà visualizzato il seguente messaggio (o simile):
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
perché jobs -pr
è vuoto - sono finito in quella 'trappola' (gioco di parole voluto).
[ -n "$(jobs -pr)" ]
non funziona sulla mia bash. Uso GNU bash, versione 4.2.46 (2) -release (x86_64-redhat-linux-gnu). Il messaggio "kill: use" continua ad apparire.
jobs -pr
non restituisce i PID dei figli dei processi in background. Non abbatte l'intero albero del processo, ma solo le radici.
Una bella versione che funziona su Linux, BSD e MacOS X. Prima prova a inviare SIGTERM e, se non ci riesce, termina il processo dopo 10 secondi.
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
Si noti che i lavori non comprendono i processi per nipoti.
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
Come https://stackoverflow.com/a/22644006/10082476 , ma con codice di uscita aggiunto
exit_code
viene in INT TERM
trappola?
jobs -p non funziona in tutte le shell se chiamato in una sub-shell, possibilmente a meno che il suo output non venga reindirizzato in un file ma non in una pipe. (Suppongo che fosse originariamente destinato esclusivamente a un uso interattivo.)
Che dire di quanto segue:
trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]
La chiamata a "lavori" è necessaria con la dash shell di Debian, che non aggiorna il lavoro corrente ("%%") se manca.
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
Se lo esegui in Bash (5.0.3) e provi a terminare, sembra che ci sia un ciclo infinito. Tuttavia, se lo si interrompe di nuovo, funziona. Anche da Dash (0.5.10.2-6) devi terminarlo due volte.
Ho fatto un adattamento della risposta di @ tokland combinato con la conoscenza di http://veithen.github.io/2014/11/16/sigterm-propagation.html quando ho notato che trap
non si attiva se sto eseguendo un processo in primo piano (non in background &
):
#!/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
Esempio di funzionamento:
$ 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
Solo per diversità posterò una variazione di https://stackoverflow.com/a/2173421/102484 , perché quella soluzione porta al messaggio "Terminato" nel mio ambiente:
trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT