Come posso terminare processi / lavori in background quando termina il mio script di shell?


194

Sto cercando un modo per ripulire il casino quando il mio script di livello superiore esce.

Soprattutto se voglio usarlo set -e, vorrei che il processo in background morisse quando lo script usciva.

Risposte:


186

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.


allora come uccidi tutti i bambini ? (o mi sto perdendo qualcosa di ovvio)
elmarco l'

18
killall uccide i tuoi figli, ma non tu
orip

4
kill $(jobs -p)non funziona nel trattino, perché esegue la sostituzione dei comandi in una subshell (vedere Sostituzione dei comandi nel trattino man)
user1431317

9
si killall backgroundsuppone che sia un segnaposto? backgroundnon è nella pagina man ...
Evan Benn l'

170

Questo funziona per me (migliorato grazie ai commentatori):

trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
  • kill -- -$$invia un SIGTERM all'intero gruppo di processi, uccidendo così anche i discendenti.

  • La specifica del segnale EXITè utile quando si utilizza set -e(maggiori dettagli qui ).


1
Dovrebbe funzionare bene nel complesso, ma i processi figlio possono cambiare i gruppi di processi. D'altra parte non richiede il controllo del lavoro e può anche far mancare alcuni processi ai nipoti da altre soluzioni.
michaeljt,

5
Nota, "kill 0" ucciderà anche uno script bash parent. Puoi usare "kill - - $ BASHPID" per uccidere solo i figli dello script corrente. Se non hai $ BASHPID nella tua versione bash, puoi esportare BASHPID = $ (sh -c 'echo $ PPID')
ACyclic

2
Grazie per la bella e chiara soluzione! Sfortunatamente, segfault Bash 4.3, che consente la ricorsione delle trap. Mi sono imbattuto in questo su 4.3.30(1)-releaseOSX, ed è anche confermato su Ubuntu . C'è un ovvio wokaround , però :)
skozin,

4
Non capisco bene -$$. 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?
Evan Benn,

4
@EvanBenn: Verifica 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.
user001

111

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 .


3
L'unica soluzione per il mio caso su Debian.
MindlessRanger,

3
Né la risposta di Johannes Schaub né quella fornita da tokland sono riuscite a uccidere i processi in background avviati dal mio script shell (su Debian). Questa soluzione ha funzionato. Non so perché questa risposta non sia più votata. Potresti espandere di più su cosa kill 0significhi / faccia esattamente ?
Jos

7
Questo è fantastico, ma uccide anche la mia shell genitore :-(
vidstige

5
Questa soluzione è letteralmente eccessiva. kill 0 (all'interno della mia sceneggiatura) ha rovinato tutta la mia sessione X! Forse in alcuni casi uccidere 0 può essere utile, ma ciò non cambia il fatto che non è una soluzione generale e dovrebbe essere evitato se possibile a meno che non ci siano ottime ragioni per usarlo. Sarebbe bello aggiungere un avvertimento che potrebbe uccidere la shell genitore o addirittura l'intera sessione X, non solo i lavori in background di uno script!
Lissanro Rayen,

3
Anche se questa potrebbe essere una soluzione interessante in alcune circostanze, come sottolineato da @vidstige, questo ucciderà l' intero gruppo di processi che include il processo di avvio (cioè la shell genitore nella maggior parte dei casi). Sicuramente non è qualcosa che si desidera quando si esegue uno script tramite un IDE.
matpen

21

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

Perché non uccidere anche i lavori interrotti? In Bash EXIT verrà eseguita anche la trap in caso di SIGINT e SIGTERM, quindi la trap verrà chiamata due volte in caso di tale segnale.
jarno,

14

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:

  1. il processo di shell riceve SIGINTo SIGTERMo EXIT;
  2. il segnale viene intrappolato, in esecuzione kill 0, che invia SIGTERMa tutti i processi nel gruppo, incluso lo shell stesso;
  3. vai a 1 :)

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!


in 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)).
Trevor Boyd Smith,

@TrevorBoydSmith, questo non funzionerebbe come previsto, immagino. Ad esempio, la shell potrebbe essere uccisa con 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.
skozin,

Probabilmente, trap - $1 && kill -s $1 0dovrebbe funzionare meglio. Testerò e aggiornerò questa risposta. Grazie per la bella idea! :)
skozin,

No, 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.
skozin,

Ho testato la ricorsione con EXIT, il trapgestore del segnale viene sempre eseguito solo una volta.
Trevor Boyd Smith,

9

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).


Questo caso di test [ -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.
Douwe van der Leest,

Sospetto che abbia a che fare con il fatto che jobs -prnon restituisce i PID dei figli dei processi in background. Non abbatte l'intero albero del processo, ma solo le radici.
Douwe van der Leest,

2

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.



1

Un'altra opzione è quella di impostare lo script come leader del gruppo di processi e intercettare un killpg sul gruppo di processi all'uscita.


Come si imposta il processo come leader del gruppo di processi? Che cos'è "killpg"?
jarno,

0

Quindi script il caricamento dello script. Esegui un killallcomando (o qualsiasi cosa sia disponibile sul tuo sistema operativo) che viene eseguito non appena lo script è terminato.


0

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.


Hmm approccio interessante, ma non sembra funzionare. Considera scipt 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.
jarno,

0

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.