Beliebte Stolperfallen beim Skripten mit Bash

|

Wer sich den Alltag auf einem Linux System etwas vereinfachen oder automatisieren möchte, wird schnell auf das Thema Bash-Skripting stoßen. Ein robustes Bash-Skript zu schreiben, ist jedoch gar nicht so einfach. Denn die Skriptsprache beinhaltet einige Fallen, in die man sowohl als Neuling als auch Erfahrener noch tappen kann.

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

Der rm Aufruf enthält aber immer noch eine Fehlerquelle, denn die Variable ist nicht in Anführungszeichen gesetzt. Ist in der Variable ein Leerzeichen vorhanden, so betreibt Bash sogenanntes Wordsplitting. Durch die Leerzeichen wird der Inhalt der Variable in mehrere Argumente aufgeteilt, anstatt sie dem Befehl als ein Argument zu übergeben. rm wird also nicht versuchen, eine Datei namens "Test Datei" zu löschen, sondern zwei Dateien, die jeweils "Test" und "Datei" heißen.

Das Bequemer geht's nicht Hosting

Agentur-Server
Hosting neu gedacht

> 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.

Ähnliche Artikel:

Webentwicklung

Website-Deploy über CI-Pipelines

Vorteile von CI-Pipelines? Dateien können automatisiert bei einem Commit auf den Server kopiert werden. Eine genau Anleitung dazu erhältst du im Blog.

Rostiges, massives Schloss
mittwald

Welcher Passwort-Manager passt zu mir?

Du kannst dir nicht all deine Passwörter merken? Mit einem Passwort-Manager brauchst du das auch nicht. Lies im Blog, welche Tools wir dir empfehlen.

Webentwicklung

Aus unserer Kubernetes Werkzeugkiste: der Secret Generator

Der Secret Generator – einer unserer eigens entwickelten Kubernetes Operatoren. Damit kannst du Passwort-Erstellung automatisieren. Wie? Lies es im Blog.

Kommentare

me___ am
Es muss " ${PIPESTATUS[0]} " heissen nicht " ${PIPESTATUS[0] "
fehlende "}"
Fabian Niepelt am

Vielen Dank für den Hinweis! Haben wir korrigiert.