So verwenden Sie Linux-Signale in Bash-Skripten
Veröffentlicht: 2022-08-09
Der Linux-Kernel sendet Signale an Prozesse über Ereignisse, auf die sie reagieren müssen. Gut erzogene Skripte verarbeiten Signale elegant und robust und können hinter sich selbst aufräumen, selbst wenn Sie Strg+C drücken. Hier ist wie.
Signale und Prozesse
Signale sind kurze, schnelle, unidirektionale Nachrichten, die an Prozesse wie Skripte, Programme und Daemons gesendet werden. Sie informieren den Prozess über etwas, das passiert ist. Der Benutzer hat möglicherweise Strg + C gedrückt, oder die Anwendung hat versucht, in den Speicher zu schreiben, auf den sie keinen Zugriff hat.
Wenn der Autor des Prozesses erwartet hat, dass ein bestimmtes Signal an ihn gesendet werden könnte, kann er eine Routine in das Programm oder Skript schreiben, um dieses Signal zu verarbeiten. Eine solche Routine wird als Signalhandler bezeichnet . Es fängt oder fängt das Signal ein und führt als Reaktion darauf eine Aktion aus.
Wie wir sehen werden, verwendet Linux viele Signale, aber aus Sicht des Skriptings gibt es nur eine kleine Teilmenge von Signalen, an denen Sie wahrscheinlich interessiert sind Das herunterzufahrende Skript sollte (wo möglich) abgefangen und ein ordnungsgemäßes Herunterfahren durchgeführt werden.
Beispielsweise kann Skripten, die temporäre Dateien erstellen oder Firewall-Ports öffnen, die Möglichkeit gegeben werden, die temporären Dateien zu löschen oder die Ports zu schließen, bevor sie heruntergefahren werden. Wenn das Skript in dem Moment stirbt, in dem es das Signal empfängt, kann Ihr Computer in einem unvorhersehbaren Zustand verbleiben.
So können Sie Signale in Ihren eigenen Skripten handhaben.
Treffen Sie die Signale
Einige Linux-Befehle haben kryptische Namen. Nicht so der Befehl, der Signale abfängt. Es heißt trap
. Wir können trap
auch mit der Option -l
(list) verwenden, um uns die gesamte Liste der von Linux verwendeten Signale anzuzeigen.
Falle -l
Obwohl unsere nummerierte Liste bei 64 endet, gibt es tatsächlich 62 Signale. Die Signale 32 und 33 fehlen. Sie sind nicht in Linux implementiert. Sie wurden durch Funktionen im gcc
-Compiler zur Handhabung von Echtzeit-Threads ersetzt. Alles vom Signal 34, SIGRTMIN
, bis zum Signal 64, SIGRTMAX
, sind Echtzeitsignale.
Sie werden verschiedene Listen auf verschiedenen Unix-ähnlichen Betriebssystemen sehen. Auf OpenIndiana zum Beispiel sind die Signale 32 und 33 vorhanden, zusammen mit einer Reihe zusätzlicher Signale, die die Gesamtzahl auf 73 erhöhen.
Signale können nach Name, Nummer oder Kurzbezeichnung referenziert werden. Ihr abgekürzter Name ist einfach ihr Name, bei dem das führende „SIG“ entfernt wurde.
Signale werden aus vielen verschiedenen Gründen ausgelöst. Wenn Sie sie entziffern können, ist ihr Zweck in ihrem Namen enthalten. Die Auswirkung eines Signals fällt in eine von wenigen Kategorien:
- Beenden: Der Vorgang wird beendet.
- Ignorieren: Das Signal beeinflusst den Prozess nicht. Dies ist ein reines Informationssignal.
- Core: Eine Dump-Core-Datei wird erstellt. Dies geschieht normalerweise, weil der Prozess in irgendeiner Weise überschritten wurde, z. B. eine Speicherverletzung.
- Stopp: Der Prozess wird gestoppt. Das heißt, es wird angehalten , nicht beendet.
- Weiter: Weist einen angehaltenen Prozess an, die Ausführung fortzusetzen.
Dies sind die Signale, denen Sie am häufigsten begegnen werden.
- SIGHUP : Signal 1. Die Verbindung zu einem entfernten Host – beispielsweise einem SSH-Server – wurde unerwartet getrennt oder der Benutzer hat sich abgemeldet. Ein Skript, das dieses Signal empfängt, wird möglicherweise ordnungsgemäß beendet oder versucht, erneut eine Verbindung zum Remote-Host herzustellen.
- SIGINT : Signal 2. Der Benutzer hat die Kombination Strg+C gedrückt, um das Schließen eines Prozesses zu erzwingen, oder der
kill
-Befehl wurde mit Signal 2 verwendet. Technisch gesehen ist dies ein Interrupt-Signal, kein Beendigungssignal, sondern ein unterbrochenes Skript ohne ein Signalhandler wird normalerweise beendet. - SIGQUIT : Signal 3. Der Benutzer hat die Kombination Strg+D gedrückt, um das Beenden eines Prozesses zu erzwingen, oder der
kill
-Befehl wurde mit Signal 3 verwendet. - SIGFPE : Signal 8. Der Prozess hat versucht, eine unzulässige (unmögliche) mathematische Operation durchzuführen, wie z. B. eine Division durch Null.
- SIGKILL : Signal 9. Dies ist das Signaläquivalent einer Guillotine. Sie können es nicht fangen oder ignorieren, und es passiert sofort. Der Vorgang wird sofort beendet.
- SIGTERM : Signal 15. Dies ist die rücksichtsvollere Version von
SIGKILL
.SIGTERM
weist einen Prozess auch an, sich zu beenden, aber es kann abgefangen werden, und der Prozess kann seine Bereinigungsprozesse ausführen, bevor er beendet wird. Dies ermöglicht ein ordnungsgemäßes Herunterfahren. Dies ist das Standardsignal, das vomkill
-Befehl ausgelöst wird.
Signale auf der Kommandozeile
Eine Möglichkeit, ein Signal abzufangen, besteht darin, trap
mit der Nummer oder dem Namen des Signals und einer Antwort zu verwenden, die ausgeführt werden soll, wenn das Signal empfangen wird. Wir können dies in einem Terminalfenster demonstrieren.
Dieser Befehl fängt das SIGINT
-Signal ab. Die Antwort besteht darin, eine Textzeile im Terminalfenster auszugeben. Wir verwenden die Option -e
(Escapes aktivieren) mit echo
, damit wir den Formatbezeichner „ \n
“ verwenden können.
trap 'echo -e "\nStrg+c erkannt."' SIGINT
Unsere Textzeile wird jedes Mal gedruckt, wenn wir die Kombination Strg+C drücken.
Um zu sehen, ob ein Trap auf ein Signal gesetzt ist, verwenden Sie die Option -p
(Trap drucken).
trap -p SIGNAL
Die Verwendung von trap
ohne Optionen bewirkt dasselbe.
Um das Signal in seinen nicht abgefangenen, normalen Zustand zurückzusetzen, verwenden Sie einen Bindestrich „ -
“ und den Namen des abgefangenen Signals.
Falle - SIGINT
trap -p SIGNAL
Keine Ausgabe des Befehls trap -p
zeigt an, dass für dieses Signal kein Trap gesetzt ist.
Signale in Skripten abfangen
Wir können den gleichen allgemeinen Format trap
Befehl in einem Skript verwenden. Dieses Skript fängt drei verschiedene Signale ab, SIGINT
, SIGQUIT
und SIGTERM
.
#!/bin/bash trap "echo I was SIGINT termined; exit" SIGINT trap "echo I was SIGQUIT beendigt; exit" SIGQUIT trap "echo I was SIGTERM beendigt; exit" SIGTERM echo $$ Zähler=0 während wahr tun echo "Schleifennummer:" $((++counter)) schlafen 1 erledigt
Die drei trap
Anweisungen befinden sich oben im Skript. Beachten Sie, dass wir den exit
Befehl in die Antwort auf jedes der Signale eingefügt haben. Das bedeutet, dass das Skript auf das Signal reagiert und dann beendet wird.
Kopieren Sie den Text in Ihren Editor und speichern Sie ihn in einer Datei namens „simple-loop.sh“ und machen Sie ihn mit dem Befehl chmod
ausführbar. Sie müssen dies für alle Skripte in diesem Artikel tun, wenn Sie auf Ihrem eigenen Computer mitmachen möchten. Verwenden Sie einfach den Namen des jeweils passenden Skripts.

chmod +x simple-loop.sh
Der Rest des Skripts ist sehr einfach. Wir müssen die Prozess-ID des Skripts kennen, damit uns das Skript dies wiedergibt. Die Variable $$
enthält die Prozess-ID des Skripts.
Wir erstellen eine Variable namens counter
und setzen sie auf Null.
Die while
-Schleife wird für immer ausgeführt, es sei denn, sie wird zwangsweise beendet. Es erhöht die counter
, gibt sie auf dem Bildschirm aus und schläft für eine Sekunde.
Lassen Sie uns das Skript ausführen und verschiedene Signale an es senden.
./simple-loop.sh
Wenn wir „Strg+C“ drücken, wird unsere Nachricht im Terminalfenster ausgegeben und das Skript beendet.
Lassen Sie uns es erneut ausführen und das SIGQUIT
-Signal mit dem kill
-Befehl senden. Wir müssen das von einem anderen Terminalfenster aus tun. Sie müssen die Prozess-ID verwenden, die von Ihrem eigenen Skript gemeldet wurde.
./simple-loop.sh
kill -SIGQUIT 4575
Wie erwartet meldet das Skript das ankommende Signal und beendet sich dann. Und schließlich, um den Punkt zu beweisen, machen wir es noch einmal mit dem SIGTERM
-Signal.
./simple-loop.sh
kill -SIGTERM 4584
Wir haben verifiziert, dass wir mehrere Signale in einem Skript abfangen und unabhängig voneinander darauf reagieren können. Der Schritt, der all dies von interessant zu nützlich macht, ist das Hinzufügen von Signal-Handlern.
Umgang mit Signalen in Skripten
Wir können die Antwortzeichenfolge durch den Namen einer Funktion in Ihrem Skript ersetzen. Der trap
Befehl ruft dann diese Funktion auf, wenn das Signal erkannt wird.
Kopieren Sie diesen Text in einen Editor und speichern Sie ihn als Datei namens „grace.sh“ und machen Sie ihn mit chmod
ausführbar.
#!/bin/bash trap graceful_shutdown SIGINT SIGQUIT SIGTERM graceful_shutdown() { echo -e "\nEntferne temporäre Datei:" $temp_file rm -rf "$temp_file" Ausfahrt } temp_file=$(mktemp -p /tmp tmp.XXXXXXXXXX) echo "Erstellte temporäre Datei:" $temp_file Zähler=0 während wahr tun echo "Schleifennummer:" $((++counter)) schlafen 1 erledigt
Das Skript setzt einen Trap für drei verschiedene Signale – SIGHUP
, SIGINT
und SIGTERM
– mit einer einzigen trap
Anweisung. Die Antwort ist der Name der Funktion graceful_shutdown()
. Die Funktion wird immer dann aufgerufen, wenn eines der drei eingefangenen Signale empfangen wird.
Das Skript erstellt mithilfe von mktemp
eine temporäre Datei im Verzeichnis „/tmp“. Die Dateinamensvorlage lautet „tmp.XXXXXXXXXX“, daher lautet der Name der Datei „tmp“. gefolgt von zehn zufälligen alphanumerischen Zeichen. Der Name der Datei wird auf dem Bildschirm angezeigt.
Der Rest des Skripts ist derselbe wie im vorherigen while
mit einer counter
und einer Endlosschleife.
./grace.sh
Wenn der Datei ein Signal gesendet wird, das sie schließt, wird die Funktion graceful_shutdown()
aufgerufen. Dadurch wird unsere einzige temporäre Datei gelöscht. In einer realen Situation könnte es jede Bereinigung durchführen, die Ihr Skript erfordert.
Außerdem haben wir alle unsere eingefangenen Signale gebündelt und mit einer einzigen Funktion verarbeitet. Sie können Signale einzeln abfangen und an ihre eigenen dedizierten Handler-Funktionen senden.
Kopieren Sie diesen Text und speichern Sie ihn in einer Datei namens „triple.sh“ und machen Sie ihn mit dem Befehl chmod
ausführbar.
#!/bin/bash trap signint_handler SIGINT trap sigusr1_handler SIGUSR1 trap exit_handler EXIT Funktion signint_handler() { ((++sign_count)) echo -e "\nSIGINT hat $sigint_count Zeit(en) empfangen." if [[ "$signint_count" -eq 3 ]]; dann echo "Beginnt mit der Schließung." loop_flag=1 fi } Funktion sigusr1_handler() { echo "SIGUSR1 hat $((++sigusr1_count)) mal(e) gesendet und empfangen." } Funktion exit_handler() { echo "Exit-Handler: Skript wird beendet..." } echo $$ sigusr1_count=0 signint_count=0 loop_flag=0 while [[ $loop_flag -eq 0 ]]; tun kill -SIGUSR1 $$ schlafen 1 erledigt
Wir definieren drei Traps am Anfang des Skripts.
- Einer fängt
SIGINT
ab und hat einen Handler namenssigint_handler()
. - Die zweite fängt ein Signal namens
SIGUSR1
ab und verwendet einen Handler namenssigusr1_handler()
. - Trap Nummer drei fängt das
EXIT
-Signal ab. Dieses Signal wird vom Skript selbst ausgelöst, wenn es geschlossen wird. Das Festlegen eines Signalhandlers fürEXIT
bedeutet, dass Sie eine Funktion festlegen können, die immer aufgerufen wird, wenn das Skript beendet wird (es sei denn, es wird mit dem SignalSIGKILL
beendet). Unser Handler heißtexit_handler()
.
SIGUSR1
und SIGUSR2
sind Signale, die bereitgestellt werden, damit Sie benutzerdefinierte Signale an Ihre Skripts senden können. Wie Sie sie interpretieren und darauf reagieren, liegt ganz bei Ihnen.
Abgesehen von den Signalhandlern sollte Ihnen der Hauptteil des Skripts vertraut sein. Es gibt die Prozess-ID an das Terminalfenster zurück und erstellt einige Variablen. Die Variable sigusr1_count
zeichnet auf, wie oft SIGUSR1
bearbeitet wurde, und sigint_count
zeichnet auf, wie oft SIGINT
bearbeitet wurde. Die Variable loop_flag
wird auf Null gesetzt.
Die while
-Schleife ist keine Endlosschleife. Die Schleife wird beendet, wenn die Variable loop_flag
auf einen Wert ungleich Null gesetzt wird. Jeder Durchlauf der while
-Schleife verwendet kill
, um das SIGUSR1
-Signal an dieses Skript zu senden, indem es an die Prozess-ID des Skripts gesendet wird. Skripte können Signale an sich selbst senden!
Die Funktion sigusr1_handler()
erhöht die Variable sigusr1_count
und sendet eine Nachricht an das Terminalfenster.
Jedes Mal, wenn das Signal SIGINT
empfangen wird, erhöht die Funktion siguint_handler()
die Variable sigint_count
und gibt ihren Wert an das Terminalfenster zurück.
Wenn die Variable sigint_count
gleich drei ist, wird die Variable loop_flag
auf eins gesetzt und eine Nachricht an das Terminalfenster gesendet, die den Benutzer darüber informiert, dass der Herunterfahrvorgang begonnen hat.
Da loop_flag
nicht mehr gleich Null ist, wird die while
-Schleife beendet und das Skript beendet. Aber diese Aktion löst automatisch das EXIT
-Signal aus und die Funktion exit_handler()
wird aufgerufen.
./triple.sh
Nach dreimaligem Drücken von Strg+C wird das Skript beendet und ruft automatisch die Funktion exit_handler()
auf.
Lesen Sie die Signale
Indem Sie Signale abfangen und in einfachen Handler-Funktionen behandeln, können Sie Ihre Bash-Skripte hinter sich selbst aufräumen, selbst wenn sie unerwartet beendet werden. Das gibt Ihnen ein saubereres Dateisystem. Es verhindert auch Instabilität bei der nächsten Ausführung des Skripts und – je nach Zweck Ihres Skripts – könnte es sogar Sicherheitslücken verhindern.
VERWANDT: So prüfen Sie die Sicherheit Ihres Linux-Systems mit Lynis