Ausgaben mit while, until und for Schleifen in Bash umsetzen

Autor / Redakteur: Mirco Lang / Stephan Augsten

Programmieren macht am meisten Spaß, wenn man Zeit spart. Und das erreicht man über Automatisierung und Wiederholungen. Mit Schleifen lassen sich große Datenmengen mit nur drei, vier Zeilen Code verarbeiten. Das ist auch für Nicht-Programmierer äußerst nützlich.

Anbieter zum Thema

Wer nicht in einer Endlosschleife landen will, sollte sich auch in der Bash um eine Abbruchbedingung bemühen.
Wer nicht in einer Endlosschleife landen will, sollte sich auch in der Bash um eine Abbruchbedingung bemühen.
(Bild gemeinfrei: Charlotte Coneybeer / Unsplash)

Scripting-Fähigkeiten stehen jedem gut – selbst fleißigen Office-Anwendern, die sich eher mit Excel-Tabellen denn mit Variablen herumschlagen. Und das im eigenen Interesse: Mit einer geschickt eingesetzten Schleife lässt sich manch ein Arbeitstag bereits mittags beenden, ohne dass Arbeit liegen bleiben würde.

Vieles lässt sich sogar ohne Skriptdateien mit schlichten Einzeilern realisieren. Ein Beispiel gefällig? Sie haben eine Liste mit Namen nach dem Muster „Frau Antonia Anton“ und wollen für jede Person einen Brief mit der richtigen Anrede (Herr/Frau) haben. Die wohl schnellste Variante:

grep Frau namensliste.txt | while read -r f; do echo "Sehr geehrte $f" > $f.txt; done

Anschließend haben Sie diverse Dateien nach dem Muster „Frau Antonia Anton.txt“. Natürlich ist das stark vereinfacht, aber grundsätzlich haben Sie mit dieser einen Zeile bereits eine Serienbrieffunktion – oder zumindest den Anfang dafür.

Der Office-Alltag ist voll von Situationen, in denen minimale Scripting-Kenntnisse Zeit sparen können. Für Programmiereinsteiger sind Schleifen sowieso für einige Zeit das A und O. Grund genug für eine kleine Einführung in while, for und until. Als Grundlage dient hier natürlich Shell-Code für die Bash.

Immer im Kreis: while

Die while-Schleife ist konzeptionell und auch bezüglich der Syntax erfreulich simpel: Eine Anweisung wird so lange ausgeführt, wie eine Bedingung erfüllt wird. So kann man zum Beispiel ganz fix Endlosschleifen starten – sinnvolle wie gemeine. Das einfachste denkbare Beispiel: Es soll für immer und ewig „Hallo Welt“ im Terminal ausgegeben werden:

while true; do echo Hallo Welt; done

Die Grundstruktur ist also ganz simpel:

while BEDINGUNG; do ANWEISUNG; done

Statt „while true“ könnte man auch etwas wie „while [ 1 -lt 2 ]“ verwenden – tendenziell wird 1 immer weniger als 2 sein und die Anweisung folglich ewig laufen. Und hier ist auch gleich eine Besonderheit bei der Syntax: Die Leerzeichen an Anfang und Ende innerhalb der Klammer müssen sein – [1 -lt 2] führt zu einem Syntax-Error.

Damit die Schleife nicht ewig läuft, kann zum Beispiel in der Bedingung eine Variable genutzt werden, die dann in der Anweisung iteriert wird:

i=1
while [ $i -lt 10 ]; do echo Hallo Welt: $i && i=$[$i+1]; done

So lange die Variable „i“ kleiner als (-lt) 10 ist, wird „Hallo Welt: 1“ bis „Hallo Welt: 9“ ausgegeben und „i“ inkrementiert (d.h. um 1 hochgezählt).

Ein typisches Beispiel aus dem Alltag ist das bereits oben erwähnte zeilenweise Auslesen von Dateien. Gegeben sei eine Datei „namensliste.txt“ nach folgendem Muster:

