Bash scripting - segnali

Da Guide@Debianizzati.Org.
Versione del 1 nov 2014 alle 11:19 di S3v (discussione | contributi)
(diff) ← Versione meno recente | Versione attuale (diff) | Versione più recente → (diff)
Vai alla navigazione Vai alla ricerca
Bash scripting

Sommario

  1. Introduzione
  2. Comandi essenziali
  3. Variabili (stringhe)
  4. Caratteri di escape, apici e virgolette
  5. Espansioni in stringhe quotate
  6. Espansioni non quotabili
  7. Istruzioni composte
  8. Funzioni
  9. File descriptor e redirezioni
  10. Segnali

Ogni processo in Unix e Unix-like può ricevere dei segnali da altri processi (se appartengono allo stesso utente oppure da root). Di seguito i principali, tra quelli previsti da POSIX:

  • INT : è equivalente a Ctrl-c da tastiera, per uno script associato a un terminale, se non è eseguito in background. L'azione di default è la terminazione;
  • QUIT : è equivalente a Ctrl-\ da tastiera, per uno script associato a un terminale, se non è eseguito in background. L'azione di default è la terminazione con core dump;
  • HUP : è equivalente a chiudere il terminale, per uno script associato a un terminale, anche se eseguito in background; o in generale alla morte di un processo padre. Viene inviato automaticamente, e l'azione di default è la terminazione;
  • TERM : è il segnale di terminazione di default. L'azione di default è la terminazione;
  • KILL : è un segnale di terminazione che non può essere ignorato (si veda trap). L'unica azione che sarà eseguita è la sua terminazione immediata;
  • PIPE : è il segnale ricevuto da un processo che invia l'output a una pipe, quando il processo lettore ha terminato. Viene inviato automaticamente, e l'azione di default è la terminazione;
  • ABRT : è il segnale di abort. L'azione di default è la terminazione con core dump;
  • STOP : è equivalente a Ctrl-z da tastiera, per uno script associato a un terminale non eseguito in background, ma come KILL non può mai essere ignorato (si veda trap). L'unica azione che sarà eseguita è la sua interruzione immediata;
  • CONT : è il segnale per continuare l'esecuzione per un processo interrotto. Se il processo non è interrotto, di default il segnale è ignorato;
  • TSTP : è equivalente a Bloc Scor (o Ctrl-s; per riprendere: Ctrl-q), per bloccare l'output. L'azione di default è interrompere il processo;
  • CHLD : è il segnale che un processo figlio è terminato. Viene inviato automaticamente, e di default è ignorato;
  • WINCH : è il segnale che la finestra del terminale è stata ridimensionata. Viene inviato automaticamente, e di default è ignorato;
  • USR1 : è un segnale lasciato da definire all'utente. L'azione di default è la terminazione;
  • USR2 : è un segnale lasciato da definire all'utente. L'azione di default è la terminazione.

Il core dump è un dump dello stato (o una sua parte) del processo terminato, a scopo di debug. Può essere limitato e anche disattivato interamente. Se non attivo, l'azione eseguita è equivalente a una terminazione senza core dump.

Invio di segnali

Alcuni segnali vengono inviati automaticamente, al sussistere di determinate condizioni, ma possono anche essere inviati esplicitamente con il comando kill come tutti gli altri.
Sintassi: kill [ -s SIGNAL ] pid
Invia un segnale (di default: TERM) al processo con il PID scelto. Si noti che l'invio è asincrono, ossia kill non resta in attesa che il processo con PID scelto effettui l'azione associata al segnale. Di conseguenza il comando ha sempre successo, salvo che il PID non esista o non possa ricevere il segnale (per esempio perché non è dello stesso utente).

Cattura dei segnali

Le azioni da intraprendere possono essere modificati con l'istruzione trap.
Sintassi: trap 'stringa' SIGNAL1 [ ... ]
Cattura i segnali (se possibile, non lo è per KILL e STOP), con il seguente comportamento:

  • se la stringa è il solo carattere -, le azioni associate alla lista di segnali sono impostate nuovamente come quelle di default per i segnali;
  • se la stringa è vuota, i segnali verranno ignorati;
  • se la stringa è il nome di una funzione, verrà eseguita ogni volta che uno dei segnali viene ricevuto. L'azione normale è sovrascritta, quindi se è necessario preservarla dopo la funzione, dovrà occuparsene la funzione stessa (per esempio di uscire con exit dopo aver effettuato la pulizia);
  • il segnale può essere EXIT, che ha significato solo per questa istruzione, in tal caso la funzione sarà eseguita alla fine dello script, che può essere causata anche da segnali (con l'unica eccezione di KILL), purché lo script non termini con un exec seguito da un eseguibile. In POSIX invece la funzione sarebbe eseguita soltanto in caso di uscita normale dello script. In ogni caso la terminazione dello script non è annullata definendo un azione, come invece per i veri segnali.
Warning.png ATTENZIONE
Al posto di 'stringa' nell'istruzione trap può esserci un qualsiasi comando, ma si consiglia l'uso di una funzione sia per chiarezza del codice, sia soprattutto per evitare la doppia interpretazione con possibili espansioni in fase di definizione di trap prima e di esecuzione del comando poi. Un uso diverso ha la stessa pericolosità di eval, e può portare a code injection e privilege escalation.


In caso di esecuzione di una subshell le azioni da intraprendere in seguito alla ricezione di un segnale vengono reimpostate al loro valore di default, ma i segnali ignorati continuano a restare ignorati.

Esempi

  • Ignora i segnali di terminazione:
trap '' INT HUP QUIT TERM  # segnali standard di terminazione

Nota che KILL non può essere ignorato e nemmeno catturato, quindi in genere è sconsigliabile non prevedere una terminazione non brutale attraverso un segnale.

  • Ignora il segnale HUP (equivalente a eseguire lo script con il comando nohup), per permettere di avviare lo script in background e sopravvivere anche dopo la chiusura del terminale associato:
trap '' HUP
  • Esegue una funzione (interrupt) alla ricezione di tutti i segnali (standard) di terminazione, prima di uscire:
interrupt () {
    # fai qualcosa
    ...
    exit 127 # chiusura esplicita NECESSARIA
             # perché l'azione di default è sovrascritta
}

...

# esegue interrupt se uno dei segnali è ricevuto
# e poi esci (perché specificato in interrupt!)
trap 'interrupt' INT HUP QUIT TERM

...

exit 0       # non esegue interrupt
  • Ignora solo i segnali di terminazione da tastiera, ma esegue una funzione (interrupt) con il segnale standard di terminazione:
interrupt () {
    # fai qualcosa
    ...
    exit 127 # chiusura esplicita NECESSARIA
             # perché l'azione di default è sovrascritta
}

# ignora terminazione da tastiera
trap '' INT QUIT

# gestisci altri segnali di terminazione
trap 'interrupt' HUP TERM
  • Esegue una funzione di pulizia (clean) al termine dello script:
clean () {
    # funzione di pulizia
    ...
    # NOTA: niente exit finale per evitare loop infinito
    # infatti è già richiamata da trap ... EXIT!
}

...

# la funzione clean è eseguita alla terminazione
trap 'clean' EXIT

# esci (codice 127), eseguendo clean,  alla ricezione di questi segnali
# non è necessario in bash, perché è implicito
trap 'exit 127' INT HUP QUIT TERM

...
if comando; then
    exec eseguibile # clean NON è eseguita dopo exec e un eseguibile
fi
...
exit 0              # clean è eseguita anche con exit