7.3 Eine Fallgrube für Signale – trap
Das Kommando trap ist das Gegenstück zu kill. Damit können Sie in Ihren Shellscripts auf Signale reagieren (bzw. sie abfangen), um so den Programmabbruch zu verhindern.
trap 'kommando1; ... ; kommaond_n' Signalnummer
Dem Kommandonamen trap folgt hier ein Befehl oder eine Liste von Befehlen, die ausgeführt werden sollen, wenn das Signal mit »Signalnummer« eintrifft. Die Befehle werden zwischen einfache Single Quotes geschrieben. Trifft beim ausführenden Script ein Signal ein, wird die Ausführung an der aktuellen Position unterbrochen und die angegebenen Befehle der trap-Anweisung werden ausgeführt. Danach wird mit der Ausführung des Scripts an der unterbrochenen Stelle wieder fortgefahren (siehe Abbildung 7.2).
Ein einfaches Beispiel:
# Demonstriert die Funktion trap zum Abfangen von Signalen
# Name: trap1
# Signal SIGINT (2) (Strg+C)
trap 'echo SIGINT erhalten' 2
i=0
while [ $i -lt 5 ]
do
echo "Bitte nicht stören!"
sleep 2
i=`expr $i + 1`
done
Das Script bei der Ausführung:
you@host > ./trap1
Bitte nicht stören!
Bitte nicht stören! (Strg)+(C)
SIGINT erhalten
Bitte nicht stören!
Bitte nicht stören! (Strg)+(C)
SIGINT erhalten
Bitte nicht stören!
you@host >
Mit der trap-Anweisung zu Beginn des Scripts sorgen Sie dafür, dass beim Eintreffen des Signals SIGINT – entweder durch die Tastenkombination (Strg)+(C) oder mit kill –SIGINT PID_von_trap1 ausgelöst – der echo-Befehl ausgeführt wird. Würden Sie hier keine trap-Anweisung verwenden, so würde das Script beim ersten (Strg)+(C) (SIGINT) beendet werden.
Natürlich können Sie in einem Script auch mehrere Signale abfangen:
# Demonstriert die Funktion trap zum Abfangen von Signalen
# Name: trap2
# Signal SIGINT (2) (Strg+C)
trap 'echo SIGINT erhalten' 2
# Signal SIGTERM (15) kill -TERM PID_of_trap2
trap 'echo SIGTERM erhalten' 15
i=0
while [ $i -lt 5 ]
do
echo "Bitte nicht stören! ($$)"
sleep 5
i=`expr $i + 1`
done
Das Script bei der Ausführung:
you@host > ./trap2
Bitte nicht stören! (10175) (Strg)+(C)
SIGINT erhalten
Bitte nicht stören! (10175)
Bitte nicht stören! (10175)
SIGTERM erhalten
Bitte nicht stören! (10175)
Bitte nicht stören! (10175)
you@host >
Bei der Ausführung von »trap2« wurde das Signal SIGTERM hier aus einer anderen Konsole wie folgt an das Script gesendet:
you@host > kill -TERM 10175
Sie müssen allerdings nicht für jedes Signal eine extra trap-Anweisung verwenden, sondern Sie können hinter der Signalnummer weitere Signalnummern – getrennt mit mindestens einem Leerzeichen – anfügen und somit auf mehrere Signale mit denselben Befehlen reagieren.
# Demonstriert die Funktion trap zum Abfangen von Signalen
# Name: trap3
# Signale SIGINT und SIGTERM abfangen
trap 'echo Ein Signal (SIGINT/SIGTERM) erhalten' 2 15
i=0
while [ $i -lt 5 ]
do
echo "Bitte nicht stören! ($$)"
sleep 5
i=`expr $i + 1`
done
In diesem Beispiel können Sie auch erkennen, warum es nicht möglich ist und auch niemals möglich sein darf, dass das Signal SIGKILL (Nr. 9) ignoriert oder als nicht unterbrechbar eingerichtet wird. Stellen Sie sich jetzt noch eine Endlosschleife vor, die ständig Daten in eine Datei schreibt. Würde hierbei das Signal SIGKILL ausgeschaltet, so würden ewig Daten in die Datei geschrieben, bis das System oder der Plattenplatz schlapp macht, und nicht mal mehr der Systemadministrator könnte das Script unterbrechen.
7.3.1 Einen Signalhandler (Funktion) einrichten
In der Praxis werden Sie beim Auftreten eines bestimmten Signals gewöhnlich keine einfache Ausgabe vornehmen. Meistens werden Sie mit Dingen wie dem »Saubermachen« von Datenresten beschäftigt sein – oder aber, Sie richten sich hierzu einen eigenen Signalhandler (Funktion) ein, welcher auf ein bestimmtes Signal reagieren soll:
# Demonstriert die Funktion trap zum Abfangen von Signalen
# Name: trap4
sighandler_INT() {
printf "Habe das Signal SIGINT empfangen\n"
printf "Soll das Script beendet werden (j/n) : "
read
if [[ $REPLY = "j" ]]
then
echo "Bye!"
exit 0;
fi
}
# Signale SIGINT abfangen
trap 'sighandler_INT' 2
i=0
while [ $i -lt 5 ]
do
echo "Bitte nicht stören! ($$)"
sleep 3
i=`expr $i + 1`
done
Das Script bei der Ausführung:
you@host > ./trap4
Bitte nicht stören! (4843)
Bitte nicht stören! (4843) (Strg)+(C)
Habe das Signal SIGINT empfangen
Soll das Script beendet werden (j/n) : n
Bitte nicht stören! (4843)
Bitte nicht stören! (4843)
Bitte nicht stören! (4843)
you@host > ./trap4
Bitte nicht stören! (4854) (Strg)+(C)
Habe das Signal SIGINT empfangen
Soll das Script beendet werden (j/n) : n
Bitte nicht stören! (4854) (Strg)+(C)
Habe das Signal SIGINT empfangen
Soll das Script beendet werden (j/n) : j
Bye!
you@host >
Hinweis Verwenden Sie in der Korn-Shell innerhalb von Funktionen die trap-Anweisung, dann sind die mit ihr eingerichteten Signale nur innerhalb dieser Funktion gültig.
|
Ein eigener Signalhandler (bzw. eine Funktion) wird häufig eingerichtet, um eine Konfigurationsdatei neu einzulesen. Bestimmt haben Sie schon einmal bei einem Dämon- oder Serverprogramm die Konfigurationsdatei Ihren Bedürfnissen angepasst. Damit die aktuellen Änderungen aktiv werden, mussten Sie die Konfigurationsdatei mittels
kill -HUP PID_of_dämon_oder_server
neu einlesen. Wie Sie dies in Ihrem Script erreichen können, haben Sie eben mit dem Script »trap4« in ähnlicher Weise gesehen. Ein einfaches Beispiel auch hierzu:
# Demonstriert die Funktion trap zum Abfangen von Signalen
# Name: trap5
# Signal SIGHUP empfangen
trap 'readconfig' 1
readconfig() {
. aconfigfile
}
a=1
b=2
c=3
# Endlosschleife
while [ 1 ]
do
echo "Werte (PID:$$)"
echo "a=$a"
echo "b=$b"
echo "c=$c"
sleep 5
done
Die Datei aconfigfile sieht wie folgt aus:
# Konfigurationsdatei für trap5
# Name: aconfigfile
# Hinweis: Um hier vorgenommene Änderungen umgehend zu
# aktivieren, müssen Sie lediglich die
# Konfigurationsdatei aconfigfile mittels
# kill -HUP PID_of_trap5
# neu einlesen.
a=3
b=6
c=9
Das Script bei der Ausführung:
###--- tty1 ---###
you@host > ./trap5
Werte (PID:6263)
a=1
b=2
c=3
Werte (PID:6263)
a=1
b=2
c=3
###--- tty2 ---###
you@host > kill -HUP 6263
###--- tty1 ---###
Werte (PID:6263)
a=3
b=6
c=9 (Strg)+(C)
you@host >
7.3.2 Mit Signalen Schleifendurchläufe abbrechen
Genauso einfach können Sie auch Signale verwenden, um Schleifen abzubrechen. Hierzu müssen Sie nur in den Befehlen von trap die Anweisung break eintragen, dann wird bei Auftreten eines gewünschten Signals eine Schleife abgebrochen:
# Demonstriert die Funktion trap zum Abfangen von Signalen
# Name: trap6
# Signale SIGINT und SIGTERM abfangen
trap 'break' 2
i=1
while [ $i -lt 10 ]
do
echo "$i. Schleifendurchlauf"
sleep 1
i=`expr $i + 1`
done
echo "Nach dem $i Schleifendurchlauf abgebrochen"
echo "--- Hinter der Schleife ---"
Das Script bei der Ausführung:
you@host > ./trap6
1. Schleifendurchlauf
2. Schleifendurchlauf
3. Schleifendurchlauf
4. Schleifendurchlauf
5. Schleifendurchlauf (Strg)+(C)
Nach dem 5. Schleifendurchlauf abgebrochen
--- Hinter der Schleife ---
you@host >
7.3.3 Mit Signalen das Script beenden
Bitte beachten Sie, dass Sie mit einem abgefangenen Signal keinen Programmabbruch erreichen. Haben Sie zunächst mit der trap-Anweisung ein Signal abgefangen, müssen Sie sich gegebenenfalls selbst um die Beendigung eines Prozesses kümmern. Diese Methode wird recht häufig eingesetzt, wenn der Anwender ein Signal an den Prozess sendet, dieser aber den Datenmüll vorher noch entsorgen soll. Hierzu setzen Sie den Befehl exit an das Ende der Kommandofolge, die Sie in der trap-Anweisung angegeben haben, beispielsweise:
trap 'rm atempfile.tmp ; exit 1' 2
Hier wird beim Auftreten des Signals SIGINT zunächst die temporäre Datei atempfile.tmp gelöscht, bevor im nächsten Schritt mittels exit das Script beendet wird.
7.3.4 Das Beenden der Shell (oder eines Scripts) abfangen
Wollen Sie, dass beim Verlassen der Shell oder eines Scripts noch eine andere Datei bzw. Script ausgeführt wird, können Sie das Signal 0 (EXIT) abfangen. Dieses Signal wird beim normalen Beenden einer Shell oder eines Scripts oder über den Befehl exit gesendet, mit dem ein Script oder eine Shell vorzeitig beendet wird. Nicht abfangen können Sie hingegen mit dem Signal 0 Abbrüche, die durch kill von außen herbeigeführt wurden. Ein einfaches Beispiel, welches das Ende einer (echten) Shell abfängt:
# Name: dasEnde
# Befehle, die beim Beenden einer Shell ausgeführt werden
cat <<MARKE
********************************************
* Hier könnten noch einige nützliche *
* zur Beendigung der Shell stehen. *
********************************************
MARKE
echo "Alles erledigt – Shell mit ENTER beenden"
read
Zunächst müssen Sie in Ihrer Shell das Signal EXIT abfangen und die Datei »dasEnde« aufrufen. Steht die »Falle« für das Signal EXIT, können Sie die Shell verlassen:
you@host > trap '$SHELL $HOME/dasEnde' 0
you@host > exit
logout
********************************************
* Hier könnten noch einige nützliche *
* zur Beendigung der Shell stehen. *
********************************************
Alles erledigt – Shell mit ENTER beenden
(ENTER)
login :
Damit die Datei »dasEnde« mit wichtigen Hinweisen in Zukunft nach jedem Verlassen einer Shell dauerhaft zur Verfügung steht, sollten Sie die Zeile
trap '$SHELL $HOME/dasEnde' 0
in die Datei .profile eintragen.
Gleiches lässt sich auch in einem Shellscript verwenden:
# Demonstriert die Funktion trap zum Abfangen von Signalen
# Name: trap7
# Signal EXIT abfangen
trap 'exithandler' 0
exithandler() {
echo "Das Script wurde vorzeitig mit exit beendet!"
# Hier noch anfallende Aufräumarbeiten ausführen
}
# Hauptfunktion
echo "In der Hauptfunktion" && exit 1
echo "Wird nicht mehr ausgeführt"
Das Script bei der Ausführung:
you@host > ./trap7
In der Hauptfunktion
Das Script wurde vorzeitig mit exit beendet!
you@host >
Im Gegensatz zum Vorgehen in Abschnitt 7.3.3 müssen Sie hierbei kein zusätzliches exit bei den Kommandos von trap angeben. Hier halten Sie das Script nur beim wirklichen Ende kurz auf.
7.3.5 Signale ignorieren
Wenn Sie in der Liste von Befehlen bei trap keine Angaben vornehmen, also leere Single Quotes verwenden, werden die angegebenen Signale (bzw. Signalnummern) ignoriert. Die Syntax:
trap '' Signalnummer
Somit würden Sie praktisch mit der Angabe von
trap '' 2
das Signal SIGINT beim Auftreten ignorieren. Das völlige Ignorieren von Signalen kann bei extrem kritischen Datenübertragungen sinnvoll sein. So können Sie zum Beispiel verhindern, dass beim Schreiben kritischer Systemdaten der Anwender mit einem unbedachten SIGINT reinpfuscht. Natürlich gilt hier weiterhin, dass die Signale SIGKILL und SIGSTOP nicht ignoriert werden können.
Hinweis Laut POSIX ist das weitere Verhalten von Prozessen undefiniert, wenn die Signale SIGFPE, SIGILL oder SIGSEGV ignoriert werden und diese auch nicht durch einen manuellen Aufruf von kill ausgelöst wurden.
|
7.3.6 Signale zurücksetzen
Wenn Sie die Reaktion von Signalen mittels trap einmal verändert haben, können Sie durch einen erneuten Aufruf von trap den Standardzustand der Signale wiederherstellen. Hierbei reicht lediglich der Aufruf von trap und der (bzw. den) Signalnummer(n), die Sie wieder auf den ursprünglichen Zustand zurücksetzen wollen. Mehrere Signalnummern werden wieder mit mindestens einem Leerzeichen von der vorangegangenen Signalnummer getrennt.
trap Signalnummer
Das Zurücksetzen von Signalen ist sinnvoll, wenn man die Signale nur bei einem bestimmten Codeausschnitt gesondert behandeln will.
# Demonstriert die Funktion trap zum Abfangen von Signalen
# Name: trap8
# Signal SIGINT ignorieren
trap '' 2
i=0
while [ $i -lt 5 ]
do
echo "Hier kein SIGINT möglich ..."
sleep 1
i=`expr $i + 1`
done
# Signal SIGINT wieder zurücksetzen
trap 2
i=0
while [ $i -lt 5 ]
do
echo "SIGINT wieder möglich ..."
sleep 1
i=`expr $i + 1`
done
Das Script bei der Ausführung:
you@host > ./trap8
Hier kein SIGINT möglich ...
Hier kein SIGINT möglich ... (Strg)+(C)
Hier kein SIGINT möglich ... (Strg)+(C)
Hier kein SIGINT möglich ...
Hier kein SIGINT möglich ...
SIGINT wieder möglich ...
SIGINT wieder möglich ... (Strg)+(C)
you@host >
Hinweis Wollen Sie wissen, welche Signale für eine bestimmte Routine mit trap abgefangen werden, können Sie das Kommando trap ohne jegliches Argument verwenden.
|
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.
|