Beliebte Stolperfallen beim Skripten mit Bash
Bash ist eine sogenannte Shell, durch die ein Benutzer per Kommandozeile mit seinem System interagieren kann und ist auf Linux-Systemen weit verbreitet. Interaktionen lassen sich durch Bash-Skripte automatisieren, sodass häufig wiederkehrende Befehlsabläufe nicht immer wieder manuell eingegeben werden müssen. Doch ein robustes Bash-Skript zu schreiben, ist gar nicht so leicht. Einige der beliebtesten Stolperfallen möchten wir daher in diesem Beitrag vorstellen, damit auch euer nächstes Bash-Skript robuster als je zuvor wird.
Überprüfung des Rückgabewertes beim Exportieren einer Variablen
export my_var=$(some_function)
if [[ $? != 0 ]] ; then
echo "some_function failed"
exit 1
fi
In diesem Beispiel-Skript wird der Variablen $my_var die Ausgabe der Funktion some_function zugewiesen. Sie wird im gleichen Zuge exportiert, sodass sie den darauf folgenden Kommandos als Umgebungsvariable zur Verfügung steht. Daraufhin erfolgt eine Überprüfung des Rückgabewertes $?, der einen Fehler aussagt, wenn er ungleich 0 ist. In diesem Fall meint der Ersteller des Skripts, den Rückgabewert von some_function zu überprüfen. Dies ist jedoch ein Irrtum. Hier wird nämlich nur der Rückgabewert des export Befehls überprüft. Sollte some_function fehlschlagen, wird es durch diesen Code nicht abgefangen. Die Lösung:
my_var=$(some_function)
if [[ $? != 0 ]] ; then
echo "some_function failed"
exit 1
fi
export my_var
Werden die Zuweisung und der export in zwei Befehle aufgeteilt, so stimmt die Überprüfung.
Hoppla, alle Dateien wurden gelöscht!
Ein beliebter Fehler bei der Verwendung des rm Befehls (Befehl zum Löschen von Dateien) ist das Benutzen von Variablen, ohne dass diese auf Inhalt überprüft werden.
rm -rf /home/fniepelt/$delete_this
Falls $delete_this keinen Inhalt enthält, hat dies katastrophale Konsequenzen. Denn in dem Fall wird fälschlicherweise das gesamte Benutzerverzeichnis des Benutzers fniepelt gelöscht. Es gibt jedoch mehrere Möglichkeiten, um diesen Fehler zu vermeiden.
rm -rf /home/fniepelt/${delete_this:?Variable nicht gesetzt!}
Mit der ${delete_this:?Fehlermeldung} Notation bricht das Skript an diesem Punkt automatisch ab, falls die Variable nicht gesetzt sein sollte.
Alternativ bietet Bash auch die Möglichkeit, dieses Verhalten automatisch für jede Variable einzuschalten.
set -o nounset
rm -rf /home/fniepelt/${delete_this}
Als dritte Möglichkeit lässt sich bei jeder Variablen auch ein Standardwert hinterlegen, der verwendet wird, falls die Variable nicht gesetzt oder leer ist.
rm /home/fniepelt/${delete_this:-löschmich.txt}
Spaß mit Anführungszeichen
> delete_this="Test Datei"
> rm ${delete_this}
rm: das Entfernen von 'Test' ist nicht möglich: No such file or directory
rm: das Entfernen von 'Datei' ist nicht möglich: No such file or directory
Werden doppelte Anführungszeichen gesetzt, so wird das Argument korrekt an rm übergeben.
> delete_this="Test Datei"
> rm -v "${delete_this}"
'Test Datei' wurde entfernt
Doch wie wird eine Datei angegeben, die buchstäblich ${delete_this} heißt? Hier zeigt sich der Unterschied zwischen doppelten und einfachen Anführungszeichen. Einfache Anführungszeichen werden verwendet, wenn Bash den beinhalteten Text nicht weiter interpretieren soll. So werden zum Beispiel keine Variablen aufgelöst und auch keine Subshells erzeugt.
> rm -v '${delete_this}'
'${delete_this}' wurde entfernt
Rückgabewerte von Pipes
Eines der nützlichsten Features von Bash sind Pipes. Mit Pipes lässt sich die Standardausgabe eines Programms mit der Standardeingabe eines anderen verknüpfen.
grep example data.txt | sort
if [[ $? != 0 ]] ; then
echo "grep fehlgeschlagen"
exit 1
fi
Dieses Skript sucht in der Datei data.txt nach Zeilen, die den Text „example“ enthalten und sendet diese an den sort Befehl, der die Zeilen dann alphabetisch sortiert anzeigt. Die Überprüfung des Rückgabewertes ist hier jedoch fehlerhaft. Nach dem Ausführen einer Pipe enthält die Statusvariable $? nur den Rückgabewert des Befehls, der zuletzt ausgeführt wurde, in diesem Fall also sort. Soll die komplette Pipe überprüft werden, so muss auf das Array $PIPESTATUS zurückgegriffen werden.
grep example data.txt | sort
if [[ ${PIPESTATUS[@]} != "0 0" ]] ; then
echo "Fehler in der Pipe"
exit 1
fi
$PIPESTATUS enthält die Rückgabewerte aller Befehle der zuletzt ausgeführten Pipe. Mit [@] wird auf alle Werte gleichzeitig zugegriffen. Besteht nur an einem bestimmten Rückgabewert Interesse, lässt sich dies durch das Spezifizieren eines Indexes abbilden.
grep example data.txt | sort
if [[ ${PIPESTATUS[0]} != "0" ]] ; then
echo "grep fehlgeschlagen"
exit 1
elif [[ ${PIPESTATUS[1]} != "0" ]] ; then
echo "sort fehlgeschlagen"
exit 1
fi
Dadurch lässt sich genau herausfinden, bei welchem Befehl ein Problem auftrat. Dabei muss beachtet werden, dass Bash Arrays bei 0 anfangen und nicht bei 1.
Dank Bash lassen sich Automatisierungen „mal eben schnell“ umsetzen
Teilweise hält das Skripten mit Bash aber auch einige Überraschungen oder unvorhergesehene Konsequenzen bereit. Wir hoffen, dass dieser Beitrag euch bei euren zukünftigten Skripten helfen kann, deren Qualität und Robustheit weiter zu verbessern!
Weitergehende Hilfe liefert übrigens das Tool ShellCheck, welches sich in die meisten bekannten IDEs und Editoren integrieren lässt, um euch beim Schreiben auf potenzielle Fehler und Verbesserungsmöglichkeiten hinzuweisen.
Habt ihr auch Erfahrungen mit Fallgruben in Bash oder habt eine Frage zum Skripten? Dann lasst uns gerne einen Kommentar da.
Kommentare
fehlende "}"
Vielen Dank für den Hinweis! Haben wir korrigiert.