Teil 4: Kontrollstrukturen

Aus LinuxUser 04/2001

Teil 4: Kontrollstrukturen

Ihre Papiere bitte…

In den ersten drei Teilen haben Sie die Grundlagen der Bash-Programmierung und die Stringverarbeitung kennengelernt. Das genügt aber nicht, um wirklich leistungsfähige Programme zu schreiben – Kontrollstrukturen tun Not.

Unter Kontrollstrukturen versteht man allgemein Sprachkonstrukte – sagen wir einfach einmal Befehle –, mit denen sich die Ausführung von Programmteilen steuern lässt. Die wohl bekannteste Kontrollstruktur ist if, die Prüfung einer Bedingung. Sie hat in der Bash die Form:

if Bedingung1; thenWenn Bedingung 1 wahr
 elif Bedingung2; thenWenn Bedingung 2 wahr
 elif Bedingung3; thenWenn Bedingung 3 wahr
 elseWenn Bedingung 1 bis 3 falsch
 fi

Die Konstruktion beginnt stets mit dem Schlüsselwort if und endet mit fi, dem umgedrehten “if”. Das Konzept des if-Konstrukts ist modular: Was nicht gebraucht wird, kann entfallen. Wenn Sie also nur sagen wollen, dass die Variablen A und B das gleiche enthalten, können Sie schreiben:

if [ $A = $B ]; then
   echo "A und B sind gleich."
 fi

Wenn Sie im Falle eines Unterschieds dies ebenfalls vermelden wollen, kommt zusätzlich else ins Spiel:

if [ $A = $B ]; then
   echo "A und B sind gleich."
 else
   echo "A und B sind ungleich."
 fi

Im letzten Beispiel hatten wir zwei Anweisungsblöcke, den if-Block und den else-Block, mit je einer echo-Anweisung. Diese Blöcke können auch mehrere Befehle enthalten:

if [ $A = $B ]; then
   echo "A und B sind gleich."
   echo "Wer hätte das gedacht?"
 else
   echo "A und B sind ungleich."
   echo "Schade eigentlich."
 fi

Die beiden Blöcke reichen vom Schlüsselwort then beziehungsweise else bis zum nächsten Schlüsselwort des if-Konstrukts. Dabei sind auch Verschachtelungen möglich:

if [ $A = $B ]; then
   echo "A und B sind gleich."
   if [ $A = "0" ]; then
     echo "A und B sind null."
   fi
 else
   echo "A und B sind ungleich."
 fi

In diesem Fall wird das innere if-Konstrukt als eine einzige Anweisung betrachtet und könnte wiederum weitere if-Konstrukte enthalten.

Bedingung

if kann nur zwei Zustände unterscheiden: Wahr oder falsch, wobei 0 als wahr und alles andere als falsch gewertet wird. Die Bedingung wird dabei als Programmaufruf aufgefasst:

if echo "Hallo Welt"; then
   echo "OK"
 fi

Die Bedingung im letzten Beispiel ist echo "Hallo Welt" – und obwohl wir keinerlei Vergleich haben, scheint die Bedingung wahr zu sein, denn es wird anschließend OK ausgegeben. Das Ergebnis des echo-Aufrufs muss 0 gewesen sein. Das können Sie mit der Spezial-Variablen $? überprüfen, die wir in Teil 2 (Ausgabe 01/2001) angerissen haben:

echo "Hallo Welt"
 Hallo Weltecho $?
 0

$? liefert Ihnen den Rückgabewert (Exit-Status), den jedes Programm beim Beenden an das Betriebssystem zurückgeben muss. Unter den Programmierern hat sich eingebürgert, bei normalem Programmablauf stellvertretend 0 zurückzugeben, im Fehlerfall dann einen anderen Wert. Es gibt allerdings auch einzelne Programme, die nur negative Werte für aufgetretene Fehler liefern. Informationen, welcher Wert was bedeutet, findet man manchmal in der Dokumentation oder Manual-Page.

Anwender benötigen den Rückgabewert normalerweise nicht, wohl aber Skripte. In der Praxis kommt man nicht immer mit dem Sprachumfang der Bash aus, man bedient sich externer Programme wie cp, cat oder grep, um einzelne Aufgaben zu lösen. Mit dem Exit-Status kann das Bash-Skript sehr leicht entscheiden, ob bei einem der Kommandos ein Fehler aufgetreten ist oder nicht – und das, ohne die Bildschirmausgabe auswerten oder den Anwender fragen zu müssen.

