Jak używać sygnałów Linuksa w skryptach Bash
Opublikowany: 2022-08-09
Jądro Linuksa wysyła do procesów sygnały o zdarzeniach, na które muszą zareagować. Dobrze zachowane skrypty obsługują sygnały elegancko i niezawodnie i mogą posprzątać po sobie, nawet jeśli naciśniesz Ctrl + C. Oto jak.
Sygnały i procesy
Sygnały to krótkie, szybkie, jednokierunkowe komunikaty wysyłane do procesów, takich jak skrypty, programy i demony. Poinformują proces o czymś, co się wydarzyło. Użytkownik mógł nacisnąć Ctrl+C lub aplikacja mogła próbować zapisać w pamięci, do której nie ma dostępu.
Jeśli autor procesu przewidział, że może zostać do niego wysłany pewien sygnał, może napisać do programu lub skryptu procedurę obsługi tego sygnału. Taka procedura nazywana jest obsługą sygnału . Łapie lub przechwytuje sygnał i w odpowiedzi na niego wykonuje jakąś akcję.
Linux używa wielu sygnałów, jak zobaczymy, ale z punktu widzenia skryptów istnieje tylko mały podzbiór sygnałów, które mogą cię zainteresować. W szczególności, w nietrywialnych skryptach, sygnały, które mówią skrypt do zamknięcia powinien być uwięziony (tam, gdzie to możliwe) i wykonać łagodne zamknięcie.
Na przykład skrypty tworzące pliki tymczasowe lub otwierające porty zapory mogą mieć możliwość usunięcia plików tymczasowych lub zamknięcia portów przed ich zamknięciem. Jeśli skrypt po prostu umrze w momencie odebrania sygnału, komputer może zostać pozostawiony w nieprzewidywalnym stanie.
Oto jak możesz obsługiwać sygnały we własnych skryptach.
Poznaj sygnały
Niektóre polecenia systemu Linux mają zagadkowe nazwy. Nie jest to polecenie, które łapie sygnały. Nazywa się trap
. Możemy również użyć trap
z opcją -l
(lista), aby pokazać nam całą listę sygnałów, z których korzysta Linux.
pułapka -l
Chociaż nasza ponumerowana lista kończy się na 64, w rzeczywistości są 62 sygnały. Brakuje sygnałów 32 i 33. Nie są zaimplementowane w Linuksie. Zostały one zastąpione funkcjonalnością kompilatora gcc
do obsługi wątków czasu rzeczywistego. Wszystko od sygnału 34, SIGRTMIN
, do sygnału 64, SIGRTMAX
, są sygnałami czasu rzeczywistego.
Zobaczysz różne listy w różnych uniksopodobnych systemach operacyjnych. Na przykład na OpenIndiana obecne są sygnały 32 i 33, wraz z kilkoma dodatkowymi sygnałami, które zwiększają całkowitą liczbę do 73.
Do sygnałów można odwoływać się według nazwy, numeru lub skróconej nazwy. Ich skrócona nazwa to po prostu ich nazwa z usuniętym wiodącym „SIG”.
Sygnały pojawiają się z wielu różnych powodów. Jeśli możesz je rozszyfrować, ich przeznaczenie jest zawarte w ich nazwie. Wpływ sygnału należy do jednej z kilku kategorii:
- Zakończ: Proces zostaje zakończony.
- Ignoruj: sygnał nie wpływa na proces. To jest tylko sygnał informacyjny.
- Rdzeń: tworzony jest plik zrzutu rdzenia. Zwykle dzieje się tak, ponieważ proces został w jakiś sposób przekroczony, na przykład naruszenie pamięci.
- Stop: Proces zostaje zatrzymany. Oznacza to, że jest wstrzymany , a nie zakończony.
- Kontynuuj: Informuje zatrzymany proces o kontynuowaniu wykonywania.
Są to sygnały, z którymi będziesz się spotykać najczęściej.
- SIGHUP : Signal 1. Połączenie ze zdalnym hostem — takim jak serwer SSH — zostało nieoczekiwanie przerwane lub użytkownik się wylogował. Skrypt odbierający ten sygnał może zakończyć się poprawnie lub może podjąć próbę ponownego połączenia się ze zdalnym hostem.
- SIGINT : Sygnał 2. Użytkownik nacisnął kombinację Ctrl+C, aby wymusić zamknięcie procesu, lub użyto polecenia
kill
z sygnałem 2. Technicznie jest to sygnał przerwania, a nie sygnał zakończenia, ale przerwany skrypt bez procedura obsługi sygnału zwykle kończy działanie. - SIGQUIT : Sygnał 3. Użytkownik nacisnął kombinację Ctrl+D, aby wymusić zakończenie procesu lub użyto polecenia
kill
z sygnałem 3. - SIGFPE : Signal 8. Proces próbował wykonać niedozwoloną (niemożliwą) operację matematyczną, taką jak dzielenie przez zero.
- SIGKILL : Sygnał 9. Jest to odpowiednik sygnału gilotyny. Nie możesz go złapać ani zignorować, a dzieje się to natychmiast. Proces zostaje natychmiast zakończony.
- SIGTERM : Sygnał 15. To jest bardziej rozważna wersja
SIGKILL
.SIGTERM
nakazuje również procesowi zakończyć, ale może zostać uwięziony, a proces może uruchomić swoje procesy czyszczenia przed zamknięciem. Pozwala to na pełne wdzięku zamknięcie. Jest to domyślny sygnał podnoszony przez poleceniekill
.
Sygnały w linii poleceń
Jednym ze sposobów schwytania sygnału jest użycie trap
z numerem lub nazwą sygnału oraz odpowiedzią, która ma nastąpić, gdy sygnał zostanie odebrany. Możemy to zademonstrować w oknie terminala.
To polecenie przechwytuje sygnał SIGINT
. Odpowiedzią jest wydrukowanie wiersza tekstu w oknie terminala. Używamy opcji -e
(włącz ucieczki) z echo
, więc możemy użyć specyfikatora formatu „ \n
”.
trap 'echo -e "\nCtrl+c Wykryto."' SIGINT
Nasz wiersz tekstu jest drukowany za każdym razem, gdy naciśniemy kombinację Ctrl+C.
Aby sprawdzić, czy pułapka jest ustawiona na sygnał, użyj opcji -p
(drukuj pułapkę).
pułapka -p SIGINT
Używanie trap
bez opcji robi to samo.
Aby zresetować sygnał do jego normalnego stanu, należy użyć myślnika „ -
” i nazwy uwięzionego sygnału.
pułapka - SIGINT
pułapka -p SIGINT
Brak danych wyjściowych komendy trap -p
wskazuje, że dla tego sygnału nie ustawiono pułapki.
Zatrzymywanie sygnałów w skryptach
Możemy użyć tego samego ogólnego polecenia formatu trap
w skrypcie. Ten skrypt przechwytuje trzy różne sygnały: SIGINT
, SIGQUIT
i SIGTERM
.
#!/kosz/bash pułapka "echo Zostałem przerwany przez SIGINT; wyjdź" SIGINT pułapka "echo Zostałem przerwany przez SIGQUIT; wyjdź" SIGQUIT pułapka "echo Zostałem zakończony SIGTERM; wyjście" SIGTERM echo $$ licznik=0 podczas gdy prawda robić echo "Numer pętli:" $((++counter)) spać 1 Gotowe
Trzy instrukcje trap
znajdują się na początku skryptu. Zauważ, że umieściliśmy polecenie exit
w odpowiedzi na każdy z sygnałów. Oznacza to, że skrypt reaguje na sygnał, a następnie kończy działanie.
Skopiuj tekst do edytora i zapisz go w pliku o nazwie „simple-loop.sh” i spraw, aby był wykonywalny za pomocą polecenia chmod
. Musisz to zrobić ze wszystkimi skryptami w tym artykule, jeśli chcesz kontynuować na własnym komputerze. Po prostu użyj nazwy odpowiedniego skryptu w każdym przypadku.

chmod +x simple-loop.sh
Reszta skryptu jest bardzo prosta. Musimy znać identyfikator procesu skryptu, więc skrypt nam to powie. Zmienna $$
zawiera identyfikator procesu skryptu.
Tworzymy zmienną o nazwie counter
i ustawiamy ją na zero.
Pętla while
będzie działać w nieskończoność, chyba że zostanie zatrzymana na siłę. Zwiększa zmienną counter
, wyświetla ją na ekranie i śpi na sekundę.
Uruchommy skrypt i wyślijmy do niego różne sygnały.
./prosta-pętla.sh
Kiedy naciśniemy „Ctrl + C”, nasza wiadomość zostanie wydrukowana w oknie terminala, a skrypt zostanie zakończony.
Uruchommy go jeszcze raz i SIGQUIT
sygnał SIGQUIT za pomocą polecenia kill
. Musimy to zrobić z innego okna terminala. Musisz użyć identyfikatora procesu, który został zgłoszony przez Twój własny skrypt.
./prosta-pętla.sh
zabić -SIGQUIT 4575
Zgodnie z oczekiwaniami skrypt zgłasza nadejście sygnału, a następnie kończy działanie. I na koniec, aby to udowodnić, zrobimy to ponownie z sygnałem SIGTERM
.
./prosta-pętla.sh
zabić -SIGTERM 4584
Zweryfikowaliśmy, że potrafimy schwytać wiele sygnałów w skrypcie i reagować na każdy z nich niezależnie. Krokiem, który promuje to wszystko od interesującego do użytecznego, jest dodanie obsługi sygnałów.
Obsługa sygnałów w skryptach
Możemy zastąpić ciąg odpowiedzi nazwą funkcji w twoim skrypcie. Polecenie trap
następnie wywołuje tę funkcję po wykryciu sygnału.
Skopiuj ten tekst do edytora i zapisz go jako plik o nazwie „grace.sh” i spraw, aby był wykonywalny za pomocą chmod
.
#!/kosz/bash trap graceful_shutdown SIGINT SIGQUIT SIGTERM Graceful_shutdown() { echo -e "\nUsuwanie pliku tymczasowego:" $temp_file rm -rf "$plik_temp" Wyjście } temp_file=$(mktemp -p /tmp tmp.XXXXXXXXXX) echo "Utworzono plik tymczasowy:" $temp_file licznik=0 podczas gdy prawda robić echo "Numer pętli:" $((++counter)) spać 1 Gotowe
Skrypt ustawia pułapkę dla trzech różnych sygnałów — SIGHUP
, SIGINT
i SIGTERM
— za pomocą pojedynczej instrukcji trap
. Odpowiedzią jest nazwa funkcji graceful_shutdown()
. Funkcja jest wywoływana za każdym razem, gdy odbierany jest jeden z trzech przechwyconych sygnałów.
Skrypt tworzy plik tymczasowy w katalogu „/tmp”, używając mktemp
. Szablon nazwy pliku to „tmp.XXXXXXXXXX”, więc nazwą pliku będzie „tmp”. po którym następuje dziesięć losowych znaków alfanumerycznych. Nazwa pliku pojawia się na ekranie.
Reszta skryptu jest taka sama jak poprzednia, ze zmienną counter
i nieskończoną pętlą while
.
./grace.sh
Gdy plik otrzymuje sygnał, który powoduje jego zamknięcie, wywoływana jest funkcja graceful_shutdown()
. Spowoduje to usunięcie naszego pojedynczego pliku tymczasowego. W rzeczywistej sytuacji może wykonać wszystko, czego wymaga twój skrypt.
Ponadto połączyliśmy wszystkie nasze uwięzione sygnały i obsłużyliśmy je za pomocą jednej funkcji. Możesz przechwytywać sygnały pojedynczo i wysyłać je do ich własnych, dedykowanych funkcji obsługi.
Skopiuj ten tekst i zapisz go w pliku o nazwie „triple.sh” i spraw, aby był wykonywalny za pomocą polecenia chmod
.
#!/kosz/bash pułapka sgint_handler SIGINT pułapka sigusr1_handler SIGUSR1 pułapka exit_handler EXIT funkcja sigint_handler() { ((++liczba_sygnatur)) echo -e "\nSIGINT otrzymał $signint_count time(s)." if [[ "$sigint_count" -eq 3 ]]; następnie echo "Uruchamianie zamykania." flaga_pętli=1 fi } funkcja sigusr1_handler() { echo "SIGUSR1 wysłany i odebrany $((++sigusr1_count)) czas(y)." } funkcja exit_handler() { echo "Obsługa wyjścia: Skrypt jest zamykany..." } echo $$ sigusr1_count=0 liczba_sigint=0 flaga_pętli=0 while [[ $loop_flag -eq 0 ]]; robić zabić -SIGUSR1 $$ spać 1 Gotowe
Na górze skryptu definiujemy trzy pułapki.
- Jeden pułapkuje
SIGINT
i ma procedurę obsługi o nazwiesigint_handler()
. - Drugi przechwytuje sygnał o nazwie
SIGUSR1
i używa procedury obsługi o nazwiesigusr1_handler()
. - Pułapka numer trzy łapie sygnał
EXIT
. Ten sygnał jest generowany przez sam skrypt, gdy się zamyka. Ustawienie obsługi sygnału dlaEXIT
oznacza, że możesz ustawić funkcję, która będzie zawsze wywoływana po zakończeniu skryptu (chyba że zostanie zabita sygnałemSIGKILL
). Nasz program obsługi nazywa sięexit_handler()
.
SIGUSR1
i SIGUSR2
to sygnały, które umożliwiają wysyłanie własnych sygnałów do skryptów. To, jak je zinterpretujesz i zareagujesz, zależy wyłącznie od Ciebie.
Pomijając na razie obsługę sygnałów, treść skryptu powinna być ci znajoma. Echa identyfikatora procesu do okna terminala i tworzy kilka zmiennych. Zmienna sigusr1_count
rejestruje, ile razy SIGUSR1
był obsługiwany, a sigint_count
rejestruje, ile razy SIGINT
był obsługiwany. Zmienna loop_flag
jest ustawiona na zero.
Pętla while
nie jest pętlą nieskończoną. Zatrzyma pętlę, jeśli zmienna loop_flag
zostanie ustawiona na dowolną niezerową wartość. Każdy obrót pętli while
używa kill
do wysłania sygnału SIGUSR1
do tego skryptu, wysyłając go do identyfikatora procesu skryptu. Skrypty mogą wysyłać sobie sygnały!
Funkcja sigusr1_handler()
zwiększa wartość zmiennej sigusr1_count
i wysyła komunikat do okna terminala.
Za każdym razem, gdy odbierany jest sygnał SIGINT
, funkcja siguint_handler()
zwiększa wartość zmiennej sigint_count
i wyświetla jej wartość w oknie terminala.
Jeśli zmienna sigint_count
jest równa trzy, zmienna loop_flag
jest ustawiana na jeden, a do okna terminala wysyłany jest komunikat informujący użytkownika o rozpoczęciu procesu zamykania.
Ponieważ loop_flag
nie jest już równe zero, pętla while
kończy działanie i skrypt zostaje zakończony. Ale ta akcja automatycznie wywołuje sygnał EXIT
i wywoływana jest funkcja exit_handler()
.
./potrójny.sh
Po trzech naciśnięciach Ctrl+C skrypt kończy działanie i automatycznie wywołuje funkcję exit_handler()
.
Przeczytaj sygnały
Zatrzymując sygnały i radząc sobie z nimi w prostych funkcjach obsługi, możesz sprawić, że twoje skrypty Bash uporządkowają się za sobą, nawet jeśli zostaną nieoczekiwanie zakończone. To daje czystszy system plików. Zapobiega również niestabilności przy następnym uruchomieniu skryptu i — w zależności od celu skryptu — może nawet zapobiegać lukom w zabezpieczeniach.
POWIĄZANE: Jak kontrolować bezpieczeństwo systemu Linux za pomocą Lynis