Suchen

Strings und Arithmetik, Variablen und Globs Bash-Bedingungen im Detail

| Autor / Redakteur: Mirco Lang / Stephan Augsten

„Was wäre, wenn?“ Das ist die grundlegende Frage für jegliche Entwicklung. Was wäre, wenn der Nutzer das Kästchen anklickt? Was wäre, wenn a einen Wert zwischen 4 und 10 hat? Was, wenn Datei XY existiert? Was, wenn String 1 in String 2 vorkommt? Und was wäre, wenn intuitiv verständlich wäre, wo die Unterschiede zwischen [[]], [], (()) und test liegen?

Firma zum Thema

Mit Bedingungen lässt sich in Bash-Skripten viel stuern, doch die Syntax ist nicht selbsterklärend.
Mit Bedingungen lässt sich in Bash-Skripten viel stuern, doch die Syntax ist nicht selbsterklärend.
(Bild: StartupStockPhotos / Pixabay )

Bedingungen hauchen Skripten erst Leben ein – ohne könnten sie nur einfachste Aufgaben erledigen und Schleifen würden ewig laufen. In der Bash sehen Bedingungen nur leider für Einsteiger nicht sonderlich reizvoll aus.

Eine If-Abfrage versteht sicherlich auch ein blutiger Anfänger auf Anhieb, die If-Then-Else-Syntax ist ziemlich selbsterklärend (mehr zu Schleifen und Abfragen in Bash). Aber die Klammer-Konstrukte in den Abfragen? Eckige oder runde Klammer, Anführungszeichen oder nicht – oder vielleicht gar keine Klammer?

Die erste Bedingung, die Ihnen über den Weg läuft ist vermutlich etwas in der Art wie „[ $i -eq 5 ]“ etwa in einer If-Abfrage:

var=Hallo
if [ $var = Hallo ]; then
   echo "var ist Hallo"
fi

Hierzu sei noch einmal gesagt, dass das einfache Gleichheitszeichen nur zum Vergleichen von Strings dient, der Abgleich von Zahlwerten und anderen erfolgt mit ==. Und in dieser simplen Standard-Abfrage lauern noch drei weitere interessante Erkenntnisse: Zum einen ist das „if“ hier überhaupt nicht nötig, zum anderen sind es auch die Klammern nicht – die Leerzeichen hinter/vor den Klammern, sobald sie genutzt werden, hingegen schon. Sie könnten nämlich auch schreiben:

var=Hallo
test $var = Hallo && echo "var ist Hallo"

Die eckigen Klammern sind nichts weiter als eine andere Schreibweise für das Tool „test“. Sie machen komplexere Bedingungen schlicht und ergreifend übersichtlicher und einfacher zu schreiben. Und da der Befehl nach „&&“ nur ausgeführt wird, wenn der vorige Befehl Erfolg vermeldet, startet „echo“ hier im Beispiel eben nur dann, wenn „var“ tatsächlich „Hallo“ ist.

Doppelte eckige Klammern hingegen sind eine Bash-eigene Umsetzung, die einige Dinge besser beziehungsweise anders handhabt. Schaut man sich die Unterschiede zwischen beiden einmal anhand konkreter Beispiele an, hat man einen schönen roten Faden, um sich überhaupt Bash-Bedingungen zu nähern.

Nach dem Vergleich kommen dann noch die doppelten runden Klammern zu ihrem Recht. Sie entsprechen weitgehend dem Kommando „let“, das arithmetische Operationen evaluiert.

Bedingungen mit [ und [[

Test, [ und [[ bieten drei unterschiedliche Vergleichsmodi: Strings, Zahlen und Dateien. Strings werden wie im Beispiel oben schlicht mit "=" und "!=" (ungleich bzw. nicht gleich) verglichen, also:

a=foobar
[ $a = $a ] && echo Wahr
[ foobar1 != foobar2 ] && echo Wahr

Schon die erste Bedingung funktioniert aber nicht mehr, wenn der Text in „$a“ Leerzeichen enthält, also zum Beispiel der String „Hallo Welt“. Dann müssen Anführungszeichen her – oder doppelte Klammern:

a="Hallo Welt"
[ "$a" = "$a" ] && echo Wahr
[[ $a = $a ]] && echo Wahr

Die test-Variante verhält sich wie gewohnt und splittet nicht quotierte Variablen, die Bash-Variante verzichtet darauf. Dadurch wird eine typische Fehlerquelle entschärft. Allerdings sollten Sie auch hier lieber …

[[ $a = "$a" ]] && echo Wahr

… verwenden, also die RHS (Right Hand Side) in Anführungszeichen setzen. Denn [[ beherrscht Shell Globbing und begreift alles rechts vom Operator, was nicht in Anführungszeichen steht, als Glob. Das heißt: Dank dieses Features lässt sich zum Beispiel ganz fix prüfen, ob der Inhalt einer Variablen einem der Strings "foobar_1", "foobar_2" oder "foobar_3" übereinstimmt:

a=foobar_2
[[ $a == foobar_[123] ]] && echo Wahr

Das ist eine tolle Funktion, bringt jedoch Probleme, wenn der Inhalt der Variablen ein String samt eckiger Klammern, etwa "[foobar]", wäre. Ohne die Anführungszeichen entstünden mit aufgelöster Variable nämlich folgendes Statement:

[[ [foobar] == [foobar] ]] && echo Funktioniert nicht

Sieht zwar richtig aus, aber die RHS wird eben als Glob gesehen, sprich „f oder o oder b ...“, und die LHS als String. Besser ist also generell:

a=foobar_2
b=foobar
[[ $a == "$b_"[123] ]] && echo Wahr

Ein mächtiges Feature macht [[ endgültig überlegen: Über den Operator „=~“ lässt sich mit einem regulären Ausdruck vergleichen, hier etwa:

a=12345_foobar
[[ $a =~ ^[[:digit:]]{1,5}_foobar ]] && echo Wahr

Der Ausdruck rechts vom Gleichheitszeichen wird als POSIX-Basis-RegEx interpretiert, hier müsste „$a“ also mit 1 bis 5 Ziffern beginnen und mit „_foobar“ aufhören.

Die nächsten Unterschiede zwischen [ und [[ betreffen nun nicht mehr (nur) Strings, sondern Kombinationen und Dateien.

File Expansion und Kombinationen

Neben den String-Operatoren bieten beide Tools allerlei Möglichkeiten zum Testen auf Dateiebene, also beispielsweise, ob eine Datei existiert (-e), zwei Dateien identisch sind (-ef), eine Datei existiert und ein Verzeichnis ist (-d) und so weiter. Eine komplette Übersicht liefert die man-Page [https://man.cx/test].

Spannend dabei ist dabei der Punkt File Expansion, also die automatische Erweiterung von Dateinamen, die sich in folgendem Beispiel zeigt:

mkdir /test
touch test/datei_1.txt test/datei_2.txt
[ -e test/*.txt ] && echo Falsch
[[ -e test/*.txt ]] && echo Falsch

Beide Varianten enden in einem False – was ist passiert? [ erweitert per Sternchen auf alle Dateinamen im Verzeichnis, bekommt in diesem Beispiel also gleich zwei Argumente geliefert. Da aber nur eines erwartet wird, ist die Aussage unwahr. Wahr wäre sie nur dann, wenn genau eine Datei mit „.txt“-Endung im Verzeichnis läge. [[ versagt aus einem ganz anderen Grund: Der Dateiname wird eben nicht erweitert – folglich wird hier nach einer Datei namens „*.txt“ gesucht.

Sollten Sie jedoch wissen wollen, ob mindestens eine .txt-Datei existiert, könnten Sie etwa per grep und ls danach suchen und dann prüfen, ob der String nicht leer (-n) ist:

[[ -n $(ls | egrep ".*\.txt") ]] && echo Wahr

Der große letzte Unterschied betrifft nun Kombinationen. Die [[-Variante setzt hier schlicht auf die etwas üblichere Notation von UND und ODER:

a=Hallo
b=Welt
[ "$a" = Hallo -a "$b" = Welt -o "$b" = Wald ] && echo Wahr
[[ "$a" = Hallo && "$b" = Welt || "$b" = Wald ]] && echo Wahr

Hier muss also „$a“ den Text „Hallo“ beinhalten UND „$b“ muss „Welt“ ODER „Wald“ sein. Das test-Kommando nutzt also „-a“ und „-o“, [[ hingegen „&&“ und „||“. Das ist allerdings nur eine Stilfrage ohne weiteren Einfluss.

Arithmetische Operationen

Auch bei der Evaluation arithmetischer Operationen ist [[ dem test-Kommando überlegen. In beiden Fällen wird einfach per Kürzel verglichen: "-eq" für "gleich", "-lt" für "kleiner als", "-ge" für "größer oder gleich" und so weiter. Allerdings lässt sich in doppelten eckigen Klammern auch rechnen!

[ 3 -lt 5 ] && echo Wahr
[[ 2**4 -eq 16 ]] && echo Wahr

Die spannendere Variante ist hier aber eindeutig der Spezialist, also die doppelte runde Klammer. Zum einen gibt es hier wieder die hübschere Syntax mit den bekannteren Operatoren:

(( 3 <= 5 )) && echo Wahr
(( 2**4 == 16 )) && echo Wahr

Neben so einem verständlicheren Kleiner-Gleich-Operator kann die Klammerkonstruktion aber auch noch etwas ganz anderes, nämlich Variablen im C-Style manipulieren; gern genutzt, um Zählvariablen zu inkrementieren:

for i in (( i=1; i<=10; i++ )); do echo Nummer $i; donei=1; while (( i <= 10 )); do echo Nummer $i && (( i++ )); done

Beide Schleifen produzieren dasselbe Ergebnis, in der while-Variante wird die Verwendung abseits einer Bedingung etwas deutlicher.

Zum Mitnehmen

Grundsätzlich sind Bedingungen relativ simpel und gerade heraus umgesetzt, es sind nur ein paar Aspekte, die man berücksichtigen sollte: Im Allgemeinen können Sie sich getrost die doppelten eckigen Klammern angewöhnen und auf das test-Kommando ausweichen, wenn Sie die mal automatische Dateinamenerweiterung benötigen

Bei [[ wiederum sollten Sie immer dran denken, dass die RHS ohne Anführungszeichen als Glob gehandelt wird. Und bei arithmetischen Evaluationen oder Operationen gewöhnen Sie sich am besten gleich die doppelten runden Klammern an: Dann müssen Sie nie von [[ nach (( umdenken, erkennen arithmetische Vergleiche sofort an der Klammerart und können auch noch einfacher damit rechnen.

(ID:46308673)

Über den Autor

 Mirco Lang

Mirco Lang

Freier Journalist & BSIler