Als Beispiel verwenden wir das Filter-Programm grep. Damit kann man Dateien nach dem Vorkommen von Wörtern und anderen Zeichenfolgen durchsuchen. Normal gibt grep das Ergebnis direkt auf dem Bildschirm aus, mit der Option -q im folgenden Listing unterdrücken wir das jedoch. Wir lassen die Datei /etc/passwd nach dem nicht existenten Benutzer tux durchsuchen:

if grep -q tux /etc/passwd; then
   echo "Hallo Tux."
 else
   echo "Tux kenne ich nicht."
 fi

Das Ergebnis ist “Tux kenne ich nicht.” Mit dem als root aufzurufenden Befehl useradd tux können Sie nun den Benutzer tux anlegen – keine Sorge, es kann sich niemand als Tux einloggen. Unser Beispiel liefert nun “Hallo Tux.” Mit userdel tux löschen Sie Tux später wieder.

Wir haben ausgenutzt, dass grep im Erfolgsfall, wenn es die Zeichenkette “tux” finden konnte, 0 zurückgibt, während der Rückgabewert bei Misserfolg 1 ist.

Mehrzeilige Bedingungen

Die Länge der Bedingung ist nicht beschränkt, sie kann mehrere Programmaufrufe umfassen. Bewertet wird dabei stets der Rückgabewert des letzten Kommandos. Zur besseren Lesbarkeit benutzt man für mehrzeilige Bedingungen eine etwas andere Schreibweise des if-Konstrukts:

01 if
 02   echo "Suche:"
 03   echo "  tux"
 04   echo "in"
 05   echo "/etc/passwd."
 06   grep -q tux /etc/passwd;
 07 then
 08   echo "Hallo Tux."
 09 else
 10   echo "Tux kenne ich nicht."
 11 fi

Wichtig ist das Semikolon hinter der letzten Anweisung im Bedingungs-Teil, ohne das das Schlüsselwort then nicht erkannt würde. Der Einsatz mehrzeiliger Bedingungs-Blöcke ist jedoch die Ausnahme und nur selten sinnvoll.

Test

Das im Bedingungs-Teil wohl am häufigsten benutzte Programm ist test. Hierbei handelt es sich um ein Hilfsmittel, mit dem umfangreiche Bedingungen aufgebaut werden können. Das Ergebnis von test ist wahr oder falsch.

if test $A = $B; then
   echo "A und B sind gleich."
 fi

Wie Sie vielleicht merken, haben wir Ihnen test gleich im ersten Beispiel untergeschoben, und zwar in der Kurzschreibweise als [ ]. Tatsächlich sind die eckigen Klammern ein Synonym (Alias) von test, das in der Bash definiert ist. In Tabelle 1 finden Sie eine Liste aller Parameter, die Sie im Zusammenhang mit test verwenden können.

Tabelle 1: Die Optionen von test

Eigenschaften von Dateien
-e Datei wahr, wenn Datei existiert
-r Datei wahr, wenn Datei existiert und lesbar ist
-w Datei wahr, wenn Datei existiert und beschreibbar ist
-x Datei wahr, wenn Datei existiert und ausführbar ist
-s Datei wahr, wenn Datei existiert und länger als null Bytes ist
Datei1 -nt Datei2 wahr, wenn Datei1 neuer ist als Datei2
Datei1 -ot Datei2 wahr, wenn Datei1 älter ist als Datei2
Datei1 -ef Datei2 wahr, wenn Datei1 und Datei2 ein und die selbe Datei sind (Hard-Link)
-O Datei wahr, wenn Datei existiert und dem aktuellen Benutzer gehört
-G Datei wahr, wenn Datei existiert und der Gruppe des aktuellen Benutzers gehört
-f Datei wahr, wenn Datei existiert und eine normale Datei ist
-d Datei wahr, wenn Datei ein Verzeichnis ist
-L Datei wahr, wenn Datei existiert und ein symbolischer Link ist
-b Datei wahr, wenn Datei ein Block-Device ist
-c Datei wahr, wenn Datei ein Character-Device ist
-p Datei wahr, wenn Datei existiert und eine Pipe ist
-S Datei wahr, wenn Datei existiert und ein Socket ist
-t fd wahr, wenn fd ein auf einem Terminal geöffneter Dateibezeichner ist
-u Datei wahr, wenn Datei existiert und das SUID-Bit gesetzt ist
-g Datei wahr, wenn Datei existiert und das SGID-Bit gesetzt ist
-k Datei wahr, wenn Datei existiert und das Sticky-Bit gesetzt ist
Verkettung von Bedingungen
! Bedingung wahr, wenn Bedingung falsch ist, und umgekehrt
Bedingung1 -a Bedingung2 wahr, wenn beide Bedingungen wahr sind
Bedingung1 -o Bedingung2 wahr, wenn eine der beiden Bedingungen wahr ist
Vergleich von Zeichenketten
String1 = String2 wahr, wenn String1 und String2 gleich sind
String1 == String2 wie String1 = String2
String1 != String2 wahr, wenn String1 und String2 nicht gleich sind
String1 < String2 wahr, wenn String1 alphabetisch vor String2 eingeordnet wird
String1 > String2 wahr, wenn String1 alphabetisch hinter String2 eingeordnet wird
-z String wahr, wenn String die Länge null hat
-n String wahr, wenn String länger als null Zeichen ist.
String wie -n String
Vergleich von Zahlenwerten
Wert1 -eq Wert2 wahr, wenn Wert1 und Wert2 arithmetisch gleich sind
Wert1 -ne Wert2 wahr, wenn Wert1 und Wert2 arithmetisch ungleich sind
Wert1 -lt Wert2 wahr, wenn Wert1 arithmetisch kleiner als Wert2 ist
Wert1 -le Wert2 wahr, wenn Wert1 arithmetisch kleiner oder gleich Wert2 ist
Wert1 -gt Wert2 wahr, wenn Wert1 arithmetisch größer als Wert2 ist
Wert1 -ge Wert2 wahr, wenn Wert1 arithmetisch größer oder gleich Wert2 ist
Bash-Optionen
-o Option wahr, wenn die Bash-Option Option eingeschaltet ist

