Schleifen E-Mail
Geschrieben von: tpm   

Ein mächtiges Instrument in der Programmierung sind Kontrollstrukturen, welche wiederkehrende Befehlsfolgen so oft auszuführen, bis sie durch eine bestimmte Abbruchbedingung beendet werden. Sie ermöglichen beispielsweise die Überwachung einer Eingabe oder die Berechnung eines Wertes.

 

Zu dieser Art von Kontrollstrukturen zählen Schleifen. Die Shell bietet davon zwei Arten an:

  • Kopfgesteuerte Schleifen
  • Durchlaufschleifen

Der klassische Vertreter der kopfgesteuerten Schleifen ist die while-Schleife. Der Begriff kopfgesteuert rührt daher, dass hier die Abbruchbedingung schon zu Beginn geprüft wird. Dies bedeutet gleichzeitig, dass die Schleife nicht unbedingt durchlaufen werden muss. Das Konstrukt einer while-Schlife ist wie folgt aufgebaut:

 

example
while [ Bedingung ]
do
Aktion
done

 

Für ein praktisches Beispiel wird nun mit dem while-Konstrukt ein Script erstellt, welches ein einfaches Menü mit der Auswahl an einigen Optionen bietet:

 

script
#!/bin/bash

eingabe=

while [ "$eingabe" != 'q' ]
do
clear
echo 'Bitte wählen Sie eine Aktion aus:'
echo 'p zum Anzeigen der Prozesse, w zum Anzeigen des Benutzers und q zum Beenden.'
read eingabe

case $eingabe in
p) ps; echo 'Bitte RETURN drücken...'; read ;;
w) who; echo 'Bitte RETURN drücken...'; read ;;
q) echo 'Das Menü wird beendet! Bitte RETURN drücken...'; read ;;
*) echo 'Ungültige Eingabe! Bitte RETURN drücken...'; read
esac
done

 

Dieses Script vergleicht zu Beginn der while-Schleife, ob der Inhalt der Variablen eingabe ungleich q ist. Dies ist zum Scriptstart der Fall, da die Variable eingabe mit NULL initialisiert wurde. Dann wird der Bildschrim gelöscht ( clear ) und eine Eingabe vom Benutzer erwartet ( read ), welche in der Variablen eingabe abgelegt wird.

Anschließend wird in der case-Abfrage die entsprechende Aktion ausgeführt. Das erneute read am Ende der Zeilen hat keinen Einfluss auf die Variable eingabe, sondern dient lediglich dazu eine Pause durchzuführen. Die Semikolons zwischen den Kommandos sind die Kommandotrenner. Das Script läuft nun so oft ab, bis der Benutzer die Eingabe q tätigt oder es mit Strg+C abbricht.

 

Eine Durchlaufschleife ist die for-Schleife. Die Typenbezeichnung rührt daher, da die for-Schleife alle Elemente einer Argumentenliste durchläuft. Der einfache Aufbau der for-Schleife ergibt sich wie folgt:

 

input
for Element in Argumentenliste
do
Aktion (mit Element)
done

 

Im for-Konstrukt is Element wieder ein frei wählbarer Bezeichner. Innerhalb von do und done wir dann eine Aktion mit genau dem Element durchgeführt, welches beim Durchlauf der Argumentenliste gerade erfasst wird. Es ist für die Funktion dieser Schleife zwar nicht zwingend erforderlich eine Aktion, also ein Kommando mit dem gerade erfassten Element durchzuführen, aber in den meisten Fällen will man ja genau dies erreichen, wenn man die for-Schleife wählt.

Die Argumentenliste selbst kann die unterschiedlichsten Werte beinhalten. Was nun genau ein Element ist, hängt von der Art der Argumentenliste ab, weshalb diese erstmal genauer erläutert werden soll.

In der Regel werden Elemente anhand des IFS identifiziert. Also ist jedes Zeichen oder jede Zeichenkette, welche durch ein oder mehrere Leerzeichen, Tabulatorschritt oder Zeilenumbruch von anderen Zeichen oder Zeichenketten getrennt wird ein Element. Die IFS selbst werden nie als Element angesehen, weshalb beispielsweise mehrere Leerzeichen zwischen zwei Zeichen nicht dazu führen würden, dass auch mal ein Leerzeichen selbst als Element angesehen würde.

In der einfachsten Form kann also die Argumentenliste direkt aus mehreren Argumenten, getrennt durch das Leerzeichen dienen:

 

example
for element in 1 2 3   A B C
do
echo $element
done

 

Dieses Konstrukt würde zur zeilenweisen Ausgabe der Elemente 1 2 3 A B C führen. Wieder wurde die Argumentenliste mit Absicht etwas auseinandergezogen. Durch die zeilenweise Ausgabe der angegebenen Elemente wird nämlich zudem belegt, dass der IFS nicht als Element angesehen wird. Andernfalls müsste es zur Ausgabe einer Leerzeile zwischen den Elementen 3 und A kommen.

Leider weis man in der Praxis selten vorher, welche Elemente die Argumentenliste enthalten wird. Daher wird die Argumentenliste meist durch eine Variable repräsentiert, in welche die Elemente im vorherigen Programmablauf abgelegt werden. Ein praktisches mit einer Variablen als Argumentenliste ist folgendes Script:

 

script
#!/bin/bash

declare -i myZaehler=0     # Variable für Addition als Integer deklarieren
echo "Geben Sie eine Argumentenliste ein:"
read myVar                 # Einlesen der Benutzereingabe in die Variable myVar

for element in $myVar
do
myZaehler="$myZaehler"+1
echo "Element $myZaehler : $element"
done
echo "Somit wurden $myZaehler Element(e) erfasst"

 

In diesem Script ist zudem ein einfacher Zähler integriert, der die Durchläufe der for-Schleife und damit die Anzahl der Elemente erfasst und ausgibt. Damit kann nun bei Interesse experimentiert werden, wie sich die for-Schleife bei der Eingabe verschiedenster Werte verhält.

Die for-Schleife eignet sich auch, um Elemente beliebieger Quellen für die spätere Weiterverarbeitung einem Array zuzuweisen. Im folgenden Beispiel soll dies durchgeführt werden. Als Erweiterung wird für die Argumentenliste die Ausgabe einer Kommandosubstitution verwendet:

 

script
#!/bin/bash

declare -i myZaehler=0       # Variable für Addition als Integer deklarieren
declare -a myArray

echo "Bitte einen gültiges Verzeichis angeben (absolute Pfadangabe): "
read myVar                   # Einlesen der Benutzereingabe in die Variable myVar

for element in `ls $myVar`   # Argumentenliste durch Kommandosubstitution
do
myArray[$myZaehler]=$element
myZaehler="$myZaehler"+1
done

echo "Das Array enthält ${#myArray[*]} Elemente!"

 

...nach dem Start des Scripts wird man zur Eingabe eines beliebigen Verzeichnispfads aufgefordert...

 

input
/usr/bin/

 

Durch die Kommandosubstitution wird in diesem Fall ls /usr/bin/ ausgeführt und das Resultat als Argumentenliste an die for-Schleife übergeben. Was ein Element ist, wird wieder durch die IFS bestimmt. Schließlich wird die Anzahl der Elemente des Arrays und damit respektive die Anzahl der Durchläufe der for-Schleife ausgegeben...

 

output
Das Array enthält 1345 Elemente!

 

 

Problematisch hingegen kann es werden, wenn man alle Elemente des Arrays mit der for-Schleife wieder auslesen möchte. Sind alle Elemente eines Arrays zusammenhängende Zeichenketten, wie in den bisherigen Beispielen, dann wird es ohne Probleme funktionieren. Nun kann ein Element im Array aber auch beispielsweise die zusammengehörige Zeichenfolge Hallo Welt! beinhalten. Bei der einfachen Angabe des kompletten Arrayinhalts als Argumentenliste käme es zu einem unerwünschten Effekt, welcher im folgenden Script verdeutlicht werden soll:

 

script
#!/bin/bash
declare -i myZaehler=0
declare -a myArray=( a b c 'Hallo Welt!' )
echo "Anzahl der Elemente des Arrays: ${#myArray[*]}"

for element in ${myArray[*]}
do
myZaehler="$myZaehler"+1
echo "Element $myZaehler : $element"
done

 

output
Anzahl der Elemente des Arrays: 4
Element 1 : a
Element 2 : b
Element 3 : c
Element 4 : Hallo
Element 5 : Welt!

 

Obwohl die Abfrage der Anzahl der Elemente des Arrays myArray eindeutig das Ergebnis 4 lieferte, werden in der for-Schleife 5 Elemente erfasst. Der Grund liegt hier wieder im IFS (hier Leerzeichen) in der eigentlich zusammengehörige Zeichenkette Hallo Welt!. Um dieses Problem zu beheben gibt es zwei praktikable Lösungen, die eine Modifikation der for-Schleife mit sich bringen:

 

example
for elements in "${myArray[@]"
do
myZaehler="$myZaehler"+1
echo "Element $myZaehler : $element"
done

 

Hier werden der for-Schleife die Inhalte von myArray explizit als Elemente übergeben. Somit wird auch das vierte Element Hallo Welt als ganzes Element erfasst. Die Argumentenliste muss in diesem Fall in doppelte Hochkommas gesetzt werden. Anmerkung: Vermutlich wird bei der Verwendung von * anstatt @ der Arrayinhalt als ein String übergeben, wodurch die Trennung durch den IFS erfolgt.

 

example
for ((i=0; i<${#myArray[*]}; i++))
do
myZaehler="$myZaehler"+1
echo "Element $myZaehler : ${my_array[$i]}"
done

 

Diese Form erinnert stark an die Zählschleife aus anderen Programmiersprachen und sie verhält sich auch genau so. Hier wird keine Argumentenliste übergeben, sondern ein Zählvorgang gestartet. Er beginnt bei 0, läuft so oft durch, solange i kleiner als die Arraygröße ist und nach jedem Durchlauf wird i um 1 erhöht. In der echo-Ausgabe wird nun der Wert von i herangezogen, um das jeweilige Element im Array über den Index zu definieren, welcher bekanntermaßen ebenfalls bei 0 beginnt. Zudem wird durch die Verwendung des Indexes das vierte Element mit dem Wert Hallo Welt! als Ganzes in dieser Schleife erfasst, so dass es zur korrekten Ausgabe kommt.

 

Eine besondere Form der for-Schleife eignet sich zum durchlaufen aller übergebenen Parameter (Optionen und Argumente) beim Aufruf eines Scripts. Die Parameter könnte man zwar durch die Systemvariable $* als Argumentenliste abrufen, für diesen speziellen Fall allerdings kann man auch folgendes Konstrukt verwenden:

 

example
for Parm
do
Aktion (mit Parm)
done

 

Hier entfällt das Schlüsselwort in, sowie die explizite Angabe einer Argumentenliste. Die for-Schleife weiß in diesem Fall aber, dass sie als Argumentenliste die übergebenen Parameter erfassen muss. Diese können dann innerhalb der for-Schleife ausgewertet werden, um  abhängig von den übergebenen Parametern die entsprechenden Aktionen einzuleiten.

 

Zuletzt aktualisiert am Mittwoch, den 28. Oktober 2009 um 21:05 Uhr