Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

 << zurück
Shell-Programmierung von Jürgen Wolf
Einführung, Praxis, Referenz
Buch: Shell-Programmierung

Shell-Programmierung
782 S., mit CD, 44,90 Euro
Rheinwerk Computing
ISBN 3-89842-683-1
gp Kapitel 4 Kontrollstrukturen
  gp 4.1 Bedingte Anweisung mit if
    gp 4.1.1 Kommandos testen mit if
    gp 4.1.2 Kommandoverkettung über Pipes mit if
  gp 4.2 Die else-Alternative für eine if-Verzweigung
  gp 4.3 Mehrfache Alternative mit elif
  gp 4.4 Das Kommando test
    gp 4.4.1 Ganze Zahlen vergleichen
    gp 4.4.2 Ganze Zahlen vergleichen mit let (Bash und Korn-Shell only)
    gp 4.4.3 Zeichenketten vergleichen
    gp 4.4.4 Zeichenketten vergleichen (Bash und Korn-Shell only)
  gp 4.5 Status von Dateien erfragen
  gp 4.6 Logische Verknüpfung von Ausdrücken
    gp 4.6.1 Negationsoperator !
    gp 4.6.2 Die UND-Verknüpfung (-a und &&)
    gp 4.6.3 Die ODER-Verknüpfung (-o und ||)
    gp 4.6.4 Klammerung und mehrere logische Verknüpfungen
  gp 4.7 Short Circuit-Tests – ergebnisabhängige Befehlsausführung
  gp 4.8 Die Anweisung case
    gp 4.8.1 Alternative Vergleichsmuster
    gp 4.8.2 case und Wildcards
    gp 4.8.3 case und Optionen
  gp 4.9 Schleifen
  gp 4.10 for-Schleife
    gp 4.10.1 Argumente bearbeiten mit for
    gp 4.10.2 for und die Dateinamen-Substitution
    gp 4.10.3 for und die Kommando-Substitution
    gp 4.10.4 for und Array (Bash und Korn Shell only)
    gp 4.10.5 for-Schleife mit Schleifenzähler (Bash only)
  gp 4.11 Die while-Schleife
  gp 4.12 Die until-Schleife
  gp 4.13 Kontrollierte Sprünge
    gp 4.13.1 Der Befehl continue
    gp 4.13.2 Der Befehl break
  gp 4.14 Endlosschleifen

Kapitel 4 Kontrollstrukturen

Um aus der »Shell« eine »echte« Programmiersprache zu machen, sind so genannte Kontrollstrukturen erforderlich. Dabei handelt es sich um Entscheidungsverzweigungen oder Schleifen. Eine Programmiersprache ohne Verzweigungen wäre wohl eine Katastrophe. Mit einer Verzweigung können Sie dafür sorgen, dass die weitere Ausführung des Scripts von einer bestimmten Bedingung abhängt und eben entsprechend verzweigt wird. Ebenso sieht es mit den Schleifen aus. Anstatt immer Zeile für Zeile dieselben Anweisungen auszuführen, können Sie dies auch in einer Schleife zusammenfassen.


Rheinwerk Computing

4.1 Bedingte Anweisung mit idowntop

Wenn Sie überprüfen wollen, ob eine Eingabe von der Kommandozeile oder der Tastatur (egal, ob eine Zahl oder eine Zeichenkette) korrekt war, ob das Auslesen einer Datei ein entsprechendes Ergebnis beinhaltet, Sie eine Datei bzw. ein bestimmtes Attribut prüfen müssen oder den Erfolg einer Kommandoausführung testen wollen, dann können Sie hierfür die bedingte Anweisung (oder auch Verzweigung) mit if verwenden. Die korrekte Syntax der if-Verzweigung sieht wie folgt aus:

if Kommando_erfolgreich
then
   # Ja, Kommando war erfolgreich
   # ... hier Befehle für erfolgreiches Kommando verwenden
fi