Im nächsten Beispiel nutzen wir die Option -r von test, um erst das Vorhandensein der /etc/passwd festzustellen, bevor wir nach dem Benutzer tux suchen:

if [ -r /etc/passwd ]; then
   if grep -q tux /etc/passwd; then
     echo "Hallo Tux."
   else
     echo "Tux kenne ich nicht."
   fi
 else
   echo "/etc/passwd nicht lesbar."
 fi

Wichtig ist, dass Sie bei Verwendung der Klammern als Kurzschreibweise für test ein Leerzeichen zwischen Klammer und erster Option lassen, sonst erkennt die Bash das Kürzel nicht. Der Parameter “-r” prüft, ob es die Datei /etc/passwd gibt und ob sie lesbar ist. Wenn wir uns zudem vergewissern wollten, dass es sich bei /etc/passwd um eine reguläre Datei und nicht etwa um einen symbolischen Link handelt, müssen wir die Bedingung erweitern:

if [ -r /etc/passwd -a -f /etc/passwd ]; then
   if grep -q tux /etc/passwd; then
     echo "Hallo Tux."
   else
     echo "Tux kenne ich nicht."
   fi
 else
   echo "/etc/passwd nicht lesbar oder nicht regulär."
 fi

Die im Listing hervorgehobene Option “-a” bewirkt, dass beide Bedingungen getestet und mit einem “logischen Und” verknüpft werden. Sind beide wahr, ist das Ergebnis wahr – ist mindestens eine falsch, ist auch das Ergebnis falsch. Anders verhält sich das “logische Oder”, bei test die Option “-o”. Hier genügt eine wahre Bedingung, damit das Gesamtergebnis wahr wird. “-o” und “-a” können mehrfach vorkommen. Es werden dann von links nach rechts zunächst alle “Und”-Verknüpfungen und dann alle “Oder”-Verknüpfungen ausgewertet.

Mehrfach-Bedingungen mit Bash-Mitteln

Doch der logischen Verknüpfung mittels test sind Grenzen gesetzt. So ist es zum Beispiel nicht möglich, die Prüfung der /etc/passwd zusammen mit der Suche nach dem Benutzer tux durchzuführen. Hier müssen wir uns der logischen Operatoren && (Und) und || (Oder) aus der Bash bedienen, die wir im ersten Teil (LinuxUser 12/2000, S. 38ff) in Tabelle 2 vorgestellt haben:

if [ -r /etc/passwd ] && grep -q tux /etc/passwd; then
   echo "Hallo Tux."
 else
   echo "Tux kenne ich nicht."
 fi

Einziger Nachteil dieser Lösung: Ist /etc/passwd nicht lesbar, erhalten wir die Meldung, den User tux gäbe es nicht. Wie auch im Beispiel der Option “-o” bei test lassen sich die logischen Operatoren der Bash mehrfach hintereinander benutzen und werden ebenfalls von links nach rechts ausgewertet.

Damit endet der vierte Teil des Programming Corners. In der nächsten Ausgabe wird uns das Thema Kontrollstrukturen weiter beschäftigen, insbesondere mit der Fallunterscheidung mittels case sowie den ersten Schleifen.

LinuxUser 04/2001 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben