Bash – Bedingte Ausführung mit if und die while-Schleife

Juli 4, 2012 um 8:12 pm | Veröffentlicht in Bash, GNU/Linux, Programmieren, Ubuntuusers | 8 Kommentare

Wie jede vernünftige Programmiersprache hat auch die Bash eine Möglichkeit Code nur dann abzuarbeiten wenn bestimmte Bedingungen eintreffen. Für alle, die mit Programmierung nicht viel zu tun haben: if wird in den meisten Programmiersprachen dazu verwendet das Programm bestimmte Dinge machen zu lassen, abhängig vom Zustand. Grundsätzlich gibt es für die Auswertung eines Audrucks in wahr und falsch das Kommando test. Allerdings ist if normalerweise ein klein wenig übersichtlicher zu verwenden. Ein Beispiel in C:

if( is_closed(WienerWald) ) {
    gehe_zu(McDonalds);
}
else {
    gehe_zu(WienerWald);
}

Dieses Codestück versucht zu ermitteln ob ein bestimmtes Restaurant geschlossen ist. Wenn es geschlossen ist, wird ein anderes Restaurant besucht. Wenn es nicht geschlossen ist, dann wird das fragliche Restaurant besucht. Die Funktion is_closed() liefert uns entweder wahr oder falsch zurück. Dies sind die einzigen Werte die das if selbst auswerten kann. Auf dieser Grundlage wird dann entschieden welcher Code ausgeführt wird. Wenn der Ausdruck wahr zurückliefert, dann wird der Code gleich nach dem if ausgeführt. Wenn der Ausdruck falsch zurückliefert, dann wird der Code nach dem else ausgeführt. Ist der Ausdruck falsch und es gibt jedoch kein else, wird das ganze if einfach übersprungen. So funktioniert das if in allen (mir bekannten) Programmiersprachen. Aus welchen Datenbeständen das Programm diese Information abfragt ist hier uninteressant. Es soll nur die bedingte Ausführung gezeigt werden.

In Bash ist die Syntax ähnlich:

if [ is_closed $WienerWald ]
then
    gehe_zu $McDonalds
else
    gehe_zu $WienerWald
fi

Wichtig dabei zu beachten ist, dass um die eckigen Klammern Leerzeichen stehen müssen, sonst wird ein Fehler ausgegeben und das if versagt. Ich weiß leider selbst nicht, warum die Bash hier so pickig ist. Bei den Klammern handelt es sich um eine Kurzform des Kommandos test, welches für die eigentliche Auswertung des Ausdrucks zuständig ist.

Vergleich zwischen Zahlen

Zwischen den Klammern kann grundsätzlich alles stehen was irgendwie zu wahr oder falsch abgeleitet werden kann. So können auch Zahlen oder Zeichenketten verglichen werden:

if [ $i -lt 10 ]
then
    echo "0$i"
else
    echo "$i"
fi

Dieses if fragt ab ob die Variable $i kleiner (lt = lesser than) ist als der Wert 10. Ist dies der Fall wird der Zahl eine führende Null vorangestellt, ist dies nicht der Fall, dann nicht. Das kann nützlich sein, wenn man bei formatierten Aufzählungen die Formatierung beibehalten will:

08 - ...             8 - ...
09 - ...    statt    9 - ...
10 - ...             10 - ...

Außer lt gibt es noch folgende Befehle zum Vergleichen von zwei Zahlenwerten:

  • eq = equal            = Gleichheit
  • ne = not equal        = Ungleichheit
  • le = lesser or equal  = kleiner oder gleich
  • ge = greater or equal = größer oder gleich
  • gt = greater than     = größer als

Vergleich zwischen Zeichenketten

Auch Zeichenketten, sogenannte strings, können verglichen werden. Hier gibt es mehrere Möglichkeiten:

if [ $str = "Auto" ]

vergleicht ob die Zeichenkette, welche in der Variable $str gespeichert ist, der Zeichenkette „Auto“ entspricht. Wenn dem so ist, dann ist der Ausdruck wahr.

Jetzt wirds ein bisschen kompliziert:

if [ $str != "Auto" ]

vergleicht ebenfalls die Variable $str und die Zeichenkette „Auto„. Dieser Ausdruck ist dann wahr, wenn die beiden Zeichenketten nicht übereinstimmen. Das Rufzeichen agiert hier als Anzeige dafür, dass der gewünschte Vergleich nicht erfolgreich sein soll, damit die Bedingung erfüllt ist.

Als dritte Möglichkeit eine Zeichenkette in einem if zu verwenden kommt die Abfrage ob in der Zeichenkette überhaupt etwas steht:

if [ $str ]

fragt ab ob irgendein Zeichen (egal welches oder wieviele) in der Variable gespeichert sind. Ist mindestens ein Zeichen vorhanden, dann ist der Ausdruck wahr. Ist die Zeichenkette leer (= „“), dann ist der Ausdruck falsch. Zum selben Ergebnis kommt dieser Ausdruck:

if [ -n $str ]

Der Befehl -n prüft ob die Länge der Zeichenkette in der angegebenen Variable nicht Null (non zero) ist. Hingegen

if [ -z $str ]

prüft genau das Gegenteil: -z fragt ob die Länge Null (zero) ist.

UND, ODER und NICHT

Wie jede brauchbare Programmiersprache bietet die Bash bei if auch die Möglichkeit mehrere Ausdrücke hintereinander auszuwerten, ohne jedesmal ein neues if zu benötigen. Der Code

if [ $i -lt 10 ]
then
    if [ $j -lt 10 ]
    then
        ...
    fi
fi

lässt sich kürzer so schreiben:

if [ $i -lt 10 -a $j -lt 10 ]
then
    ...
fi

Das -a (UND) sorgt dafür, dass der Gesamtausdruck nur dann wahr ist, wenn beide Seiten (also links vom -a und rechts davon) jeweils wahr sind. In den meisten Programmiersprachen ist es so, dass zuerst die linke und dann die rechte Seite ausgewertet werden. Sollte die linke Seite bereits falsch sein, wird normalerweise die rechte Seite nicht mehr ausgewertet. Ist im allgemeinen auch nicht notwendig, da der gesamte Ausdruck dann sowieso falsch ist, wenn eine Seite falsch ist. Wahrer als falsch kann es nicht mehr werden. Die Bash hingegen wertet beide Seiten aus. Egal ob der Gesamtausdruck bereits falsch ist oder nicht.

Ähnlich funktioniert das ODER (-o). Hierbei soll geschaut werden ob eine der beiden Seiten wahr ist. Wenn zumindest eine wahr ist, ist der Gesamtausdruck wahr. Auch hier sollte normalerweise der rechte Ausdruck nicht mehr angesehen werden, wenn der Linke bereits wahr ergeben hat. Die Bash machts trotzdem:

if [ $i -lt 10 -o $j -lt 10 ]

Zu guter Letzt im Reich der sogenannten logischen Operatoren gibt es noch das NICHT. Dieses wird als Rufzeichen dargestellt (wie auch schon bei den Vergleichen der Zeichenketten). Seine Aufgabe ist es ein wahr in ein falsch umzukehren und vice versa:

if [ ! $i -lt 10]

Dieser Ausdruck ist dann wahr, wenn $i gerade nicht kleiner ist als 10. Selbstverständlich kann dieses Verhalten auch mit einem anderen Befehl erreicht werden. Im konkreten Fall wäre ein

if [ $i -ge 10 ]

gleichwertig. Das Rufzeichen hat trotzdem seine Existenzberechtigung, weil es manche Ausdrücke wesentlich übersichtlicher werden lässt, da es weniger zu schreiben gibt.

Arbeiten mit Dateien und Ordnern

Es ist mit if in der Bash auch möglich direkt mit Dateien zu interagieren. Ein einfaches

if [ -e datei ]

prüft ob eine Datei mit Namen „datei“ existiert. Dabei wird sich immer auch das aktuelle Verzeichnis bezogen. Man kann natürlich auch einen kompletten Pfad angeben, dann ist es egal in welchen Ordner man sich befindet:

if [ -e /Pfad/zur/datei ]

Bisher kann „datei“ jedoch sowohl Ordner als auch reguläre Datei sein. Um zwischen diesen Möglichkeiten zu unterscheiden gibt es eigene Befehle:

if [ -f datei ]

prüft ob eine reguläre Datei mit Namen „datei“ im aktuellen Verzeichnis liegt. Ist das der Fall, ist das Ergebnis wahr. Es kann natürlich auch ein Ordner mit dem Namen „datei“ im aktuellen Verzeichnis liegen. Dann liefert dieser Ausdruck falsch, da ein Ordner mit diesem Namen zwar existiert, aber ein Ordner keine reguläre Datei ist. Hingegen mit

if [ -d ordner ]

wird konkret geprüft ob ein Ordner mit Namen „ordner“ an dieser Stelle existiert. Hier gilt dasselbe wie bei Dateien: existiert ein Ordner mit angegebenem Namen, ist der Ausdruck wahr. Existiert kein Ordner mit diesem Namen (egal ob eine Datei mit diesem Namen existiert oder nicht) ist der Ausdruck falsch.

if [ -x executable ]

prüft ob die angegebene Datei ausführbar ist. Das kann ein Programm, ein ausführbares Script oder ein Ordner sein.

Soviel zu den wichtigsten Vergleichen bei der Verwendung von if. Eine vollständige Auflistung gibt es auf der GNU-Infopage zu Bash.

while

Zum Abschluss noch ein Hinweis: das if lässt sich mit drei kleinen Änderungen in eine Schleife verwandeln. Eine Schleife ist ein Gebilde dessen Code mehrmals ausgeführt wird bis eine bestimmte Bedingung eintritt:

while [ $i -lt 10 ]
do
    echo $i
    ((i++))
done

Diese Schleife gibt die Zahlen von n (ein Wert der vorher festgelegt wurde) bis 9 aus. Die '((' und '))' lassen uns ungehindert arithmetische Ausdrücke ausführen. i++ bedeutet, dass der Wert, der in $i gespeichert ist um 1 erhöht wird.

  • Das Wort if muss also durch ein while ersetzt werden
  • then muss durch do ersetzt werden
  • fi muss durch done ersetzt werden

Als Ausdruck in den Klammern ist alles möglich das auch im if möglich ist.

Wichtig: eine while-Schleife verfügt nicht über ein else.

=-=-=-=-=
Powered by Blogilo

8 Kommentare »

RSS feed for comments on this post. TrackBack URI

  1. Hi,

    leider sind in deinem Artikel einige Dinge falsch, bzw. es gibt einige Fallstricke:

    – Wenn schon bash, dann verwendet man am besten [[ statt [ und (( für numerische Vergleiche. Damit fallen einige Fallstricke weg, weil das ein Syntax-Element der Bash ist, und nicht ein externes Tool. So kann man zum Beispiel ( und ) direkt nutzen, und muss sie nicht escapen und \( sowie \) nutzen.
    [ ist eigentlich veraltet und wird nur noch bei älteren Shells gebraucht

    – „“ zeigen in Bash keine Strings an, sondern sind dazu da, um Variablen nicht zu splitten. Du solltest *alle* deine Variablen in „“ einschliessen! Vorallem bei Dateinamen mit Leerzeichen stolpert man da gerne mal. Aber auch dein Beispiel „if [ $str ]“ funktioniert gar nicht, wenn $str leer ist! (Okay, unter Bash 4 inzwischen offensichtlich schon). Ausserdem kann man || und && für oder/und verwenden, und sonstige kleine Dinge.

    – Die Bash ist mit den Leerzeichen so pickig weil [ ein Programm ist (du kannst es auch mit „test“ aufrufen, dann ohne ]). Du könntest genauso „if grep foo datei; then“ , etc. machen. If prüft einfach nur den Rückgabewert eines Programms, und [ ist halt ein Programm, das verschiedene Tests machen kann. Andersherum kann man für ein Kommando z.B. auch [ -e „$foo“ ] && rm „$foo“ schreiben, also ohne if.

    – Auch wenn dein erstes Beispiel Pseudocode war, bash kennt keine Funktionsaufrufe mit Klammern (diese sind für Subshells da). Bei Bash würdest du einfach is_closed „$WienerWald“ schreiben.

    – Das Ausrufezeichen wird in einer interaktiver Shell als history expansion verstanden, wird [ statt [[ benutzt, muss \! geschrieben werden. (Gut, manchmal geht es zufällig, weil die history expansion grad nicht zutrifft).

    Flo

    • Danke für deine zahlreichen Korrekturen/Ergänzungen. Habe ein paar Sachen ausgebessert. Da ich mich selbst eher noch im Lernprozess befinde sind mir manche Sachen nicht so klar. Ich habe mich daher hauptsächlich am Bash-Manual des GNU-Projekts orientiert.
      Was das ! angeht, so ist mir schon klar, dass es für History Expansion eingesetzt wird. Im Fall, dass es am Anfang eines if verwendet wird, sollte es jedoch als NOT interpretiert werden. Dementsprechend muss es nicht escaped werden.

  2. Sehr gut! Danke! Allerdings heißt es „less than“ und „greater than“ -> http://en.wikipedia.org/wiki/Less-than_sign

    • Ah, Englisch🙂
      Ausgebessert.

  3. „Wichtig dabei zu beachten ist, dass um die eckigen Klammern Leerzeichen stehen müssen, sonst wird ein Fehler ausgegeben und das if versagt. Ich weiß leider selbst nicht, warum die Bash hier so pickig ist.“

    Die Leerzeichen sind wichtig, da es sich bei den Elementen nach if um Parameter handelt, die an weitere Programme übergeben werden. Das fängt schon mit dem [ an, welches normalerweise /usr/bin/[ aufruft.

    • Danke für diese Erklärung. Mir war nicht klar, dass die Klammer ein eigenes Programm ist. Laut Manpage ist es eine Kurzform von test.

  4. Hey, Danke für den Artikel.🙂
    Ein paar kleine Anmerkungen habe ich noch dazu. Die eckige Klammer [ ist ein Befehl und ruft in Kombination mit ihrem Bruder der ] den Befehl „test“ auf. Ein „man test“ gibt einen Hinweis darauf. Eigentlich ist das aber auch nur die halbe Wahrheit. Auf meinem System ist [ ein bash-builtin also ein Kommando der Bash selbst.
    „man bash“ gibt bei built in commands:
    test expr
    [ expr ]
    Return a status of 0 or 1 depending on the evaluation of the conditional Expression expr. Each operator and operand must be a separate argument. Expressions are
    composed of the primaries described above under CONDITIONAL EXPRESSIONS. test does not accept any options, nor does it accept and ignore an argument of — as signi‐
    fying the end of options.

    Heißt dass if an sich vom [..] (also test getrennt ist.
    man bash
    if list; then list; [ elif list; then list; ] … [ else list; ] fi
    The if list is executed. If its exit status is zero, the then list is executed. Otherwise, each elif list is executed in turn, and if its exit status is zero, the
    corresponding then list is executed and the command completes. Otherwise, the else list is executed, if present. The exit status is the exit status of the last command executed, or zero if no condition tested true.

    Ähnliches gilt im übrigen für die while Schleife. Das fehlt Dir btw das Hochzählen von ‚i‘ .

    Grüße
    Ralf

    • Danke für deine Ausführliche Erklärung.
      Das Hochzählen vom $i … ich war wohl doch schon etwas müde am Schluss …
      Ist korrigiert.


Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

Bloggen auf WordPress.com.
Entries und Kommentare feeds.

%d Bloggern gefällt das: