Cum să utilizați semnalele Linux în scripturile Bash
Publicat: 2022-08-09
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.
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
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.
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 comandakill
.
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
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
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
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
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
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
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
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
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 numitsigint_handler()
. - Al doilea captează un semnal numit
SIGUSR1
și folosește un handler numitsigusr1_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 pentruEXIT
î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 semnalulSIGKILL
). Handler-ul nostru se numeșteexit_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
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