Frau Antonia Anton
Frau Berta Berfeld
Herr Carl Caesar

Im einfachsten Fall sieht der Befehl wie folgt aus:

while read zeile; do echo $zeile; done < namensliste.txt

Die Ausgabe wäre die gleiche wie bei „cat namensliste.txt“, also einfach der Inhalt wie in der Textdatei zu finden. Dies ist im Grunde der Aufruf aus dem Eingangsbeispiel (ohne grep-Filter und erstellte Dateien). Hier wird schlicht jede Zeile der Datei „namensliste.txt“ ausgelesen, in der Variablen „zeile“ gespeichert und ausgegeben.

Der Vollständigkeit halber: Es gibt auch noch die until-Schleife, die in der freien Wildbahn allerdings eher selten anzutreffen ist. Die Syntax entspricht exakt der der while-Schleife, ausgeführt wird aber nicht, so lange eine Bedingung zutrifft, sondern bis sie zutrifft – das zweite Beispiel lautet daher:

i=1
until [ $i -gt 10 ]; do echo Hallo Welt: $i && i=$[$i+1]; done

Hier ist lediglich aus „kleiner gleich“ (-lt) ein „größer gleich“ (-gt) geworden – das Ergebnis ist identisch. Die while-Schleife führt also aus, bis die Bedingung „false“ wird, until führt aus bis die Bedinung „true“ wird.

Von A bis Z: for

Die for-Schleife läuft hingegen für eine festgelegte Anzahl an Elementen. Das Prinzip ist: „ for BEREICH; do ANWEISUNG; done“. Der Bereich gibt dabei die Anzahl der Wiederholungen vor, sei es über eine Range (zum Beispiel 1-10) oder eine Menge an zu verarbeitenden Elementen (etwa Zeilen).

Wie schon bei while, soll für den Anfang eine Endlosschleife den Text „Hallo Welt“ ausgeben:

for (( ; ; )); do echo Hallo Welt; done

Das „for (( ; ; ))“ ist leider deutlich weniger intuitiv verständlich als „while true“, meint aber dasselbe: In doppelten runden Klammern können in Bash arithmetische Berechnungen durch geführt werden. In diesem Fall stehen die drei ausgelassenen Werte für Anfang, Ende und Iteration. Was das heißt wird deutlich, wenn das zweite Beispiel aus dem while-Kapitel umgesetzt wird, die Ausgabe von „Hallo Welt: 1“ bis „Hallo Welt: 9“:

for ((i=1;i<=9;i++)); do echo Hallo Welt: $i; done

Die Bedingungen der for-Schleife ist hier also schlicht der Bereich von 1 bis 9, wobei die Variable „i“ jeweils um 1 hochgezählt wird. Eine Alternative ist die direkte Angabe eines Bereichs in geschweiften Klammern oder gar ganz manuell:

for i in {1..9}; do echo Hallo Welt: $i; done

bzw.

for i in 1 2 3 4 5 6 7 8 9; do echo Hallo Welt: $i; done

Auch das dritte while-Beispiel lässt sich mit for umsetzen, also das Auslesen und Ausgeben einer Datei:

for i in $(cat namensliste.txt); do echo $i; done

Hier wird die Anzahl der Wiederholungen durch die Elemente in der Datei „namensliste.txt“ bestimmt – aber Vorsicht: Diese Anweisung gibt nicht Zeile für Zeile aus, sondern Wort für Wort! Als Begrenzer für die einzelnen Felder verwendet for den Bash-Standard, also Whitespaces (Leerzeichen, Tab, Umbruch). In der while-Version wurde das Tool „read“ eingesetzt, das hingegen Zeilen einliest.

Sie können der Bash aber befehlen, nur Umbrüche als Begrenzer zu nutzen:

IFS=$'\n'

IFS steht für Internal Field Seperator und wird hier auf Newline (/n) gesetzt. Anschließend liefert die for-Schleife exakt die gleiche Ausgabe wie die while-Schleife. Nicht vergessen: IFS über „unset IFS“ wieder zurücksetzen.

for oder while?

In den obigen Beispielen haben for- und while-Schleifen jeweils dieselben Ergebnisse produziert. Da stellt sich schnell die Frage, welche denn nun besser ist. Erfreulicherweise gibt es darauf weder eine pauschale, noch eine technisch komplizierte Antwort – sondern eine ganz einfache: Letztlich ist es Geschmackssache, beide Varianten erledigen dieselbe Aufgabe.

Rein inhaltlich bietet sich aber jeweils eine Vorgehensweise an. Ist die Anzahl der Durchläufe bekannt wird meist zu for gegriffen, andernfalls zu while. Ein Vorteil der for-Schleife ist allerdings nicht zu unterschätzen: Das Risiko, versehentlich Endlosschleifen zu generieren, ist geringer – eine while-Bedingung kann schließlich auch mal ausbleiben.

Zudem sollte jeweils die Schleife gewählt werden, die für Dritte besser verständlich ist. Das Initiieren einer Endlosschleife mal als Beispiel: Die Syntax „for (( ; ; ))“ muss man schon wirklich kennen, die Formulierung „while true“ versteht auch ein Laie.

Als zweites Beispiel hier nochmal die Iteration von for und while im direkten Vergleich:

while [ $i -lt 10 ]; do echo Hallo Welt: $i && i=$[$i+1]; donefor ((i=1;i<=9;i++)); do echo Hallo Welt: $i; done

Natürlich wäre „for“ hier die logischere Wahl, da die Anzahl der Durchläufe bekannt ist. Vor allem aber ist das Statement deutlich besser lesbar, da die ganze „Mathematik“ direkt am Anfang stattfindet.

Kleine Unterbrechung

Zum Schluss sehen Sie noch eine Gemeinsamkeit beider Schleifen, für die allerdings if-Abfragen bekannt sein müssen: break und continue. Über break lassen sich Schleifen komplett unterbrechen, sobald eine Abbruchsbedingung auftritt. Mit continue wird lediglich die aktuelle Iteration bei einer Abbruchsbedingung beendet und die Schleife fährt mit dem nächsten Eintrag fort.

Zunächst ein simples break-Beispiel für das bereits bekannte for-Statement – allerdings lässt sich das nicht mehr hübsch als Einzeiler zeigen:

for ((i=1;i<=9;i++))
do
   if [ $i -eq 5 ]
   then
      echo Abbruch
      break
   fi
   echo Hallo Welt: $i
done

Hier wurde lediglich die if-Abfrage eingebaut: Falls „i“ den Wert 5 hat, wird im Terminal “Abbruch” ausgegeben und die Schleife per break beendet. Damit ließe sich etwa beim Kopieren größerer Dateimengen ein Abbruch erzielen, falls kein Speicherplatz mehr vorhanden ist.

Und nun noch ein schnelles continue-Beispiel: Wenn in der bereits bekannten „namensliste.txt“ eine Zeile mit „Berta Berfeld“ beinhaltet, soll die Schleife diese Zeile überspringen – vielleicht, weil Sie das selbst sind. Hier nochmal die Ausgangslage:

Frau Antonia Anton
Frau Berta Berfeld
Herr Carl Caesar

Und die Schleife:

while read zeile
do
   if [[ $zeile == *"Berta Berfeld"* ]]
   then
      continue
   fi
   echo $zeile
done < namen

Das Resultat: Der Inhalt der Datei wie oben zu sehen, aber ohne die Zeile mit Frau Berfeld. Mit diesem einfachen Werkzeug ließen sich nun komplexe Regeln umsetzen, um allerlei Sonderfälle abzuhandeln.

Statt netter Worte zum Abschluss ein until-Beispiel als kleine Spielerei – für alle, die um 18.00 Uhr Feierabend machen:

until [ $(date +"%H") -eq 18 ]; do sleep 1; done; echo Feierabend!

(ID:46160506)