Si të përdorni sinjalet Linux në skriptet Bash


Kerneli Linux i dërgon sinjale proceseve në lidhje me ngjarjet ndaj të cilave duhet të reagojnë. Skriptet me sjellje të mirë i trajtojnë sinjalet në mënyrë elegante dhe të fuqishme dhe mund të pastrohen pas vetes edhe nëse shtypni Ctrl+C. Ja se si.

Sinjalet dhe proceset

Sinjalet janë mesazhe të shkurtra, të shpejta, të njëanshme, të dërguara në procese të tilla si skriptet, programet dhe demonët. Ata e njoftojnë procesin për diçka që ka ndodhur. Përdoruesi mund të ketë shtypur Ctrl+C, ose aplikacioni mund të jetë përpjekur të shkruajë në memorien ku nuk ka akses.

Nëse autori i procesit ka parashikuar që një sinjal i caktuar mund t'i dërgohet atij, ata mund të shkruajnë një rutinë në program ose skript për të trajtuar atë sinjal. Një rutinë e tillë quhet trajtues sinjali. Ai kap ose bllokon sinjalin dhe kryen disa veprime në përgjigje të tij.

Linux përdor shumë sinjale, siç do të shohim, por nga pikëpamja e skriptimit, ka vetëm një nëngrup të vogël sinjalesh për të cilat ka të ngjarë të jeni të interesuar. Në veçanti, në skriptet jo të parëndësishme, sinjalet që tregojnë skripti për t'u mbyllur duhet të bllokohet (ku është e mundur) dhe të kryhet një mbyllje e këndshme.

Për shembull, skriptet që krijojnë skedarë të përkohshëm ose hapin portat e murit të zjarrit mund t'u jepet mundësia të fshijnë skedarët e përkohshëm ose të mbyllin portet përpara se të mbyllen. Nëse skripti thjesht vdes në momentin që merr sinjalin, kompjuteri juaj mund të lihet në një gjendje të paparashikueshme.

Ja se si mund të trajtoni sinjalet në skriptet tuaja.

Takoni sinjalet

Disa komanda Linux kanë emra të fshehtë. Nuk është kështu komanda që bllokon sinjalet. Quhet kurth. Mund të përdorim gjithashtu trap me opsionin -l (lista) për të na treguar të gjithë listën e sinjaleve që përdor Linux.

trap -l

Edhe pse lista jonë e numëruar përfundon në 64, në fakt ka 62 sinjale. Mungojnë sinjalet 32 dhe 33. Ato nuk janë implementuar në Linux. Ato janë zëvendësuar nga funksionaliteti në përpiluesin gcc për trajtimin e temave në kohë reale. Gjithçka nga sinjali 34, SIGRTMIN, tek sinjali 64, SIGRTMAX, janë sinjale në kohë reale.

Do të shihni lista të ndryshme në sisteme të ndryshme operative të ngjashme me Unix. Në OpenIndiana për shembull, sinjalet 32 dhe 33 janë të pranishme, së bashku me një mori sinjalesh shtesë që e çojnë numrin total në 73.

Sinjalet mund të referohen me emër, numër ose me emrin e tyre të shkurtuar. Emri i tyre i shkurtuar është thjesht emri i tyre me SIG kryesor të hequr.

Sinjalet ngrihen për shumë arsye të ndryshme. Nëse mund t'i deshifroni, qëllimi i tyre gjendet në emrin e tyre. Ndikimi i një sinjali bie në një nga disa kategori:

  • Përfundoni: Procesi përfundon.
  • Injoroni: Sinjali nuk ndikon në proces. Ky është një sinjal vetëm për informacion.
  • Bërthama: Krijohet një skedar dump-core. Kjo zakonisht bëhet sepse procesi ka tejkaluar në një farë mënyre, si për shembull një shkelje e kujtesës.
  • Ndalo: Procesi është ndalur. Kjo do të thotë, ai është ndërprerë, nuk përfundon.
  • Vazhdo: I thotë një procesi të ndaluar të vazhdojë ekzekutimin.

Këto janë sinjalet që do të hasni më shpesh.

  • SIGHUP: Sinjali 1. Lidhja me një host në distancë—si p.sh. një server SSH—ka rënë papritur ose përdoruesi ka dalë nga llogaria. Një skrip që merr këtë sinjal mund të përfundojë në mënyrë të këndshme ose mund të zgjedhë të përpiqet të rilidhet me hostin në distancë.
  • SIGINT: Sinjali 2. Përdoruesi ka shtypur kombinimin Ctrl+C për të detyruar një proces të mbyllet, ose komanda kill është përdorur me sinjalin 2. Teknikisht , ky është një sinjal ndërprerjeje, jo një sinjal përfundimi, por një skript i ndërprerë pa një mbajtës sinjali zakonisht përfundon.
  • SIGQUIT: Sinjali 3. Përdoruesi ka shtypur kombinimin Ctrl+D për të detyruar një proces të mbyllet, ose komanda kill është përdorur me sinjalin 3.< /li>
  • SIGFPE: Sinjali 8. Procesi u përpoq të kryente një operacion matematikor të paligjshëm (të pamundur), si p.sh. pjesëtimi me zero.
  • SIGKILL: Sinjali 9. Ky është ekuivalenti i sinjalit të një gijotine. Ju nuk mund ta kapni ose ta injoroni atë, dhe kjo ndodh menjëherë. Procesi përfundon menjëherë.
  • SIGTERM: Sinjali 15. Ky është versioni më i vëmendshëm i SIGKILL. SIGTERM i thotë gjithashtu një procesi që të përfundojë, por ai mund të bllokohet dhe procesi mund të kryejë proceset e tij të pastrimit përpara se të mbyllet. Kjo lejon një mbyllje të këndshme. Ky është sinjali i paracaktuar i ngritur nga komanda kill.

Sinjalet në vijën e komandës

Një mënyrë për të bllokuar një sinjal është përdorimi i trap me numrin ose emrin e sinjalit dhe një përgjigje që dëshironi të ndodhë nëse sinjali merret. Ne mund ta demonstrojmë këtë në një dritare terminali.

Kjo komandë bllokon sinjalin SIGINT. Përgjigja është të printoni një rresht teksti në dritaren e terminalit. Ne po përdorim opsionin -e (aktivizo arratisjet) me echo që të mund të përdorim ” specifikuesi i formatit.

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

Linja jonë e tekstit printohet sa herë që shtypim kombinimin Ctrl+C.

Për të parë nëse një kurth është vendosur në një sinjal, përdorni opsionin -p (kurth printimi).

trap -p SIGINT

Përdorimi i trap pa opsione bën të njëjtën gjë.

Për të rivendosur sinjalin në gjendjen e tij normale të pa kurth, përdorni vizën - dhe emrin e sinjalit të bllokuar.

trap - SIGINT
trap -p SIGINT

Asnjë dalje nga komanda trap -p tregon se nuk ka kurth të vendosur në atë sinjal.

Mbledhja e sinjaleve në skripta

Ne mund të përdorim të njëjtin komandë të formatit të përgjithshëm trap brenda një skripti. Ky skript kap tre sinjale të ndryshme, SIGINT, SIGQUIT dhe SIGTERM.

#!/bin/bash

trap "echo I was SIGINT terminated; exit" SIGINT
trap "echo I was SIGQUIT terminated; exit" SIGQUIT
trap "echo I was SIGTERM terminated; exit" SIGTERM

echo $$
counter=0

while true
do 
  echo "Loop number:" $((++counter))
  sleep 1
done

Tre deklaratat trap janë në krye të skenarit. Vini re se ne kemi përfshirë komandën dalje brenda përgjigjes ndaj secilit prej sinjaleve. Kjo do të thotë se skripti reagon ndaj sinjalit dhe më pas del.

Kopjojeni tekstin në redaktorin tuaj dhe ruajeni në një skedar të quajtur simple-loop.sh dhe bëjeni të ekzekutueshëm duke përdorur komandën chmod. Do t'ju duhet ta bëni këtë me të gjitha skriptet në këtë artikull nëse doni ta ndiqni në kompjuterin tuaj. Thjesht përdorni emrin e skriptit të duhur në secilin rast.

chmod +x simple-loop.sh

Pjesa tjetër e skenarit është shumë e thjeshtë. Ne duhet të dimë ID-në e procesit të skenarit, kështu që ne kemi jehonë të skriptit për ne. Variabli $$ mban ID-në e procesit të skriptit.

Ne krijojmë një variabël të quajtur counter dhe e vendosim atë në zero.

Cikli while do të funksionojë përgjithmonë nëse nuk ndalet me forcë. Ai rrit variablin counter, i bën jehonë në ekran dhe fle për një sekondë.

Le të ekzekutojmë skriptin dhe t'i dërgojmë sinjale të ndryshme.

./simple-loop.sh

Kur shtypim Ctrl+C mesazhi ynë printohet në dritaren e terminalit dhe skripti përfundon.

Le ta ekzekutojmë përsëri dhe të dërgojmë sinjalin SIGQUIT duke përdorur komandën kill. Ne do të duhet ta bëjmë këtë nga një dritare tjetër terminali. Do t'ju duhet të përdorni ID-në e procesit që është raportuar nga skripti juaj.

./simple-loop.sh
kill -SIGQUIT 4575

Siç pritej, skripti raporton se sinjali që mbërrin më pas përfundon. Dhe së fundi, për të vërtetuar çështjen, do ta bëjmë përsëri me sinjalin SIGTERM.

./simple-loop.sh
kill -SIGTERM 4584

Ne kemi verifikuar se mund të kapim sinjale të shumta në një skenar dhe të reagojmë ndaj secilit në mënyrë të pavarur. Hapi që promovon të gjitha këto nga interesante në të dobishme është shtimi i mbajtësve të sinjalit.

Trajtimi i sinjaleve në skripta

Ne mund të zëvendësojmë vargun e përgjigjes me emrin e një funksioni në skriptin tuaj. Komanda trap më pas thërret atë funksion kur zbulohet sinjali.

Kopjojeni këtë tekst në një redaktues dhe ruajeni si skedar të quajtur grace.sh dhe bëjeni të ekzekutueshëm me chmod.

#!/bin/bash

trap graceful_shutdown SIGINT SIGQUIT SIGTERM

graceful_shutdown()
{
  echo -e "\nRemoving temporary file:" $temp_file
  rm -rf "$temp_file"
  exit
}

temp_file=$(mktemp -p /tmp tmp.XXXXXXXXXX)
echo "Created temp file:" $temp_file

counter=0

while true
do 
  echo "Loop number:" $((++counter))
  sleep 1
done

Skripti vendos një kurth për tre sinjale të ndryshme— SIGHUP, SIGINT dhe SIGTERM — duke përdorur një deklaratë të vetme trap . Përgjigja është emri i funksionit graceful_shutdown(). Funksioni thirret sa herë që merret një nga tre sinjalet e bllokuara.

Skripti krijon një skedar të përkohshëm në drejtorinë “/tmp”, duke përdorur mktemp. Modeli i emrit të skedarit është tmp.XXXXXXXXXXXX, kështu që emri i skedarit do të jetë tmp. e ndjekur nga dhjetë karaktere alfanumerike të rastësishme. Emri i skedarit shfaqet në ekran.

Pjesa tjetër e skriptit është e njëjtë me atë të mëparshme, me një variabël counter dhe një lak të pafund while.

./grace.sh

Kur skedarit i dërgohet një sinjal që shkakton mbylljen e tij, thirret funksioni graceful_shutdown(). Kjo fshin skedarin tonë të vetëm të përkohshëm. Në një situatë të botës reale, ai mund të kryejë çdo pastrim që kërkon skenari juaj.

Gjithashtu, ne bashkuam të gjitha sinjalet tona të bllokuara së bashku dhe i trajtuam ato me një funksion të vetëm. Ju mund t'i kapni sinjalet individualisht dhe t'i dërgoni në funksionet e tyre të dedikuara të mbajtësit.

Kopjojeni këtë tekst dhe ruajeni në një skedar të quajtur triple.sh dhe bëjeni të ekzekutueshëm duke përdorur komandën chmod .

#!/bin/bash

trap sigint_handler SIGINT
trap sigusr1_handler SIGUSR1
trap exit_handler EXIT

function sigint_handler() {
  ((++sigint_count))

  echo -e "\nSIGINT received $sigint_count time(s)."

  if [[ "$sigint_count" -eq 3 ]]; then
    echo "Starting close-down."
    loop_flag=1
  fi
}

function sigusr1_handler() {
  echo "SIGUSR1 sent and received $((++sigusr1_count)) time(s)."
}

function exit_handler() { 
  echo "Exit handler: Script is closing down..."
}

echo $$
sigusr1_count=0
sigint_count=0
loop_flag=0

while [[ $loop_flag -eq 0 ]]; do
  kill -SIGUSR1 $$
  sleep 1
done

Ne përcaktojmë tre kurthe në krye të skenarit.

  • One bllokon SIGINT dhe ka një mbajtës të quajtur sigint_handler().
  • E dyta bllokon një sinjal të quajtur SIGUSR1 dhe përdor një mbajtës të quajtur sigusr1_handler() .
  • Kurthi numër tre bllokon sinjalin EXIT. Ky sinjal ngrihet nga vetë skenari kur mbyllet. Vendosja e një mbajtësi të sinjalit për EXIT do të thotë që mund të caktoni një funksion që do të thirret gjithmonë kur skripti përfundon (përveç rastit kur ai mbyllet me sinjalin SIGKILL). Trajtuesi ynë quhet exit_handler() .

SIGUSR1 dhe SIGUSR2 janë sinjale të ofruara në mënyrë që të mund të dërgoni sinjale të personalizuara në skriptet tuaja. Si i interpretoni dhe si reagoni ndaj tyre varet tërësisht nga ju.

Duke i lënë mënjanë mbajtësit e sinjalit tani për tani, trupi i skenarit duhet të jetë i njohur për ju. Ai i bën jehonë ID-së së procesit në dritaren e terminalit dhe krijon disa variabla. Variabli sigusr1_count regjistron numrin e herëve që është trajtuar SIGUSR1 dhe sigint_count regjistron numrin e herëve që është trajtuar SIGINT. Ndryshorja loop_flag është vendosur në zero.

Cikli while nuk është një cikli i pafund. Ajo do të ndalojë së bashku nëse ndryshorja loop_flag vendoset në ndonjë vlerë jo zero. Çdo rrotullim i ciklit while përdor kill për të dërguar sinjalin SIGUSR1 te ky skript, duke e dërguar në ID-në e procesit të skriptit. Skriptet mund t'i dërgojnë sinjale vetes!

Funksioni sigusr1_handler() rrit variablin sigusr1_count dhe dërgon një mesazh në dritaren e terminalit.

Sa herë që merret sinjali SIGINT, funksioni siguint_handler() rrit variablin sigint_count dhe i bën jehonë vlerës së tij në dritaren e terminalit.

Nëse ndryshorja sigint_count është e barabartë me tre, ndryshorja loop_flag vendoset në një dhe një mesazh dërgohet në dritaren e terminalit duke e njoftuar përdoruesin se procesi i mbylljes ka filluar.

Për shkak se loop_flag nuk është më i barabartë me zero, cikli while përfundon dhe skripti përfundon. Por ky veprim ngre automatikisht sinjalin EXIT dhe thirret funksioni exit_handler().

./triple.sh

Pas tre shtypjes Ctrl+C, skripti përfundon dhe automatikisht thërret funksionin exit_handler().

Lexoni sinjalet

Duke bllokuar sinjalet dhe duke i trajtuar ato në funksione të drejtpërdrejta të mbajtësit, ju mund t'i rregulloni skriptet tuaja Bash pas vetes edhe nëse ato mbyllen papritur. Kjo ju jep një sistem skedarësh më të pastër. Ai gjithashtu parandalon paqëndrueshmërinë herën tjetër që të ekzekutoni skriptin dhe, në varësi të qëllimit të skriptit tuaj, madje mund të parandalojë vrimat e sigurisë.