Cum să utilizați semnalele Linux în scripturile Bash

Publicat: 2022-08-09
Laptop Linux afișează un prompt bash
fatmawati achmad zaenuri/Shutterstock.com

Nucleul Linux trimite semnale către procese despre evenimentele la care trebuie să reacționeze. Scripturile bine comportate gestionează semnalele elegant și robust și pot curăța în spatele lor, chiar dacă apăsați Ctrl+C. Iată cum.

Semnale și procese

Semnalele sunt mesaje scurte, rapide, unidirecționale trimise către procese precum scripturi, programe și demoni. Ei au anunțat procesul despre ceva ce sa întâmplat. Este posibil ca utilizatorul să fi apăsat Ctrl+C sau este posibil ca aplicația să fi încercat să scrie în memoria la care nu are acces.

Dacă autorul procesului a anticipat că i-ar putea fi trimis un anumit semnal, poate scrie o rutină în program sau script pentru a gestiona acel semnal. O astfel de rutină se numește un handler de semnal . Captează sau prinde semnalul și efectuează o anumită acțiune ca răspuns la acesta.

Cum să gestionați procesele de la terminalul Linux: 10 comenzi pe care trebuie să le cunoașteți
LEGATE Cum să gestionați procesele de la terminalul Linux: 10 comenzi pe care trebuie să le cunoașteți

Linux folosește o mulțime de semnale, așa cum vom vedea, dar din punct de vedere al scripturilor, există doar un mic subset de semnale de care probabil că veți fi interesat. În special, în scripturile non-triviale, semnalele care spun scriptul de închidere ar trebui să fie blocat (acolo unde este posibil) și efectuată o închidere grațioasă.

De exemplu, scripturilor care creează fișiere temporare sau deschid porturi firewall li se poate oferi șansa de a șterge fișierele temporare sau de a închide porturile înainte de a se închide. Dacă scriptul moare în momentul în care primește semnalul, computerul poate fi lăsat într-o stare imprevizibilă.

Iată cum puteți gestiona semnalele în propriile scripturi.

Faceți cunoștință cu Semnalele

Unele comenzi Linux au nume criptice. Nu așa este comanda care captează semnalele. Se numește trap . De asemenea, putem folosi trap cu opțiunea -l (listă) pentru a ne arăta întreaga listă de semnale pe care le folosește Linux.

 capcană -l 

Listarea semnalelor în Ubuntu cu trap -l

Deși lista noastră numerotată se termină la 64, există de fapt 62 de semnale. Semnalele 32 și 33 lipsesc. Nu sunt implementate în Linux. Au fost înlocuite cu funcționalitatea compilatorului gcc pentru gestionarea firelor de execuție în timp real. Totul, de la semnalul 34, SIGRTMIN , la semnalul 64, SIGRTMAX , sunt semnale în timp real.

Veți vedea liste diferite pe diferite sisteme de operare asemănătoare Unix. Pe OpenIndiana, de exemplu, semnalele 32 și 33 sunt prezente, împreună cu o grămadă de semnale suplimentare ducând numărul total la 73.

Listarea semnalelor în OpenIndiana cu capcana -l

Semnalele pot fi referite după nume, număr sau după numele lor prescurtat. Numele lor prescurtat este pur și simplu numele lor, cu „SIG” principal eliminat.

Semnalele sunt ridicate din multe motive diferite. Dacă le puteți descifra, scopul lor este conținut în numele lor. Impactul unui semnal se încadrează în una dintre câteva categorii:

  • Terminare: Procesul este încheiat.
  • Ignora: semnalul nu afectează procesul. Acesta este un semnal numai de informare.
  • Core: este creat un fișier dump-core. Acest lucru se face de obicei deoarece procesul a încălcat într-un fel, cum ar fi o încălcare a memoriei.
  • Oprire: procesul este oprit. Adică este întreruptă , nu terminată.
  • Continuare: Spune unui proces oprit să continue execuția.

Acestea sunt semnalele pe care le veți întâlni cel mai frecvent.

  • SIGHUP : Semnal 1. Conexiunea la o gazdă la distanță, cum ar fi un server SSH, a întrerupt în mod neașteptat sau utilizatorul s-a deconectat. Un script care primește acest semnal se poate termina cu grație sau poate alege să încerce să se reconecteze la gazda de la distanță.
  • SIGINT : Semnal 2. Utilizatorul a apăsat combinația Ctrl+C pentru a forța un proces să se închidă, sau comanda kill a fost folosită cu semnalul 2. Din punct de vedere tehnic, acesta este un semnal de întrerupere, nu un semnal de terminare, ci un script întrerupt fără un handler de semnal se va termina de obicei.
  • SIGQUIT : Semnalul 3. Utilizatorul a apăsat combinația Ctrl+D pentru a forța un proces să iasă, sau comanda kill a fost folosită cu semnalul 3.
  • SIGFPE : Semnal 8. Procesul a încercat să efectueze o operație matematică ilegală (imposibilă), cum ar fi împărțirea la zero.
  • SIGKILL : Semnalul 9. Acesta este echivalentul semnalului unei ghilotine. Nu o poți prinde sau ignora și se întâmplă instantaneu. Procesul se încheie imediat.
  • SIGTERM : Semnal 15. Aceasta este versiunea mai atentă a SIGKILL . SIGTERM spune, de asemenea, unui proces să se termine, dar acesta poate fi blocat și procesul își poate rula procesele de curățare înainte de a se închide. Acest lucru permite o oprire grațioasă. Acesta este semnalul implicit ridicat de comanda kill .

Semnale pe linia de comandă

O modalitate de a capta un semnal este să utilizați trap cu numărul sau numele semnalului și un răspuns pe care doriți să se întâmple dacă semnalul este primit. Putem demonstra acest lucru într-o fereastră de terminal.

Această comandă captează semnalul SIGINT . Răspunsul este să tipăriți o linie de text în fereastra terminalului. Folosim opțiunea -e (activare escapes) cu echo , astfel încât să putem folosi specificatorul de format „ \n ”.

 capcană „echo -e „\nCtrl+c detectat.”” SIGINT 

Captură Ctrl+C pe linia de comandă

Linia noastră de text este tipărită de fiecare dată când apăsăm combinația Ctrl+C.

Pentru a vedea dacă o capcană este setată pe un semnal, utilizați opțiunea -p (capcană de imprimare).

 capcană -p SIGINT 

Verificarea dacă o capcană este pusă pe un semnal

Utilizarea trap fără opțiuni face același lucru.

Pentru a reseta semnalul la starea normală, neblocat, utilizați o cratimă „ - ” și numele semnalului prins.

 capcană - SIGINT
 capcană -p SIGINT 

Îndepărtarea unei capcane dintr-un semnal

Nicio ieșire de la comanda trap -p indică că nu există nicio capcană setată pe acel semnal.

Captarea semnalelor în scripturi

Putem folosi aceeași comandă de format general trap în interiorul unui script. Acest script captează trei semnale diferite, SIGINT , SIGQUIT și SIGTERM .

 #!/bin/bash

capcană "echo am fost SIGINT terminat; ieșire" SIGINT
capcană "echo am fost SIGQUIT terminat; ieșire" SIGQUIT
capcană "echo am fost SIGTERM terminat; ieșire" SIGTERM

eco $$
contor=0

în timp ce adevărat
do 
  echo „Numărul buclei:” $((++contor))
  somn 1
Terminat

Cele trei declarații trap sunt în partea de sus a scenariului. Rețineți că am inclus comanda de exit în răspunsul la fiecare dintre semnale. Aceasta înseamnă că scriptul reacționează la semnal și apoi iese.

Copiați textul în editor și salvați-l într-un fișier numit „simple-loop.sh” și faceți-l executabil folosind comanda chmod . Va trebui să faceți asta pentru toate scripturile din acest articol dacă doriți să urmați pe propriul computer. Utilizați doar numele scriptului corespunzător în fiecare caz.

 chmod +x simple-loop.sh 

Realizarea unui script executabil cu chmod

Restul scenariului este foarte simplu. Trebuie să cunoaștem ID-ul procesului al scriptului, așa că avem ca ecou script-ul pentru noi. Variabila $$ deține ID-ul procesului al scriptului.

Creăm o variabilă numită counter și o setăm la zero.

Bucla while va rula pentru totdeauna dacă nu este oprită forțat. Incrementează variabila counter , o transmite pe ecran și se așteaptă pentru o secundă.

Să rulăm scriptul și să-i trimitem semnale diferite.

 ./simple-loop.sh 

Un script care îl identifică a fost încheiat cu Ctrl+C

Când apăsăm „Ctrl+C”, mesajul nostru este tipărit în fereastra terminalului și scriptul este terminat.

Să-l rulăm din nou și să trimitem semnalul SIGQUIT folosind comanda kill . Va trebui să facem asta dintr-o altă fereastră de terminal. Va trebui să utilizați ID-ul procesului care a fost raportat de propriul script.

 ./simple-loop.sh
 ucide -SIGQUIT 4575 

Un script care îl identifică a fost încheiat cu SIGQUIT

După cum era de așteptat, scriptul raportează sosirea semnalului, apoi se termină. Și, în sfârșit, pentru a dovedi ideea, o vom face din nou cu semnalul SIGTERM .

 ./simple-loop.sh
 ucide -SIGTERM 4584 

Un script care îl identifică a fost încheiat cu SIGTERM

Am verificat că putem capta mai multe semnale într-un script și că putem reacționa la fiecare în mod independent. Pasul care promovează toate acestea de la interesant la util este adăugarea de gestionare a semnalelor.

Manipularea semnalelor în scripturi

Putem înlocui șirul de răspuns cu numele unei funcții din scriptul dumneavoastră. Comanda trap apelează apoi acea funcție când semnalul este detectat.

Copiați acest text într-un editor și salvați-l ca fișier numit „grace.sh” și faceți-l executabil cu chmod .

 #!/bin/bash

trap graceful_shutdown SIGINT SIGQUIT SIGTERM

graceful_shutdown()
{
  echo -e „\nSe elimină fișierul temporar:” $temp_file
  rm -rf „$temp_file”
  Ieșire
}

temp_file=$(mktemp -p /tmp tmp.XXXXXXXXX)
echo „Fișier temp creat:” $temp_file

contor=0

în timp ce adevărat
do 
  echo „Numărul buclei:” $((++contor))
  somn 1
Terminat

Scriptul setează o capcană pentru trei semnale diferite — SIGHUP , SIGINT și SIGTERM — folosind o singură instrucțiune trap . Răspunsul este numele funcției graceful_shutdown() . Funcția este apelată ori de câte ori este recepționat unul dintre cele trei semnale blocate.

Scriptul creează un fișier temporar în directorul „/tmp”, folosind mktemp . Șablonul de nume de fișier este „tmp.XXXXXXXXX”, deci numele fișierului va fi „tmp”. urmat de zece caractere alfanumerice aleatorii. Numele fișierului este redat pe ecran.

Restul scriptului este același cu cel anterior, cu o variabilă counter și o buclă while infinită.

 ./grație.sh 

Un script care efectuează o închidere grațioasă prin ștergerea unui fișier temporar

Când fișierului i se trimite un semnal care determină închiderea acestuia, este apelată funcția graceful_shutdown() . Acest lucru șterge unicul nostru fișier temporar. Într-o situație reală, ar putea efectua orice curățare necesită scriptul tău.

De asemenea, am combinat toate semnalele prinse și le-am gestionat cu o singură funcție. Puteți capta semnalele individual și le puteți trimite către propriile lor funcții de gestionare dedicate.

Copiați acest text și salvați-l într-un fișier numit „triple.sh” și faceți-l executabil folosind comanda chmod .

 #!/bin/bash

capcană sigint_handler SIGINT
trap sigusr1_handler SIGUSR1
trap exit_handler EXIT

funcția sigint_handler() {
  ((++sigint_count))

  echo -e „\nSIGINT a primit $sigint_count timp(e).”

  dacă [[ "$sigint_count" -eq 3 ]]; apoi
    echo „Începe închiderea”.
    loop_flag=1
  fi
}

funcția sigusr1_handler() {
  echo „SIGUSR1 a trimis și primit $((++sigusr1_count)) timp(e).”
}

funcția exit_handler() { 
  echo „Exit handler: Scriptul se închide...”
}

eco $$
sigusr1_count=0
sigint_count=0
loop_flag=0

while [[ $loop_flag -eq 0 ]]; do
  ucide -SIGUSR1 $$
  somn 1
Terminat

Definim trei capcane în partea de sus a scenariului.

  • Unul prinde SIGINT și are un handler numit sigint_handler() .
  • Al doilea captează un semnal numit SIGUSR1 și folosește un handler numit sigusr1_handler() .
  • Capcana numărul trei captează semnalul EXIT . Acest semnal este ridicat de scriptul însuși când se închide. Setarea unui handler de semnal pentru EXIT înseamnă că puteți seta o funcție care va fi apelată întotdeauna când scriptul se termină (cu excepția cazului în care este oprit cu semnalul SIGKILL ). Handler-ul nostru se numește exit_handler() .

SIGUSR1 și SIGUSR2 sunt semnale furnizate astfel încât să puteți trimite semnale personalizate către scripturile dvs. Modul în care interpretezi și reacționezi la ele depinde în întregime de tine.

Lăsând deocamdată manevrele de semnal deoparte, corpul scriptului ar trebui să vă fie familiar. Acesta transmite ID-ul procesului în fereastra terminalului și creează unele variabile. Variabila sigusr1_count înregistrează de câte ori a fost tratat SIGUSR1 , iar sigint_count înregistrează de câte ori a fost tratat SIGINT . Variabila loop_flag este setată la zero.

Bucla while nu este o buclă infinită. Se va opri bucla dacă variabila loop_flag este setată la orice valoare diferită de zero. Fiecare rotire a buclei while folosește kill pentru a trimite semnalul SIGUSR1 către acest script, trimițându-l la ID-ul de proces al scriptului. Scripturile pot trimite semnale către ele însele!

Funcția sigusr1_handler() incrementează variabila sigusr1_count și trimite un mesaj către fereastra terminalului.

De fiecare dată când semnalul SIGINT este primit, funcția siguint_handler() incrementează variabila sigint_count și transmite valoarea acesteia în fereastra terminalului.

Dacă variabila sigint_count este egală cu trei, variabila loop_flag este setată la unu și este trimis un mesaj către fereastra terminalului care informează utilizatorul că procesul de oprire a început.

Deoarece loop_flag nu mai este egal cu zero, bucla while se termină și scriptul este terminat. Dar această acțiune ridică automat semnalul EXIT și funcția exit_handler() este apelată.

 ./triplu.sh 

Un script care utilizează SIGUSR1, care necesită trei combinații Ctrl+C pentru a se închide și captează semnalul EXIT la închidere

După trei apăsări Ctrl+C, scriptul se termină și invocă automat funcția exit_handler() .

Citiți Semnalele

Prin captarea semnalelor și tratarea cu ele în funcții simple de gestionare, puteți face scripturile dvs. Bash ordonate în spatele lor, chiar dacă sunt terminate în mod neașteptat. Asta vă oferă un sistem de fișiere mai curat. De asemenea, previne instabilitatea data viitoare când rulați scriptul și, în funcție de scopul scriptului dvs., ar putea chiar preveni găurile de securitate.

LEGATE: Cum să auditați securitatea sistemului dvs. Linux cu Lynis