Come utilizzare i segnali Linux negli script Bash

Pubblicato: 2022-08-09
Laptop Linux che mostra un prompt bash
fatmawati achmad zaenuri/Shutterstock.com

Il kernel Linux invia segnali ai processi sugli eventi a cui devono reagire. Gli script ben educati gestiscono i segnali in modo elegante e robusto e possono ripulire da soli anche se si preme Ctrl+C. Ecco come.

Segnali e processi

I segnali sono messaggi brevi, veloci e unidirezionali inviati a processi come script, programmi e demoni. Fanno sapere al processo qualcosa che è successo. L'utente potrebbe aver premuto Ctrl+C o l'applicazione potrebbe aver tentato di scrivere nella memoria a cui non ha accesso.

Se l'autore del processo ha previsto che un certo segnale potrebbe essere inviato ad esso, può scrivere una routine nel programma o nello script per gestire quel segnale. Tale routine è chiamata gestore del segnale . Cattura o intrappola il segnale ed esegue alcune azioni in risposta ad esso.

Come gestire i processi dal terminale Linux: 10 comandi che devi conoscere
CORRELATI Come gestire i processi dal terminale Linux: 10 comandi che devi conoscere

Linux usa molti segnali, come vedremo, ma dal punto di vista dello scripting, c'è solo un piccolo sottoinsieme di segnali che probabilmente ti interessano. In particolare, negli script non banali, segnali che raccontano il lo script per l'arresto deve essere bloccato (ove possibile) e deve essere eseguito un arresto regolare.

Ad esempio, agli script che creano file temporanei o aprono porte del firewall può essere data la possibilità di eliminare i file temporanei o di chiudere le porte prima che si spengano. Se lo script muore nell'istante in cui riceve il segnale, il tuo computer può rimanere in uno stato imprevedibile.

Ecco come puoi gestire i segnali nei tuoi script.

Incontra i segnali

Alcuni comandi Linux hanno nomi criptici. Non così il comando che intrappola i segnali. Si chiama trap . Possiamo anche usare trap con l'opzione -l (list) per mostrarci l'intero elenco di segnali utilizzati da Linux.

 trappola -l 

Elencare i segnali in Ubuntu con trap -l

Sebbene il nostro elenco numerato finisca a 64, in realtà ci sono 62 segnali. Mancano i segnali 32 e 33. Non sono implementati in Linux. Sono stati sostituiti da funzionalità nel compilatore gcc per la gestione dei thread in tempo reale. Tutto, dal segnale 34, SIGRTMIN , al segnale 64, SIGRTMAX , sono segnali in tempo reale.

Vedrai elenchi diversi su diversi sistemi operativi simili a Unix. Su OpenIndiana, ad esempio, sono presenti i segnali 32 e 33, insieme a una serie di segnali extra che portano il conteggio totale a 73.

Elencare i segnali in OpenIndiana con trap -l

I segnali possono essere referenziati per nome, numero o per nome abbreviato. Il loro nome abbreviato è semplicemente il loro nome con il "SIG" iniziale rimosso.

I segnali vengono generati per molte ragioni diverse. Se riesci a decifrarli, il loro scopo è contenuto nel loro nome. L'impatto di un segnale rientra in una di alcune categorie:

  • Termina: il processo è terminato.
  • Ignora: il segnale non influisce sul processo. Questo è un segnale di sola informazione.
  • Core: viene creato un file dump-core. Questo di solito viene fatto perché il processo ha trasgredito in qualche modo, ad esempio una violazione della memoria.
  • Stop: il processo viene interrotto. Cioè, è in pausa , non terminato.
  • Continua: indica a un processo interrotto di continuare l'esecuzione.

Questi sono i segnali che incontrerai più frequentemente.

  • SIGHUP : Segnale 1. La connessione a un host remoto, come un server SSH, si è interrotta in modo imprevisto o l'utente si è disconnesso. Uno script che riceve questo segnale potrebbe terminare correttamente o scegliere di tentare di riconnettersi all'host remoto.
  • SIGINT : Segnale 2. L'utente ha premuto la combinazione Ctrl+C per forzare la chiusura di un processo, oppure è stato utilizzato il comando kill con il segnale 2. Tecnicamente, questo è un segnale di interruzione, non un segnale di terminazione, ma uno script interrotto senza un gestore di segnale di solito termina.
  • SIGQUIT : Segnale 3. L'utente ha premuto la combinazione Ctrl+D per forzare l'uscita da un processo, oppure è stato utilizzato il comando kill con il segnale 3.
  • SIGFPE : Segnale 8. Il processo ha tentato di eseguire un'operazione matematica illegale (impossibile), come la divisione per zero.
  • SIGKILL : Segnale 9. Questo è il segnale equivalente di una ghigliottina. Non puoi prenderlo o ignorarlo, e succede all'istante. Il processo viene terminato immediatamente.
  • SIGTERM : Segnale 15. Questa è la versione più premurosa di SIGKILL . SIGTERM dice anche a un processo di terminare, ma può essere bloccato e il processo può eseguire i suoi processi di pulizia prima di chiudersi. Ciò consente uno spegnimento regolare. Questo è il segnale predefinito generato dal comando kill .

