Fehlerbehandlung ist nötig…
Dieser Code verursacht Übelkeit und kein Entwickler von Welt würde so etwas tun:
Fehler„behandlung“ durch „Ignorieren und Weitermachen“. Exception
oder nicht? Egal. The show goes on. Igitt!
… auch in Shellskripten.
Dieser Code in einem Shellskript verursacht bei mir genau dieselbe Übelkeit:
Aus demselben Grund. Ob es das Kommando some_stuff
überhaupt gibt
oder ob das Ding fehlschlägt: The show goes on.
set -e FTW
Dagegen hilft, ganz oben in sein Shellskript einzubauen:
Damit schlägt das ganze Skript fehl, sobald das erste einzelne
Kommando fehlschlägt. (Egal ob in ``
oder einfach so.)
So weit, so gut.
Fehler und Pipes: Die versteckte Falle
Heute finde ich in einem Shellskript etwas in dieser Art:
Das ist leider auch daneben. Warum?
Eine nicht sehr bekannte Regel der Pipes+Filters-Architektur der Shell ist:
Ob die Pipeline fehlschlägt, entscheidet allein der letzte Filter.
Der Entscheider ist also sed
. Der ist aber völlig zufrieden damit,
keinen Input zu bekommen und nichts zu tun.
Ob also aws ssm get-parameter
funktioniert oder nicht? Egal. The show goes on.
(Zum Ausprobieren kann man /bin/false
nehmen statt aws ...
.)
set -o pipefail FTW
Dagegen kann man sich wehren, indem man oben in seinem Skript schreibt:
Das set -o pipefail
schaltet in Pipelines das Konsensprinzip ein:
Eine Pipeline scheitert, sobald irgendwo in der Pipeline etwas schief
geht.
Das ist das, was man will (fast immer[1]).
Wähle Deine shell weise!
Leider beherrscht nicht jede Discounter-sh
dieses set -o pipefail
,
wohl aber die bash
.
Wenn man oben in der ersten Zeile des Shellskripts nicht #!/bin/sh
,
sondern #!/bin/bash
sagt und das funktioniert, hat man gewonnen.
Das funktioniert natürlich nicht, wenn es eine /bin/bash
nicht gibt.
Manchmal muss man mit einer Discounter-sh
Vorlieb nehmen.
Wenn man dann erst set -e
und danach set -o pipefail
gemacht
hat und set -o pipefail
wird nicht unterstützt, wirkt zumindest das
set -e
und das ganze Skript scheitert an dieser Stelle.
Und dann?
Dann könnte man als Notausgang versuchen, die |
gar nicht zu nutzen.
Das funktioniert insofern, dass Fehler von aws ...
zum Skriptabbruch
führen. Trotzdem möchte man das nicht.
Aus offensichtlichen und weniger offensichtlichen Gründen. Ein
weniger offensichtlicher: Das Öffnen von temporären Dateien an
vorhersagbaren Stellen mit allgemeinem Schreibrecht öffnet
Sicherheitslücken. Wenn auf dem System auch jemand Unangenehmes einen
sh
-Prompt hat, wird der Notausgang so möglicherweise zum
Noteingang…
Und Fehlermeldungen?
Wenn Fehler zu einem sauberen Abbruch führen, ist das schon mal gut.
Die Situation bleibt trotzdem unangenehm: Denn man weiß nicht, was eigentlich das Problem ist.
Als Notnagel kann hier set -x
dienen, womit alles geloggt wird, was
die Shell tut.
Aber wer treibt schon den Aufwand, nicht nur eine ordentliche Fehlerbehandlung, sondern auch eine ordentliche Fehlermeldung vorzusehen? Wenn man das wirklich tut, werden aus einer Zeile schnell fünf:
Noch besser: Mach’s ganz ohne Shell.
Geht das nicht einfacher?
Ja! …wenn man denn bereit ist, die Shell zu verlassen.
Meine Meinung: Ein Shellskript soll eine Handvoll Zeilen benötigen. Mehr nicht. Wird ein Skript länger, lasse ich die Shell Shell sein. Dann greife zu einer anderen Skriptsprache, gerne Python oder Ruby.
Das Beispiel sieht mit sauberer Fehlerbehandlung in Python 3.6 so aus:
Das ist nach bloßem from subprocess import run, PIPE
. Mit eigener
run
Funktion wird der Aufruf noch kürzer und knackiger: Eine Zeile
reicht!
Aber diesmal mit sauberer Fehlerbehandlung. Und eine informative Fehlermeldung fällt gleich mit aus der Tüte:
Nebenbei bringt dieser Ansatz weitere Annehmlichkeiten. RegExp, JSON/YAML, HTTP-Client, unvorhersagbare Temp-Dateien, Datenbankzugriff, eigene wiederverwendbare Module: Luxus, an dem man sich gerne und schnell gewöhnt.
Und Pipes+Filters?
Was eine bash
kann, kann jede Skriptsprache schon lange! Die
bewährte Pipes+Filters-Architektur steht weiter zur Verfügung.
Aber Details dazu würden hier den Rahmen sprengen.
Wen sie interessieren: Bei einem innoQ-intern Workshop habe ich solche Details vorgeführt. Die damaligen Workshopunterlagen (mit Code auf Ruby-Basis) stehen öffentlich zur Verfügung.