12.11Verzweigungen, Schleifen und Funktionen in bash-Scripts

Verzweigungen in Shell-Programmen können mit den Kommandos if und case gebildet werden. Während sich if eher für einfache Fallunterscheidungen eignet, ist case für die Analyse von Zeichenketten prädestiniert (Mustervergleich).

if-Verzweigungen

In der Shell-Datei iftst wird durch eine if-Abfrage getestet, ob zwei Parameter übergeben wurden. Wenn das nicht der Fall ist, wird eine Fehlermeldung ausgegeben. Das Programm wird durch exit mit einem Rückgabewert ungleich 0 (Indikator für Fehler) beendet. Andernfalls wird der Inhalt der beiden Parameter auf dem Bildschirm angezeigt.

#! /bin/sh # Beispiel iftst if test $# -ne 2; then echo "Dem Kommando müssen genau zwei Parameter übergeben werden!" exit 1 else echo "Parameter 1: $1, Parameter 2: $2" fi

Ein kurzer Testlauf demonstriert das Verhalten des Programms:

user$ iftst a Dem Kommando müssen genau zwei Parameter übergeben werden! user$ iftst a b Parameter 1: a, Parameter 2: b

Als Kriterium für die Verzweigung gilt der Rückgabewert des letzten Kommandos vor then. Die Bedingung ist erfüllt, wenn dieses Kommando den Rückgabewert 0 liefert. Wenn then noch in derselben Zeile angegeben wird (und nicht erst in der nächsten), dann muss das Kommando mit einem Semikolon abgeschlossen werden.

Verkehrte Logik

Beachten Sie, dass in der bash die Wahrheitswerte für wahr (0) und falsch (ungleich 0) umgekehrt definiert sind als in den meisten anderen Programmiersprachen! Kommandos, die ordnungsgemäß beendet werden, liefern den Rückgabewert 0. Jeder Wert ungleich 0 deutet auf einen Fehler hin. Manche Kommandos liefern je nach Fehlertyp unterschiedliche Fehlerwerte.

Im obigen Beispiel wurde die Bedingung unter Zuhilfenahme des bash-Kommandos test gebildet. Der Operator -ne steht dabei für ungleich (not equal). test kommt immer dann zum Einsatz, wenn zwei Zeichenketten oder Zahlen miteinander verglichen werden sollen, wenn getestet werden soll, ob eine Datei existiert etc. Das Kommando wird im nächsten Abschnitt beschrieben.

Das obige Programm könnte auch anders formuliert werden: Statt des test-Kommandos kann eine Kurzschreibweise in eckigen Klammern verwendet werden. Dabei muss nach [ und vor ] jeweils ein Leerzeichen angegeben werden!

Außerdem kann das zweite echo-Kommando aus der if-Struktur herausgelöst werden, weil wegen der exit-Anweisungen alle Zeilen nach fi nur dann ausgeführt werden, wenn die Bedingung erfüllt ist.

#! /bin/sh # Beispiel iftst, 2. Variante if [ $# -ne 2 ]; then echo "Dem Kommando müssen genau zwei Parameter übergeben werden!" exit 1 fi echo "Parameter 1: $1, Parameter 2: $2"

Formulierung von Bedingungen mit »test«

In der bash ist es nicht möglich, Bedingungen – etwa den Vergleich einer Variablen mit einem Wert – direkt anzugeben. Zum einen basiert die ganze Konzeption der bash darauf, dass alle Aktionen über ein einheitliches Kommandokonzept durchgeführt werden, zum anderen sind Sonderzeichen wie > und < bereits für andere Zwecke vergeben. Aus diesem Grund müssen Sie zur Formulierung von Bedingungen in Schleifen und Verzweigungen das bash-Kommando test verwenden. (test existiert übrigens auch als eigenständiges Kommando außerhalb der bash. Es wurde aber auch in die bash integriert, um eine höhere Verarbeitungsgeschwindigkeit zu erzielen.)

test liefert als Rückgabewert 0 (wahr), wenn die Bedingung erfüllt ist, oder 1 (falsch), wenn die Bedingung nicht erfüllt ist. Um den Schreibaufwand zu verringern, ist eine Kurzschreibweise in eckigen Klammern vorgesehen.

test wird in drei Aufgabenbereichen eingesetzt: zum Vergleich zweier Zahlen, zum Vergleich von Zeichenketten und zum Test, ob eine Datei existiert und bestimmte Eigenschaften aufweist. Die folgenden Beispiele zeigen einige mögliche Anwendungsfälle:

test "$x"

überprüft, ob x belegt ist. Das Ergebnis ist falsch, wenn die Zeichenkette 0 Zeichen aufweist, andernfalls ist es wahr.

test $x -gt 5
testet, ob die Variable x einen Zahlenwert größer 5 enthält. Wenn x keine Zahl enthält, kommt es zu einer Fehlermeldung. Statt -gt (greater than) können auch die folgenden Vergleichsoperatoren verwendet werden: -eq (equal), -ne (not equal), -lt (less than), -le (less equal) und -ge (greater equal).

test -f $x
testet, ob eine Datei mit dem in x angegebenen Namen existiert.

Wenn test interaktiv in der Shell ausgeführt werden soll, muss nach dem test-Kommando die Variable $? (Rückgabewert des letzten Kommandos) mit echo gelesen werden:

user$ a=20 user$ test $a -eq 20; echo $? 0 user$ test $a -gt 20; echo $? 1

case-Verzweigungen

case-Konstruktionen werden mit dem Schlüsselwort case eingeleitet, dem der zu analysierende Parameter zumeist in einer Variablen folgt. Nach dem Schlüsselwort in können dann mehrere mögliche Musterzeichenketten angegeben werden, mit denen der Parameter verglichen wird. Dabei sind die gleichen Jokerzeichen wie bei Dateinamen erlaubt. Das Muster wird mit einer runden Klammer ) abgeschlossen, also etwa mit --*) zur Erkennung von Zeichenketten, die mit zwei Minuszeichen beginnen. Mehrere Muster können durch | voneinander getrennt werden. In diesem Fall werden beide Muster getestet. Beispielsweise dient *.c|*.h) zur Erkennung von *.c- und *.h-Dateien im selben Zweig.

Die der Klammer folgenden Kommandos müssen durch zwei Semikola abgeschlossen werden. Wenn ein else-Zweig benötigt wird, dann muss als letztes Muster * angegeben werden – alle Zeichenketten entsprechen diesem Muster. Bei der Abarbeitung einer case-Konstruktion wird nur der erste Zweig berücksichtigt, bei dem der Parameter dem angegebenen Muster entspricht.

Das folgende Beispiel casetst zeigt die Anwendung von case zur Klassifizierung der übergebenen Parameter in Dateinamen und Optionen. Die Schleife für die Variable i wird für alle der Shell-Datei übergebenen Parameter ausgeführt. Innerhalb dieser Schleife wird jeder einzelne Parameter mit case analysiert. Wenn der Parameter mit einem Bindestrich beginnt, wird der Parameter an das Ende der Variablen opt angefügt, andernfalls an das Ende von dat.

#!/bin/bash # Beispiel casetst for i do # Schleife für alle übergebenen Parameter case "$i" in -* ) opt="$opt $i";; * ) dat="$dat $i";; esac done # Ende der Schleife echo "Optionen: $opt" echo "Dateien: $dat"

Ein Beispiellauf der Shell-Datei beweist die Wirkungsweise dieser einfachen Fallunterscheidung. Die in ihrer Reihenfolge wahllos übergebenen Parameter werden in Optionen und Dateinamen untergliedert:

user$ casetst -x -y dat1 dat2 -z dat3 Optionen: -x -y -z Dateien: dat1 dat2 dat3

Nach demselben Schema können case-Verzweigungen auch zur Klassifizierung von bestimmten Dateikennungen verwendet werden, indem im Suchmuster *.abc angegeben wird. Wenn Sie sich eingehender mit case-Analysen beschäftigen möchten, sollten Sie sich die Shell-Datei /usr/bin/gnroff ansehen. Die Datei bereitet die in der Syntax von nroff übergebenen Parameter so auf, dass das verwandte Kommando groff damit zurechtkommt.

for-Schleifen

Die bash kennt drei Kommandos zur Bildung von Schleifen: for führt eine Schleife für alle Elemente einer angegebenen Liste aus. while führt eine Schleife so lange aus, bis die angegebene Bedingung nicht mehr erfüllt ist, until führt sie dagegen so lange aus, bis die Bedingung zum ersten Mal erfüllt ist. Alle drei Schleifen können mit break vorzeitig verlassen werden. continue überspringt den restlichen Schleifenkörper und setzt die Schleife mit dem nächsten Schleifendurchlauf fort.

Im ersten Beispiel werden der Variablen i der Reihe nach die Zeichenketten a, b und c zugewiesen. Im Schleifenkörper wird zwischen do und done der Inhalt der Variablen ausgegeben. Beachten Sie, dass sowohl am Ende der Liste als auch am Ende des echo-Kommandos ein Strichpunkt erforderlich ist. Auf diese Strichpunkte kann nur verzichtet werden, wenn die Eingabe auf mehrere Zeilen verteilt wird (was in Script-Dateien häufig der Fall ist).

user$ for i in a b c; do echo $i; done a b c

Die äquivalente mehrzeilige Formulierung des obigen Kommandos in einer Script-Datei würde so aussehen:

#! /bin/sh for i in a b c; do echo $i done

Die Liste für for kann auch mit Jokerzeichen für Dateinamen oder mit {..}-Konstruktionen zur Bildung von Zeichenketten gebildet werden. Im folgenden Beispiel werden alle *.tex-Dateien in *.tex~-Dateien kopiert. Das Zeichen ~ am Ende eines Dateinamens bezeichnet unter Unix/Linux üblicherweise eine Backup-Datei. Beim cp-Kommando ist $file jeweils in Anführungszeichen gestellt, damit auch Dateinamen mit Leerzeichen korrekt behandelt werden.

user$ for file in *.tex; do cp "$file" "$file~"; done

Oft benötigen Sie Schleifen, um eine Textdatei Zeile für Zeile abzuarbeiten. Kein Problem: Übergeben Sie an das Schlüsselwort in einfach das Ergebnis von cat datei! Das folgende Miniprogramm erstellt für alle Datenbanken, die in der Datei dbs.txt zeilenweise genannt sind, ein komprimiertes Backup in der Datei dbname.sql.gz:

#!/bin/bash # Schleife über for db in $(cat dbs.txt); do mysqldump $db | gzip -c > $db.sql.gz done

Wenn for-Schleifen ohne in ... gebildet werden, dann werden der Schleifenvariablen der Reihe nach alle beim Aufruf übergebenen Parameter übergeben (das entspricht also in $*). Ein Beispiel für so eine Schleife finden Sie bei der Beschreibung von case.

Wenn an das case-Beispiel Dateinamen mit Leerzeichen übergeben werden, kommt es allerdings zu Problemen: Die bash interpretiert das Leerzeichen als Trennzeichen und verarbeitet die Teile des Dateinamens getrennt. Abhilfe schafft die folgende Konstruktion:

#!/bin/bash # Schleife über alle Parameter, kommt mit Leerzeichen in den Dateinamen zurecht for i in "$@"; do ls -l "$i" done

while-Schleifen

Im folgenden Beispiel wird der Variablen i der Wert 1 zugewiesen. Anschließend wird die Variable im Schleifenkörper zwischen do und done so oft um 1 erhöht, bis der Wert 5 überschritten wird. Beachten Sie, dass Bedingungen wie bei if-Verzweigungen mit dem Kommando test bzw. mit dessen Kurzschreibweise in eckigen Klammern angegeben werden müssen.

user$ i=1; while [ $i -le 5 ]; do echo $i; i=$[$i+1]; done 1 2 3 4 5

Die folgende Schleife verarbeitet alle Dateinamen, die sich aus dem Kommando ls *.jpg ergeben:

ls *.jpg | while read file do echo "$file" done

until-Schleifen

Der einzige Unterschied zwischen until-Schleifen und while-Schleifen besteht darin, dass die Bedingung logisch negiert formuliert wird. Das folgende Kommando ist daher zur obigen while-Schleife äquivalent. Dabei wird -gt zur Formulierung der Bedingung i>5 (greater than) verwendet.

user$ i=1; until [ $i -gt 5 ]; do echo $i; i=$[$i+1]; done 1 2 3 4 5

Funktionen

Das Schlüsselwort function definiert eine Subfunktion, die wie ein neues Kommando aufgerufen werden kann. Der Code der Funktion muss zwischen geschwungene Klammern gesetzt werden. Innerhalb der Funktion können mit local lokale Variablen definiert werden. Funktionen können rekursiv aufgerufen werden. Funktionen müssen vor ihrem ersten Aufruf deklariert werden!

An Funktionen können Parameter übergeben werden. Anders als bei vielen Programmiersprachen werden die Parameter nicht in Klammern gestellt. Innerhalb der Funktion können die Parameter den Variablen $1, $2 entnommen werden, d.h., eine Funktion verarbeitet Parameter auf die gleiche Art und Weise wie ein bash-Script. Das folgende Mini-Script gibt Hello World, Linux! aus:

#!/bin/bash function myfunc { echo "Hello World, $1!" } myfunc "Linux"

Das Schlüsselwort function ist optional. Wenn auf function verzichtet wird, müssen dem Funktionsnamen allerdings zwei runde Klammern folgen. Somit ist das folgende Programm gleichwertig zum obigen Beispiel:

#!/bin/bash myfunc() { echo "Hello World, $1!" } myfunc "Linux"

exit

exit beendet das laufende Script. Damit gibt das Script den Rückgabewert des zuletzt ausgeführten Kommandos zurück. Wenn Sie das nicht wünschen, können Sie an exit explizit einen Wert übergeben. Dabei wird der Wert 0 verwendet, um ein fehlerfreies Programmende zu signalisieren. Alle anderen Zahlen signalisieren einen Fehler, wobei 1 üblicherweise auf einen allgemeinen Fehler hindeutet und 2 auf einen Fehler in den übergebenen Parametern. Alle anderen Fehlercodes sind anwendungsspezifisch.