Fallensteller

Mit trap kann der Programmierer verhindern, dass der Nutzer sein Skript abbricht. Doch der Befehl kann noch einiges mehr.

Lesezeit: 4 Min.
In Pocket speichern
vorlesen Druckansicht Kommentare lesen
Von
  • Thomas Freudenberg

Viele Skripte erledigen ihre Arbeit gut, solange keine unvorhergesehenen Ereignisse eintreten. Es kommt in der Praxis jedoch nicht selten vor, dass Dateien und Verzeichnisse unauffindbar sind oder der Nutzer nicht die erforderliche Zugriffsberechtigung besitzt. Auch könnte der Nutzer oder ein anderes Programm das Skript vorzeitig abbrechen. Der Programmierer sollte dafür sorgen, dass sein Skript auf solche Ereignisse angemessen reagiert.

Außergewöhnliche Umstände meldet der Kernel einem Programm durch Signale. Drückt der Nutzer zum Beispiel Strg-C, sendet der Kernel das Signal INT (Interrupt), das normalerweise das gerade laufende Programm beendet. Allerdings kann man mit trap "echo ’Bitte nicht stören!’" INT die Wirkung beeinflussen: Drückt der Anwender jetzt Strg-C, gibt das Skript nur eine Meldung aus und läuft weiter. Mit trap "" INT ignoriert es das Signal sogar vollständig. Nach dem Kommando trap - INT verhält sich das Skript wieder normal: Strg-C bricht das Programm ab.

Unix kennt eine ganze Reihe von Signalen mit unterschiedlicher Bedeutung - in aktuellen Versionen 30 oder mehr. Der Befehl kill -l zeigt die komplette Liste an. Meist genügt es jedoch, nur die gängigsten zu berücksichtigen: HUP, INT, QUIT, ABRT, KILL, ALRM und TERM - auf Posix-kompatiblen Systemen kann man in der Regel statt der Namen auch die Signalnummern 1, 2, 3, 6, 9, 14 und 15 verwenden.

Sie alle beenden normalerweise den Empfänger. Das Signal KILL besitzt eine Sonderstellung: Es lässt sich weder umleiten noch deaktivieren. Zwar akzeptiert die Shell den Befehl trap "echo ’Ich sterbe’" KILL ohne Klage. Das KILL-Signal beendet jedoch den Prozess, ohne dass die Meldung erscheint.

Viele Skripte legen Informationen in temporären Dateien und Verzeichnissen ab. Soll das Skript sie bei einem Abbruch automatisch entfernen, kann der Programmierer einen Befehl wie trap "rm -rf /tmp/mytemp; exit" HUP INT QUIT ABRT ALRM TERM verwenden. Es empfiehlt sich allerdings, dem trap-Kommando statt mehrerer Befehle den Namen einer Funktion zu übergeben, die alle Aufräumarbeiten erledigt:

cleanup() {
# Aufräumen
rm -rf /tmp/mytemp
# Skript beenden
exit
}

In nahezu allen Fällen soll das Skript auch bei normaler Beendigung aufräumen. Dazu kann man cleanup explizit aufrufen. Eleganter geht es mit trap: Ist das Skript zu Ende oder führt es den Befehl exit aus, generiert die Shell ein Pseudosignal mit der Nummer 0. Installiert man mit trap cleanup 0 einen „Exit Handler“, räumt sie automatisch auf. Das funktioniert auch in interaktiven Shells: trap clear 0 etwa löscht beim Ausloggen den Bildschirm. Manche Shells, etwa die Gnu-Shell bash, akzeptieren statt der Nummer 0 auch den Namen EXIT.

Soll die Aufräumfunktion die Ursache des Abbruchs untersuchen, kann der Programmierer ihr den Namen oder die Nummer des Signals als Argument übergeben:

for sig in 0 1 2 3 6 14 15; do
trap "cleanup $sig" $sig
done

Falls das Skript regulär oder mit einem exit endete, sollte cleanup zuerst den Fehlercode sichern, damit es ihn später ans aufrufende Programm weiterreichen kann:

cleanup() {
EXIT_CODE=$?
...
# Exit Handler deaktivieren
trap - 0
# Skript beenden
exit $EXIT_CODE
}

Man kann cleanup noch dahingehend erweitern, dass es eine passende Fehlermeldung ausgibt. Dazu muss der Programmierer eigene Fehlercodes definieren und in der Funktion mit dem Exit-Code vergleichen (siehe Listing 1). Tritt bei der Ausführung des Skripts ein Fehler auf, kann er es einfach mit einem Befehl wie exit $ERR_INVALID_PATH beenden.

Bash-Nutzer können trap außerdem als Debugging-Hilfe verwenden. Nach trap DEBUG führt die Shell vor jeder Anweisung den angegebenen Befehl aus. Das ist zum Beispiel nützlich, wenn man sich die Inhalte von Variablen anzeigen lassen möchte: trap ’echo i = $i’ DEBUG etwa zeigt den Inhalt der Variablen i. Muss der Trap-Handler mehrere Werte anzeigen, kann man eine Funktion verwenden und ihr die Variablennamen übergeben (siehe Listing 2).

Thomas Freudenberg
ist Vorstand der 4commerce technologies AG in Hamburg.

Mehr Infos

Listing 1

ERR_INVALID_PATH=82
ERR_INVALID_FILE=83
...
cleanup() {
EXIT_CODE=$?
...
case $EXIT_CODE in
0)
;;
$ERR_INVALID_PATH)
echo "Ungültiger Pfad";;
$ERR_INVALID_FILE)
echo "Ungültiger Dateiname";;
...
*)
echo "Unbekannter Fehler";;
esac
...
exit $EXIT_CODE
}

Mehr Infos

Listing 2

__debug_var() {
for var in "$@"; do
eval echo "__debug_var: $var = [\$$var]"
done
}
# Variablen i, n und m beobachten
trap "__debug_var i n m" DEBUG


(mr)