Im Anschluss des Schlüsselwortes if muss ein Kommando oder eine Kommandofolge folgen. Wurde das Kommando (oder die Kommandofolge) erfolgreich ausgeführt, wird als Rückgabewert 0 zurückgegeben (wie dies ja bei Kommandos üblich ist). Im Fall einer erfolgreichen Kommandoausführung werden also die Befehle im darauf folgenden then (bis zum fi) ausgeführt. Bei einer fehlerhaften Kommandoausführung wird die Ausführung, sofern weitere Kommandos vorhanden sind, hinter dem fi fortgesetzt. Oder einfacher: Falls der Befehl einen Fehler zurückgab, wird der Anweisungsblock in der Verzweigung übersprungen. fi (rückwärts geschriebenes if) schließt eine if-Anweisung bzw. den Anweisungsblock ab (siehe Abbildung 4.1).


Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 4.1   Bedingte Anweisung mit if


Wenn Sie sich schon mal einige Scripts angesehen haben, werden Sie feststellen, dass häufig folgende Syntax bei einer if-Verzweigung verwendet wird:

if [ bedingung ]
then
   # Ja, Bedingung war erfolgreich
   # ... hier Befehle für erfolgreiche Bedingung verwenden
fi

Selbstverständlich dürfen Sie die if-Verzweigung zu einem etwas unleserlichen Code zusammenpacken, müssen dann aber entsprechend Semikolons zur Trennung verwenden (diese Schreibweise wird gewöhnlich verwendet, um eine Verzweigung auf der Kommandozeile einzugeben):

if [ bedingung ]; then befehl(e) ; fi

Bei einer Verwendung von eckigen Klammern handelt es sich um den test-Befehl, dessen symbolische Form eben [ ... ] ist. Hierauf wird noch recht umfangreich eingegangen. Wir wollen uns jetzt weiter mit der if-Verzweigung ohne den test-Befehl befassen.


