So verwenden Sie Linux-Signale in Bash-Skripten

Veröffentlicht: 2022-08-09
Linux-Laptop mit einer Bash-Eingabeaufforderung
fatmawati achmad zaenuri/Shutterstock.com

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.

So verwalten Sie Prozesse vom Linux-Terminal aus: 10 Befehle, die Sie kennen müssen
VERWANDTE So verwalten Sie Prozesse vom Linux-Terminal aus: 10 Befehle, die Sie kennen müssen

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 

Auflisten der Signale in Ubuntu mit trap -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.

Auflisten der Signale in OpenIndiana mit trap -l

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 vom kill -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 

Abfangen von Strg+C in der Befehlszeile

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 

Prüfen, ob auf ein Signal eine Falle gesetzt ist

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 

Entfernen einer Falle von einem 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 

Mit chmod ein Skript ausführbar machen

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 

Ein Skript, das es identifiziert, wurde mit Strg+C beendet

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 

Ein Skript, das ihn identifiziert, wurde mit SIGQUIT beendet

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 

Ein Skript, das es identifiziert, wurde mit SIGTERM beendet

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 

Ein Skript, das ein ordnungsgemäßes Herunterfahren durch das Löschen einer temporären Datei durchführt

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 namens sigint_handler() .
  • Die zweite fängt ein Signal namens SIGUSR1 ab und verwendet einen Handler namens sigusr1_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ür EXIT bedeutet, dass Sie eine Funktion festlegen können, die immer aufgerufen wird, wenn das Skript beendet wird (es sei denn, es wird mit dem Signal SIGKILL beendet). Unser Handler heißt exit_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 

Ein Skript, das SIGUSR1 verwendet, zum Schließen drei Strg+C-Kombinationen benötigt und beim Herunterfahren das EXIT-Signal abfängt

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