Risposte:
Per pulire un po 'di casino, trappuò 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 trapti 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 backgroundsuppone che sia un segnaposto? backgroundnon è nella pagina man ...
Questo funziona per me (migliorato grazie ai commentatori):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
4.3.30(1)-releaseOSX, 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 killo man bashe 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 INTe TERMuscire? Perché entrambi dovrebbero attivare il kill 0senza entrare in un ciclo infinito.
Perché grilletto kill 0su 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 0significhi / 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 EXITsoluzione 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:
SIGINTo SIGTERMo EXIT;kill 0, che invia SIGTERMa 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; stopfunzione 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 0invia SIGTERM, che rimarrà intrappolata di nuovo. Questo non produrrà una ricorsione infinita, tuttavia, poiché SIGTERMverrà de-intrappolato durante la seconda stopchiamata.
trap - $1 && kill -s $1 0dovrebbe funzionare meglio. Testerò e aggiornerò questa risposta. Grazie per la bella idea! :)
trap - $1 && kill -s $1 0non funzionerebbe troppo, dato che non possiamo uccidere EXIT. Ma è davvero sufficiente de-trap TERM, perché killinvia questo segnale per impostazione predefinita.
EXIT, il trapgestore 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 -prnon 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_codeviene in INT TERMtrappola?
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; doneSe 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 trapnon 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