Skurriles: Wenn ich hier behaupte, dass die eckigen Klammern eine symbolische Form für test sind, stimmt das eigentlich nicht. Wer mal nach einem Kommando »[« sucht ( which [ ), wird überrascht sein, dass es tatsächlich ein »Binary« mit diesem Namen gibt. Richtig gesehen handelt es sich somit nicht um eine andere Schreibweise von test, sondern um ein eigenständiges Programm, das als letzten Parameter die schließende eckige Klammer auswertet.



Rheinwerk Computing

4.1.1 Kommandos testen mit if  downtop

Hierzu ein einfaches Shellscript, welches das Kommando grep auf erfolgreiche Ausführung überprüft. In der Datei /etc/passwd wird einfach nach einem Benutzer gesucht, den Sie als erstes Argument (Positionsparameter $1) in der Kommandozeile angeben (siehe Abbildung 4.2). Je nach Erfolg, bekommen Sie eine entsprechende Meldung ausgegeben.

# Demonstriert eine Verzweigung mit if
# Name: aif1
# Benutzer in /etc/passwd suchen ...
if grep "^$1" /etc/passwd
then
   # Ja, grep war erfolgreich
   echo "User $1 ist bekannt auf dem System"
   exit 0;  # Erfolgreich beenden ...
fi
# Angegebener User scheint hier nicht vorhanden zu sein ...
echo "User $1 gibt es hier nicht"

Das Script bei der Ausführung:

you@host > ./aif1 you
you:x:1001:100::/home/you:/bin/bash
User you ist bekannt auf dem System
you@host > ./aif1 tot
tot:x:1000:100:J.Wolf:/home/tot:/bin/bash
User tot ist bekannt auf dem System
you@host > ./aif1 root
root:x:0:0:root:/root:/bin/bash
User root ist bekannt auf dem System
you@host > ./aif1 rot
User rot gibt es hier nicht

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 4.2   Bedingte if-Anweisung in der Praxis


Findet grep hier den Benutzer, den Sie als erstes Argument übergeben haben, liefert es den Exit-Status 0 zurück und somit wird then in der if-Verzweigung ausgeführt. Bei erfolgloser Suche gibt grep einen Wert ungleich 0 zurück, womit nicht in die if-Verzweigung gewechselt, sondern mit der Ausführung hinter dem fi der if-Verzweigung fortgefahren wird.

Was in diesem Beispiel ein wenig stört, ist die Standardausgabe, die hier unerwünscht »hereinplappert«. Hier lässt sich aber wie in der Kommandozeile eine einfache Umleitung in das Datengrab /dev/null legen. Hierzu müssen Sie nur die Zeile

if grep "^$1" /etc/passwd

umändern in

if grep "^$1" /etc/passwd > /dev/null

Natürlich kann die Fehlerausgabe (stderr) auch noch unterdrückt werden:

if grep "^$1" /etc/passwd > /dev/null 2>&1

Somit würde die Ausführung des Scripts jetzt wie folgt aussehen:

you@host > ./aif1 you
User you ist bekannt auf dem System
you@host > ./aif1 tot
User tot ist bekannt auf dem System
you@host > ./aif1 root
User root ist bekannt auf dem System
you@host > ./aif1 rot
User rot gibt es hier nicht

Aus Abschnitt 1.8.8 kennen Sie ja bereits den Exit-Status und wissen, wie Sie diesen mit der Variablen $? abfragen können. Somit könnten Sie statt – wie im Beispiel geschehen – den Kommandoaufruf mit if auf Erfolg zu testen, den Rückgabewert eines Kommandos testen. Allerdings benötigen Sie hierzu wieder das test-Kommando oder dessen »alternative« Schreibweise in eckigen Klammern. Zwar wurde das test-Kommando noch nicht behandelt, aber aus Referenzgründen will ich Ihnen das Beispiel hier nicht vorenthalten:

# Demonstriert eine test-Verzweigung mit if
# Name: aif2
# Benutzer in /etc/passwd suchen ...
grep "^$1" /etc/passwd > /dev/null
# Ist der Exit-Status in $? nicht gleich (not equal) 0 ...
if [ $? -ne 0 ]
   then
   echo "Die Ausführung von grep ist fehlgeschlagen"
   echo "Vermutlich existiert User $1 hier nicht"
   exit 1  # Erfolglos beenden
fi
# grep erfolgreich
echo "User $1 ist bekannt auf dem System"

Das Script bei der Ausführung:

you@host > ./aif2 you
User you ist bekannt auf dem System
you@host > ./aif2 rot
Die Ausführung von grep ist fehlgeschlagen
Vermutlich existiert User rot hier nicht

Rheinwerk Computing

4.1.2 Kommandoverkettung über Pipes mit if  toptop

Wenn bei einer if-Abfrage mehrere Kommandos mit den Pipes verwendet werden, ist der Rückgabewert immer der des letzten Kommandos. Dies scheint auch irgendwie logisch, denn es gibt nur eine Variable für den Exit-Status ($?). Somit wird in einer Kommandoverkettung immer die Variable $? mit einem neuen Exit-Status belegt – bis zum letzten Kommando in der Verkettung.

Trotzdem hat eine solche Kommandoverkettung durchaus ihre Tücken. Denn wem nützt es, wenn das letzte Kommando erfolgreich war, aber eines der vorhergehenden Kommandos einen Fehler produziert hat? Hier ein Beispiel, das zeigt, worauf ich hinaus will:

# Demonstriert eine Kommandoverkettung mit if
# Name: aif3
# In /usr/include nach erstem eingegebenen Argument suchen
if ls -l /usr/include | grep $1 | wc -l
   then
   echo "Suche erfolgreich"
   exit 0
fi
echo "Suche erfolglos"

Das Script bei der Ausführung:

you@host > ./aif3 l.h
15
Suche erfolgreich
you@host > ./aif3 std
5
Suche erfolgreich
you@host > ./aif3 asdfjklö
0
Suche erfolgreich
you@host > ./aif3
Aufruf: grep [OPTION]... MUSTER [DATEI]...
»grep --help« gibt Ihnen mehr Informationen.
0
Suche erfolgreich

Wie Sie sehen konnten, gibt es bei den letzten zwei Suchanfragen eigentlich keine erfolgreichen Suchergebnisse mehr. Trotzdem wird »Suche erfolgreich« zurückgegeben – was ja auch klar ist, weil die Ausführung des letzten Kommandos wc –l keinen Fehler produziert hat und 0 zurückgab. Sie haben zwar jetzt die Möglichkeit, die Standardfehlerausgabe zu überprüfen, aber dies wird mit jedem weiteren Befehl in der Pipe immer unübersichtlicher.

PIPESTATUS auswerten (Bash only)

In der Bash kann Ihnen hierbei die Shell-Variable PIPESTATUS helfen. Die Variable PIPESTATUS ist ein Array, welches die Exit-Status der zuletzt ausgeführten Kommandos enthält. Der Rückgabewert des ersten Befehls steht in ${PIPESTATUS[0]}, der zweite in ${PIPESTATUS[1]}, der dritte in ${PIPESTATUS[2]} usw. Die Anzahl der Befehle, die in einer Pipe ausgeführt wurden, enthält ${#PIPESTATUS[*]} und alle Rückgabewerte (getrennt mit einem Leerzeichen) erhalten Sie mit ${PIPESTATUS[*]}. Hier die Verwendung von PIPESTATUS in der Praxis, angewandt auf das Beispiel aif3:

# Demonstriert die Verwendung von PIPESTATUS in der Bash
# Name: apipestatus
# In /usr/include suchen
ls -l /usr/include | grep $1 | wc -l
# kompletten PIPESTATUS in die Variable STATUS legen
STATUS=${PIPESTATUS[*]}
# Die Variable STATUS auf die einzelnen
# Positionsparameter aufteilen
set $STATUS
# Status einzeln zurückgeben
echo "ls    : $1"
echo "grep  : $2"
echo "wc    : $3"
# Fehlerüberprüfungen der einzelnen Werte ...
if [ $1 -ne 0 ]; then
  echo "Fehler bei ls" >&2
fi
if [ $2 -ne 0 ]; then
  echo "Fehler bei grep" >&2
fi
if [ $3 -ne 0 ]; then
  echo "Fehler bei wc" >&2
fi

Das Shellscript bei der Ausführung:

you@host > ./apipestatus std
5
ls    : 0
grep  : 0
wc    : 0
you@host > ./apipestatus
Aufruf: grep [OPTION]... MUSTER [DATEI]...
»grep --help« gibt Ihnen mehr Informationen.
0
ls    : 141
grep  : 2
wc    : 0
Fehler bei ls
Fehler bei grep
you@host > ./apipestatus asdf
0
ls    : 0
grep  : 1
wc    : 0
Fehler bei grep

Wichtig ist, dass Sie PIPESTATUS unmittelbar nach der Kommandoverkettung abfragen. Führen Sie dazwischen bspw. eine echo-Ausgabe durch, finden Sie im Array PIPESTATUS nur noch den Exit-Status des echo-Kommandos.

Pipestatus auswerten (für alle Shells)

Mit einigen Klimmzügen ist es auch möglich, die Auswertung der Exit-Codes aller Kommandos in einer Pipe vorzunehmen. Zuerst werden neue Ausgabekanäle angelegt und die Ausführung der Pipe muss in einen eval-Anweisungsblock gepackt werden. Leitet man die entsprechenden Kanäle in die richtigen Bahnen, erhält man auch hier sämtliche Exit-Codes einer Pipe. Auf die Funktion eval wird noch in Kapitel 9, Nützliche Funktionen, näher eingegangen, aber auch in diesem Fall will ich Ihnen vorab ein Beispiel anbieten (auf die test-Überprüfung wurde allerdings verzichtet).

# Demonstriert, wie man alle Kommandos einer Pipe ohne
# die Shell-Variable PIPESTATUS ermitteln kann
# Name: apipestatus2
exec 3>&1 # dritten Ausgabekanal öffnen (für stdout)
exec 4>&1 # vierten Ausgabekanal öffnen (für Exit-Status)
eval `
{
    {
        ls -l /usr/include
        echo lsexit=$? >&4;
    } | {
        grep $1
        echo grepexit=$? >&4;
    } | wc -l
} 4>&1 >&3 # Umleitung
`
echo "ls    : $lsexit"
echo "grep  : $grepexit"
echo "wc    : $?"

Das Shellscript bei der Ausführung:

you@host > ./apipestatus2
Aufruf: grep [OPTION]... MUSTER [DATEI]...
"grep --help" gibt Ihnen mehr Informationen.
0
ls    : 141
grep  : 2
wc    : 0
you@host > ./apipestatus2 std
5
ls    : 0
grep  : 0
wc    : 0
you@host > ./apipestatus2 asdfasf
0
ls    : 0
grep  : 1
wc    : 0


Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

 << zurück
  
  Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Shell-Programmierung
Shell-Programmierung
bestellen
 Buchtipps
Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-Programmierung


Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo





Copyright © Rheinwerk Verlag GmbH 2005
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern