Für Programmschleifen innerhalb der Scripte bietet die bash die for, while und until-Schleife zur Auswahl. Die for-Schleife gibt es in zwei Modifikationen. Die erste Variante, die in vielen anderen Shells verfügbar ist, ist eine Abzählschleife, die Werte aus einer Werteliste ausliest und darauf hin die Schleife durchläuft. Die zweite Variante, die in der bash verfügbar ist, ist eine echte arithmetische Zählschleife. Die while und until-Schleifen sind bedingte Schleifen.
Die for-Schleife wird zur mehrfachen Ausführung einer Programmsequenz benutzt. Wie oft die Programmsequenz abgearbeitet wird, ist von der Anzahl der Elemente in der übergebenen Werteliste abhängig. for erwartet eine Variable, die als Zählvariable benutzt wird. Die Anweisungen zwischen do und done werden so oft durchlaufen, wie Werte aus der Werteliste gelesen weder können. Bei jedem Durchlauf wird der nächste Wert aus der Werteliste in die Variable geschrieben und die Kommandos zwischen do und done ausgeführt. Wenn die Liste abgearbeitet ist, wird die for-Schleife verlassen. Ein vorzeitiger Abbruch der for-Schleife ist mit break möglich. Ein vorzeitiger weiterer Schleifendurchlauf kann mit continue veranlasst werden. Durch Angabe eines Zahlenwertes hinter continue kann angegeben werden, wieviele Schleifendurchläufe übersprungen werden sollen. Mit dem Befehl break wird die Schleife vorzeitig verlassen. Die Werteliste enthält durch Leerzeichen oder Tabulator getrennte Worte, die fest vorgegeben oder durch Expansion oder Kommandosubstitution der Shell erzeugt werden. Worte die selbst Leerzeichen oder Sonderzeichen enthalten müssen in Anführungszeichen gesetzt (gequotet) werden. Wenn keine Werteliste vorgegeben wird, werden die Parameter $@ benutzt. Soll die Werteliste aus einer einfachen Zahlenfolge bestehen, so bietet sich die Verwendung des Kommandos seq an. Zwei einfache Beispiele sollen die Arbeit der for-Schleife verdeutlichen.
#!/bin/bash echo "Was braucht der Wandersmann?" for VAR in Stock Hut Gesangbuch do echo $VAR done |
Die Arbeitsweise ist also denkbar einfach. Beim ersten Schleifendurchlauf enthält die Variable VAR das erste Element der Werteliste, also das Wort Stock und die echo-Anweisung wird ausgeführt. Beim zweiten Durchlauf wird der Inhalt der Variable VAR auf Hut gesetzt usw usf. Wenn alle drei Werte ausgelesen sind, wird die Schleife nach der Ausführung des echo-Befehls verlassen. Interessant wird die Sache, wenn die Wertelist z.B. durch die Ausgabe eines Kommandos erfolgt. Im nächsten Beispiel wird mit ls der Inhalt des aktuellen Verzeichnisses ausgelesen. In der Schleife werden die einzelnen Dateinamen dann nacheinander in der Variable VAR abgelegt und dem Kommando file übergeben. Damit erhält man zu jeder Datei eine kurze Angabe zum Dateityp.
#!/bin/bash for VAR in $( ls ) do file $VAR done |
Dieses kleine Script sollte nun wie erwartet funktionieren und wahrscheinlich lief der Test auch zufriedenstellend. Es gibt jedoch ein Problem. Durch die Kommandosubstitution $( ls ) wird die Ausgabe des Kommandos ls an for übergeben, zuvor werden eventuell vorhandene Zeilenschaltungen durch Leerzeichen ersetzt. Dadurch erhält for nun wie gewünscht eine durch Leerzeichen getrennte Liste der Dateinamen aus dem aktuellen Verzeichnis. Das geht aber nur gut, wenn die Dateinamen selbst keine Leerzeichen enthalten. In diesem Fall separiert for die einzelnen Teile des Dateinamens und das Script produziert Unsinn!!!. Eine bessere Variante gibt es bei while.
In der bash (nicht in der sh !!!) ist auch eine arithmetische Variate der for-Schleife nutzbar, so wie man sie aus anderen Programmiersprachen kennt. Dabei kann eine echte Zählvariable verwendet werden und damit die Anzahl der Schleifendurchläufe vorher berechnet werden. Dazu ein einfaches Demonstrationsbeispiel:
#!/bin/bash for ((VAR=0;VAR<11;VAR=VAR+1)); do echo $VAR done |
Die Bildschirmausgabe sieht dann so aus:
bash-2.05b$ ./test 0 1 2 3 4 5 6 7 8 9 10 bash-2.05b$ |
In der Klammerung ((....)) (Arithmetik-Expansion der bash) befinden sich drei aritmetische Anweisungen (Expressions).
Die erste Anweisung wird nur beim Start der Schleife einmalig ausgeführt, hier wird der Wert der Variable VAR mit Null initialisiert (Startwert).
Innerhalb der Schleife wird nun getestet, ob die zweite Bedingung wahr oder falsch ist.
Bei false wird die Schleife sofort verlassen, damit wird also die Endbedingung festgelegt.
Bei true wird der Anweisungsblock zwischen do und done ausgeführt und danach die dritte Test-Anweisung ausgeführt (VAR wird um eins erhöht) und somit die Schrittweite definiert.
Nun wird zum Schleifenanfang zurückgesprungen und wieder Test-Anweisung zwei ausgeführt.
Mit dem Befehl continue kann innerhalb des do...done-Blocks vorzeitig ein neuer Schleifendurchlauf veranlasst werden, als Anzahl ist nur der Wert 1 möglich, also kann nur der unmittelbar nächste Schleifendurchlauf angesprungen werden.
Mit dem Befehl break wird die Schleife vorzeitig verlassen.
Das selbe erreicht man, wenn der Wert der Zählvariable so gesetzt wird, dass er zu einem Abbruch der Schleife führt.
Das ist z.B. sinnvoller als break, wenn mehrere Schleifenbefehle geschachtelt wurden.
Die Methode break [Zahl] ist oft unübersichtlicher.
Die Zählvariable ist auch außerhalb der Schleife definiert.
Sie kann schon vor dem Schleifenstart benutzt und berechnet werden, sodass das Script z.B. den Startwert schon vor dem Schleifenstart festlegen kann.
Nach dem Schleifendurchlauf kann mit dem Inhalt der Zählvariable z.B. getestet werden, ob die Schleife vollständig durchlaufen oder mit break vorzeitig beendet wurde.
Auch dazu ein einfaches Beispiel:
#!/bin/bash VAR=4 for ((VAR>0;VAR<11;VAR=VAR+2)); do echo $VAR done echo VAR=$VAR |
Und die Bildschirmausgabe sieht dann so aus:
bash-2.05b$ ./test 4 6 8 10 VAR=12 bash-2.05b$ |
Die Schleife startet also mit dem Wert 4, der vorher festgelegt wurde.
Die erste Test-Anweisung (Bedingung) ist im Prinzip ein NOP, ich habe eine Bedingung verwendet, die den Inhalt der Variablen VAR nicht beeinflusst. Der Wahrheitsgehalt dieser Testbedingung ist ohne Bedeutung.
Als Sprungweite habe ich 2 festgelegt.
Am Schleifenende wird der Inhalt der Variable VAR ausgegeben und es ist zu erkennen, dass die Test-Anweisung 3 zum Schluss wirklich ausgeführt wird und zum Schleifenabbruch führt.
Zum Abschluss noch ein Scriptbeispiel.
Es soll mehrfach getestet werden, ob ein bestimmter Webserver erreichbar ist und wenn ja automatisch ein Browser gestartet werden.
Die Erreichbarkeit der Hosts wird mit ping getestet und je nach exit-status entweder der Browser gestartet oder weiter gewartet.
Nach einer bestimmten Zeit beendet sich das Script.
Es wird eine Schleife mehrfach durchlaufen und dabei mit ping ein Packet (-c 1) abgeschickt und maximal 5Sekunden (-t 5) gewartet.
Die Ausgaben des Ping-Kommandos werden komplett nach /dev/null geschickt, da sie nur strören würden.
Danach wird mit einer case-Mehrfachentscheidung der exit-status ausgewertet.
Bei 0 ist der Host erreichbar, der Browser wird gestartet und die for-schleife mit break verlassen.
Bei 1 hat der Host nicht geantwortet und die Schleife wird nochmals durchlaufen, vielleicht meldet er sich ja noch.
Bei höheren exit-codes gibt es systematische Fehler, die die Erreichbarkeit des Hosts unmöglich machen und deshalb wird die Schleife verlassen.
Ein solches Script könnte z.B. in den Autostartordner des KDE gelegt werden und damit wird immer nach der Anmeldung der Browser automatisch gestartet, wenn ein Internetkontakt besteht.
#!/bin/bash for (( VAR=0; VAR<4; VAR=VAR+1)) do ping -c1 -t5 www.linux-web.de &> /dev/null case $? in 0) nohup dillo http://www.linux-web.de & break ;; 1) echo "Der Host antwortet nicht!" ;; 2) echo "Es existiert keine Route zu dem Host" VAR=5 ;; *) echo "Der Host ist nicht erreichbar!" VAR=6 ;; esac done echo VAR=$VAR |
Den vorzeitigen Schleifenabbruch habe ich unterschiedlich gestaltet.
Einmal mit break und zwei Mal mit der Zuweisung in die Variable VAR, die zum Schleifenabbruch führt.
Damit kann außerhalb der Schleife leicht getestet werden, warum sie beendet wurde.
Bei VAR=0,1,2,3 wurde der Host erreicht, bei VAR=4 hat alles warten nichts genützt und bei VAR=5,6 gibt es Fehler im System, die es unmöglich machen den Host zu erreichen.
Das letzte Beispiel ist mal eine arithmetische Berechnung.
Das Script wurzel berechnet den ganzzahligen Näherungswert der Wurzel einer positiven Integerzahl nach dem Heron-Verfahren.
Der Quellcode des Scripts sieht wie folgt aus:
#!/bin/bash var="$1" y=0 z=0 for ((x=var/2;x*x!=var;x=(x+var/x)/2)); do if [ $x -eq $y -o $x -eq $z ]; then break else z=$y y=$x fi done echo $x |
Die Zahl aus der die Quadratwurzel gezogen werden soll, wird als Parameter übergeben. Für die Quadratwurzel von 111000000000000000 sieht das dann so aus:
bash-3.1# ./wurzel 111000000000000000 333166624 bash-3.1# |
In der for-Schleife wird so lange durchlaufen, wie für x nicht gilt x2=var, die Wurzel also noch nicht gefunden wurde. Da viele Wurzeln nicht ganzzahlig darstellbar sind, muss noch eine weitere Abbruchbedingung gefunden werden, wenn der Wert x bereits nahe genug an dem Wert der Wurzel liegt. Dazu werden die zwei zuletzt berechneten Werte des Algorithmus in den Variablen y und z abgelegt und getestet, ob x einem der Werte entspricht. Wenn ja, hat der Algorithmus einen "nahen" ganzzahligen Wert gefunden (selbes Ergebnis oder alterierend zwischen zwei benachbarten Integerzahlen) und bricht ab. Da das Script nur Demonstrationszwecken dient, wurde nicht getestet, ob der Parameter wirklich größer als Null ist.
Die while-Schleife erlaubt die bedingte Ausführung einer Programmschleife. While erwartet dazu ein Kommando und wenn dieses mit dem exit-code 0 (true) beendet wird, dann wird der Schleifenkörper durchlaufen. Liefert das Kommando nicht den exit-code 0, so wird die Schleife verlassen. Hier ein einfaches Zählschleifenbeispiel:
#!/bin/bash VAR=10 while [ $VAR -gt 3 ] do echo "VAR ist ${VAR}" let "VAR=VAR-1" done echo "Schluss bei VAR=${VAR}" |
bash-2.05b$ ./test VAR ist 10 VAR ist 9 VAR ist 8 VAR ist 7 VAR ist 6 VAR ist 5 VAR ist 4 Schluss bei VAR=3 bash-2.05b$ |
Solange der Ausdruck [ $VAR -gt 3 ] ein true liefert, der Wert der Variable VAR also größer als 3 ist, wird die Schleife ausgeführt.
Beim Wert 3 liefert [ $VAR -gt 3 ] ein false und die Schleife wird verlassen.
Und nun noch einmal zu dem Beispiel aus der for-Schleife.
Aus dem aktuellen Verzeichnis sollen alle Dateinamen ausgelesen werden und für jede Datei der Befehl file ausgeführt werden.
Mit der for-Schleife gab es nun Probleme, wenn die Dateinamen Leerzeichen enthalten, da das Leerzeichen auch Feldseparator ist.
Deshalb nun ein Beispiel mit der while-Schleife.
#!/bin/bash while read $VAR do file $VAR done < <( ls ) |
Die while-Schleife wird solange ausgeführt, wie read Daten von stdin lesen kann.
read liest die Daten zeilenweise, also durch Zeilenschaltungen \n getrennt ein und legt den eingelesenen String in der Variable VAR ab.
Wenn read nun also einen String lesen konnte, dann wird file mit diesem String aufgerufen.
Damit read nun die Dateinamen von stdin lesen kann, wird eine Datenstromumleitung benutzt.
Das Kommando ls wird in einer Subshell ausgeführt und die Ausgabe nach stdin der while-Schleife umgeleitet.
Damit erreichen die Ausgaben des ls-Kommandos den Standard-Eingabekanal der while-Schleife und werden von read zeilenweise eingelesen.
Als Separator wird dadurch die Zeilenschaltung nach jedem Dateinamen in der Ausgabe von ls benutzt und Leerzeichen in den Dateinamen machen keine Probleme.
Möglich wäre auch folgende Lösung
#!/bin/bash ls | while read $VAR do file "$VAR" done |
Die until-Schleife funktioniert genauso wie die while-Schleife, nur wird der Schleifenkörper solange ausgeführt, wie die Bedingung ein false liefert.