Segnali sulla linea di comando

Un modo per intercettare un segnale consiste nell'utilizzare trap con il numero o il nome del segnale e una risposta che si desidera avvenga se il segnale viene ricevuto. Possiamo dimostrarlo in una finestra di terminale.

Questo comando cattura il segnale SIGINT . La risposta è stampare una riga di testo nella finestra del terminale. Stiamo usando l'opzione -e (abilita escape) con echo in modo da poter usare l'identificatore di formato " \n ".

 trap 'echo -e "\nCtrl+c rilevato."' SIGINT 

Trapping Ctrl+C sulla riga di comando

La nostra riga di testo viene stampata ogni volta che premiamo la combinazione Ctrl+C.

Per vedere se una trappola è impostata su un segnale, utilizzare l'opzione -p (stampa trappola).

 trap -p SIGINT 

Verifica se una trappola è impostata su un segnale

Usare trap senza opzioni fa la stessa cosa.

Per ripristinare il segnale allo stato normale non intrappolato, utilizzare un trattino “ - ” e il nome del segnale intrappolato.

 trappola - SIGINT
 trap -p SIGINT 

Rimuovere una trappola da un segnale

Nessun output dal comando trap -p indica che non è stata impostata alcuna trap su quel segnale.

Segnali di cattura negli script

Possiamo usare lo stesso comando trap di formattazione generale all'interno di uno script. Questo script cattura tre diversi segnali, SIGINT , SIGQUIT e SIGTERM .

 #!/bin/bash

trap "echo I was SIGINT terminato; exit" SIGINT
trap "echo sono stato interrotto da SIGQUIT; uscita" SIGQUIT
trap "echo sono stato terminato da SIGTERM; uscita" SIGTERM

eco $$
contatore=0

mentre vero
fare 
  echo "Numero loop:" $((++contatore))
  dormire 1
fatto

Le tre istruzioni trap sono nella parte superiore dello script. Nota che abbiamo incluso il comando di exit all'interno della risposta a ciascuno dei segnali. Ciò significa che lo script reagisce al segnale e quindi esce.

Copia il testo nel tuo editor e salvalo in un file chiamato “simple-loop.sh” e rendilo eseguibile usando il comando chmod . Dovrai farlo su tutti gli script in questo articolo se vuoi seguire sul tuo computer. Basta usare il nome dello script appropriato in ogni caso.

 chmod +x simple-loop.sh 

Rendere eseguibile uno script con chmod

Il resto della sceneggiatura è molto semplice. Abbiamo bisogno di conoscere l'ID del processo dello script, quindi lo script ce lo fa eco. La variabile $$ contiene l'ID di processo dello script.

Creiamo una variabile chiamata counter e la impostiamo a zero.

Il ciclo while verrà eseguito per sempre a meno che non venga interrotto forzatamente. Incrementa la variabile counter , ne fa eco sullo schermo e dorme per un secondo.

Eseguiamo lo script e inviamo diversi segnali ad esso.

 ./ciclo-semplice.sh 

Uno script che lo identificava è stato terminato con Ctrl+C

Quando premiamo "Ctrl+C" il nostro messaggio viene stampato nella finestra del terminale e lo script viene terminato.

Eseguiamolo di nuovo e inviamo il segnale SIGQUIT usando il comando kill . Dovremo farlo da un'altra finestra del terminale. Dovrai utilizzare l'ID processo che è stato segnalato dal tuo script.

 ./ciclo-semplice.sh
 uccidere -SIGQUIT 4575 

Uno script che lo identificava è stato terminato con SIGQUIT

Come previsto, lo script riporta che il segnale in arrivo quindi termina. E infine, per dimostrare il punto, lo faremo di nuovo con il segnale SIGTERM .

 ./ciclo-semplice.sh
 uccidere -SIGTERM 4584 

Uno script che lo identifica è stato terminato con SIGTERM

Abbiamo verificato che possiamo intrappolare più segnali in uno script e reagire a ciascuno in modo indipendente. Il passaggio che promuove tutto questo da interessante a utile è l'aggiunta di gestori di segnale.

Gestione dei segnali negli script

Possiamo sostituire la stringa di risposta con il nome di una funzione nel tuo script. Il comando trap chiama quindi quella funzione quando viene rilevato il segnale.

Copia questo testo in un editor e salvalo come file chiamato “grace.sh” e rendilo eseguibile con chmod .

 #!/bin/bash

trap graceful_shutdown SIGINT SIGQUIT SIGTERM

grazioso_spegnimento()
{
  echo -e "\nRimozione del file temporaneo:" $temp_file
  rm -rf "$file_temp"
  Uscita
}

file_temp=$(mktemp -p /tmp tmp.XXXXXXXXXX)
echo "File temporaneo creato:" $file_temp

contatore=0

mentre vero
fare 
  echo "Numero loop:" $((++contatore))
  dormire 1
fatto

Lo script imposta una trappola per tre diversi segnali - SIGHUP , SIGINT e SIGTERM - utilizzando un'unica istruzione trap . La risposta è il nome della funzione graceful_shutdown() . La funzione viene chiamata ogni volta che viene ricevuto uno dei tre segnali intrappolati.

Lo script crea un file temporaneo nella directory "/tmp", utilizzando mktemp . Il modello del nome del file è "tmp.XXXXXXXXX", quindi il nome del file sarà "tmp". seguito da dieci caratteri alfanumerici casuali. Il nome del file viene ripetuto sullo schermo.

Il resto dello script è lo stesso del precedente, con una variabile counter e un ciclo while infinito.

 ./grazia.sh 

Uno script che esegue un arresto regolare eliminando un file temporaneo

Quando al file viene inviato un segnale che ne provoca la chiusura, viene chiamata la funzione graceful_shutdown() . Questo elimina il nostro singolo file temporaneo. In una situazione del mondo reale, potrebbe eseguire qualsiasi pulizia richiesta dal tuo script.

Inoltre, abbiamo raggruppato tutti i nostri segnali intrappolati e li abbiamo gestiti con un'unica funzione. Puoi intercettare i segnali individualmente e inviarli alle loro funzioni di gestione dedicate.

Copia questo testo e salvalo in un file chiamato “triple.sh”, e rendilo eseguibile usando il comando chmod .

 #!/bin/bash

trap sigint_handler SIGINT
trap sigusr1_handler SIGUSR1
trap exit_handler EXIT

funzione sign_handler() {
  ((++signant_count))

  echo -e "\nSIGINT ha ricevuto $sigint_count tempo(i)."

  if [[ "$sigint_count" -eq 3 ]]; poi
    echo "Avvio chiusura".
    loop_bandiera=1
  fi
}

funzione sigusr1_handler() {
  echo "SIGUSR1 ha inviato e ricevuto $((++sigusr1_count)) tempo(i)."
}

funzione gestore_uscita() { 
  echo "Gestore di uscita: lo script si sta chiudendo..."
}

eco $$
sigusr1_count=0
conteggio_segni=0
loop_flag=0

mentre [[ $loop_flag -eq 0 ]]; fare
  uccidere -SIGUSR1 $$
  dormire 1
fatto

Definiamo tre trappole nella parte superiore dello script.

  • Uno cattura SIGINT e ha un gestore chiamato sigint_handler() .
  • Il secondo cattura un segnale chiamato SIGUSR1 e usa un gestore chiamato sigusr1_handler() .
  • La trappola numero tre intrappola il segnale EXIT . Questo segnale viene generato dallo script stesso quando si chiude. L'impostazione di un gestore di segnale per EXIT significa che puoi impostare una funzione che verrà sempre chiamata quando lo script termina (a meno che non venga ucciso con il segnale SIGKILL ). Il nostro gestore si chiama exit_handler() .

SIGUSR1 e SIGUSR2 sono segnali forniti in modo che tu possa inviare segnali personalizzati ai tuoi script. Il modo in cui interpreti e reagisci a loro dipende interamente da te.

Lasciando da parte i gestori del segnale per ora, il corpo dello script dovrebbe esserti familiare. Riporta l'ID del processo alla finestra del terminale e crea alcune variabili. La variabile sigusr1_count registra il numero di volte SIGUSR1 è stato gestito e sigint_count registra il numero di volte in cui SIGINT è stato gestito. La variabile loop_flag è impostata su zero.

Il ciclo while non è un ciclo infinito. Interromperà il ciclo se la variabile loop_flag è impostata su qualsiasi valore diverso da zero. Ogni rotazione del ciclo while utilizza kill per inviare il segnale SIGUSR1 a questo script, inviandolo all'ID di processo dello script. Gli script possono inviare segnali a se stessi!

La funzione sigusr1_handler() incrementa la variabile sigusr1_count e invia un messaggio alla finestra del terminale.

Ogni volta che viene ricevuto il segnale SIGINT , la funzione siguint_handler() incrementa la variabile sigint_count e ne riproduce il valore nella finestra del terminale.

Se la variabile sigint_count è uguale a tre, la variabile loop_flag viene impostata su uno e viene inviato un messaggio alla finestra del terminale che informa l'utente che il processo di spegnimento è iniziato.

Poiché loop_flag non è più uguale a zero, il ciclo while termina e lo script è terminato. Ma quell'azione genera automaticamente il segnale EXIT e viene chiamata la funzione exit_handler() .

 ./tripla.sh 

Uno script che utilizza SIGUSR1, che richiede tre combinazioni Ctrl+C per chiudersi e cattura il segnale EXIT allo spegnimento

Dopo tre pressioni di Ctrl+C, lo script termina e richiama automaticamente la funzione exit_handler() .

Leggi i segnali

Intrappolando i segnali e trattandoli in semplici funzioni di gestione, puoi riordinare i tuoi script Bash dietro di loro anche se vengono terminati inaspettatamente. Questo ti dà un filesystem più pulito. Previene anche l'instabilità la prossima volta che esegui lo script e, a seconda dello scopo dello script, potrebbe persino prevenire falle di sicurezza.

CORRELATI: Come controllare la sicurezza del tuo sistema Linux con Lynis