Schleifen ermöglichen das mehrmalige Abarbeiten von Anweisungen. Dabei kann das begrenzende Ereignis sowohl außerhalb als auch innerhalb der Schleife liegen.
Mit Schleifen lassen Sie einen Teil eines Shellskripts solange immer wieder ausführen, bis eine beendende Bedingung eintritt – etwa für Menüsteuerungen, aber auch für die “richtige” Datenverarbeitung. Ohne Schleifen müssten Sie sonst das Skript immer wieder sich selbst starten lassen.
Eine Schleife leiten Sie mit einem der Schlüsselworte for, while oder until ein (Schleifenkopf). Dann folgt die Anweisung do, gefolgt von den Anweisungen, die innerhalb der Schleife ausgeführt werden sollen (Schleifenkörper). Ein abschließendes done beendet die Schleife.
Im Schleifenkörper bringen Sie neben den Arbeitsanweisungen auch steuernde Elemente für den Schleifenablauf unter. Das Kommando break beendet die Ausführung der Schleife und setzt das Skript mit den folgenden Anweisungen fort. Der Befehl continue bricht den aktuellen Durchlauf ab und startet einen neuen. Bei beiden Kommandos können Sie zusätzlich die Zahl der Ebenen angeben, die Sie bei verschachtelten Schleifen zurückspringen möchten. Soll das Skript die Ausführung eines anderen Programms abwarten, geschieht dies mittels wait; mit exit beenden Sie das Shellskript sofort und komplett.
For-Schleifen
Für die For-Schleife benötigen Sie “Datenfutter”. Jeder Durchlauf erfolgt für genau einen Parameter, den die Shell einer Liste entnimmt, aus einem Inkrement berechnet oder aus einer anderen Datenquelle gewinnt. Grundsätzlich läuft die for-Schleife solange, wie sie entsprechende Daten vorfindet. Innerhalb des Schleifenkörpers können Sie gegebenenfalls mittels Ein- oder Mehrfachverzweigung (if, case) ein vorzeitiges Ende der Ausführung bestimmen.
Im einfachsten Fall geben Sie Parameter für den Schleifendurchlauf vor (Listing 1). Dabei kann es sich etwa um Dateien und Verzeichnisse, Benutzer, Anwendungen, Rechnernamen, IP-Adressen oder Ähnliches handeln. Listing 2 zeigt ein Minimalbeispiel dazu, in Abbildung 1 sehen Sie das Ergebnis.
Listing 1
for Variable in Parameter1, Parameter2 ...; do [... Anweisungen ...] done
Listing 2
#!/bin/sh for k in a b c; do echo $k done

Abbildung 1: Eine For-Schleife mit festen Parametern.
Mittels der eingebauten Rechenkünste von Shells wie der Bash oder der Zsh können Sie ein Zählwerk schaffen, mit dem sich der Wert einer Variablen gleichmäßig erhöhen oder vermindern lässt. Nicht alle Shells stellen diese Funktion bereit, weswegen es hier sinnvoll ist, die gewünschte Shell innerhalb eines Shebang explizit anzugeben, beispielsweise mit #!/bin/bash statt des generischen #!/bin/sh, das die Standard-Systemshell aufruft.
Der Aufbau des rechnenden Schleifenkopfs beginnt mit der Definition des Ausgangswerts der Variablen. Es folgen eine Bedingung (Minimal/Maximalwert der Variablen) und die Anweisung der Erhöhung (Inkrement) respektive der Verminderung (Dekrement) des Werts. Einige Beispiele dazu finden Sie in Listing 3, die zugehörige Ausgabe zeigt Abbildung 2.
In den ersten beiden Schleifen beträgt der Inkrement- respektive Dekrementwert 1; die dritte Schleife zeigt die Berechnungsmethode für einen anderen, festen Erhöhungswert. In der letzten Schleife erhalten Sie durch die Multiplikation der Variable mit sich selbst ein quadratisches Inkrement. In diesem Fall darf der Startwert der Variable nicht 0 oder 1 betragen, weil Sie damit sonst eine Endlosschleife schaffen.
Listing 3
#!/bin/bash echo "zunehmend" for ((k=1; k<5; k++)); do echo $k done echo "abnehmend" for ((k=4; k>0; k--)); do echo $k done echo "beliebiges Inkrement" for (( k=10; k<50; k=k+10 )); do echo $k done echo "quadratisches Inkrement" for (( k=2; k<300; k=k*k )); do echo $k done
Sie können die für den Lauf der For-Schleife notwendigen Parameter auch durch andere Anwendungen bereitstellen lassen. Der Aufruf erfolgt dann in folgender Form:
for Variable in $(Anwendung)
Hier dürfen Sie fast alles als Datenquelle nutzen, was eindeutige, durch Leerzeichen abgesetzte Werte produziert. Das Skript aus Listing 4 (den Ablauf zeigt Abbildung 3) bestimmt zunächst mithilfe von file den Typ aller mittels ls -1 im aktuellen Verzeichnis gefundenen Objekte. Das fold-Kommando dient dabei der Ausgabeformatierung.
Als zweites Beispiel sehen Sie das Auslesen einer Textdatei, die jeweils einen Namen pro Zeile aufweist. Je Durchlauf belegt das Skript die Variable k mit einem dieser Namen. Am Schluss liest es aus einer Datenbanktabelle die als Schlüssel fungierende orgnr für alle Zahlen kleiner 500 aus. Die Datenbanktabelle weist nur drei Einträge auf, bei den Nummern 1 und 3 ist die gestellte Bedingung erfüllt. Das Beispiel begnügt sich mit der reinen Ausgabe des Zahlenwerts. In der Praxis setzen Sie hier eher umfangreiche Datenbankoperationen oder weitere Verarbeitungsschritte ein.
Listing 4
#!/bin/sh echo "Bestimmen des Dateityps in einer For-Schleife" for k in $(ls -1); do echo "---------------------------------------------" file $k | fold -sbw 50 done echo "---------------------------------------------" echo -e "\nVerwendung einer Textdatei als Werteliste\n" for k in $(cat eingabe.txt); do echo $k done echo -e "\nDatenbankabfrage (Postgres) als Datenquelle\n" for k in $(psql -t -c "select orgnr from versuch where zahl < 500;"); do echo $k done
While- und Until-Schleifen
Die While-Schleife läuft durch, solange die im Kopf angegebene Bedingung erfüllt wird. Unabhängig von der im Schleifenkopf gestellten Bedingung greifen Sie auch hier gegebenenfalls im Schleifenkörper mit continue, break oder exit steuernd ein.
Dieser Schleifentyp führt im Gegensatz zur For-Schleife keine Daten zu. Ein Minimalbeispiel zeigen Listing 5 und die zugehörige Abbildung 4. Die Schleife läuft solange, bis die Variable $k den Wert 5 erreicht. Anhand der kleinen Demonstration sehen Sie auch, dass Sie hier selbst für die Wertzuweisung an inkrementierende Variablen sorgen müssen.
Listing 5
#!/bin/bash
k=0
while [ $k -lt 5 ]; do
k=$(echo $k + 1 | bc)
for (( j=0; j<$k; j++ )); do
echo -n "."
done
echo $k
done

Abbildung 4: Der Ablauf der While-Schleife aus Listing 5.
Die Until-Schleife unterscheidet sich vom While-Gegenstück nur dadurch, dass sie solange läuft, bis die im Kopf angegebene Bedingung erfüllt ist (Listing 6).
Listing 6
#!/bin/sh k=0 until [ $k -eq 5 ]; do k=$(echo $k + 1 | bc) echo $k done
Endlosschleifen
Benötigen Sie von vornherein eine Endlosschleife, die aus dem Schleifenkörper heraus gesteuert wird, setzen Sie mit while die Bedingung true ein, mit until als Gegenstück false. Die beiden Hilfsprogramme ersetzen die einleitende Bedingung und geben nur einen Exitcode aus: true 0 und false 1.
Endlosschleifen setzen Sie beispielsweise in Menüsteuerungen oder für Überwachungsaufgaben ein. Das Listing 7 zeigt Ihnen den Einsatz in beiden Schleifentypen. Im Vorgriff zu den folgenden Punkten der Schleifensteuerung finden Sie auch bereits break und exit im Einsatz.
Listing 7
$$nonumber
#!/bin/sh
echo "While-Schleife"
k=0
while true; do
k=$(echo $k + 1 | bc)
echo $k
# Schleifendurchlauf
# mit 'break' beenden
if [ $k -eq 5 ]; then
break
fi
done
echo "Until-Schleife"
k=0
until false; do
k=$(echo $k + 1 | bc)
echo $k
# Schleife und Programm
# beenden mit 'exit'
if [ $k -eq 5 ]; then
exit
fi
done
Stop and go
Eine Schleife brechen Sie bei Bedarf mittels break ab, wie das While-Beispiel in Listing 7 demonstriert. In verschachtelten Schleifen dürfen Sie mehrere Ebenen überspringen, indem Sie zusätzlich die Ebenenzahl angeben.
Um nicht nur die Schleifenausführung abzubrechen, sondern das gesamte Skript, greifen Sie zu exit, wie im Until-Beispiel aus Listing 7. Optional geben Sie dem Befehl noch einen numerischen Exit-Code mit auf den Weg (exit 3), den Sie dann auf der Kommandozeile auswerten können. Zur Erinnerung: Den Exit-Code fragen Sie unmittelbar nach dem Kommando durch echo $? ab.
Der Befehl continue springt ohne Abarbeiten eventueller weiterer Befehle ans Schleifenende und startet einen neuen Schleifendurchlauf. Innerhalb verschachtelter Schleifen dürfen Sie wiederum durch mehrere Ebenen wechseln, indem Sie die Zahl der Ebenensprünge angeben. Die Zählung (eigene Schleife) beginnt dabei mit 1. Ohne diese Angabe wirkt continue stets in der aktuellen Schleife.
Schleifenkörper
Das Skript aus Listing 8 soll dreimal innerhalb verschachtelter Schleifen zählen. Eine Bedingung in der Hauptschleife löst per exit-Befehl das Skriptende aus. In der untersten Ebene unterbricht ein continue den Schleifendurchlauf und springt zur “Hauptschleife”. Diese läuft anschließend weiter und zählt dann bis zur Abbruchbedingung weiter (Abbildung 5).
Listing 8
#!/bin/sh
a=0
b=0
c=0
while true; do
if [ $a -eq 8 ]; then
echo "Schluss!"
exit
fi
a=$(echo $a + 1 | bc)
echo "Schleife 1: $a"
if [ $a -eq 5 ]; then
while true; do
b=$(echo $b + 1 | bc)
echo -e "\tSchleife 2: $b"
if [ $b -eq 5 ]; then
while true; do
c=$(echo $c + 1 | bc)
echo -e "\t\tSchleife 3: $c"
if [ $c -eq 5 ]; then
continue 3
fi
done
fi
done
fi
done
Warteschleifen
Mit wait warten Sie die Ausführung eines oder mehrerer im Hintergrund gestarteter Prozesse ab. Dieser Befehl gehört zwar nicht explizit zur Schleifensteuerung, lässt sich aber auch hier sinnvoll einsetzen. Ohne dieses Kommando würde eine Schleife sofort nach dem Aufruf eines im Hintergrund gestarteten Prozesses weiterlaufen und gegebenenfalls falsche Ergebnisse liefern. Mittels wait halten Sie den Durchlauf einfach an, bis alle notwendigen Jobs erledigt sind.
Unmittelbar nach dem Start eines Programms erhalten Sie auf der Shell dessen Prozessnummer durch die Abfrage der Variablen $!. Wollen Sie auf die Ausführung mehrerer Prozesse warten, legen Sie jede Prozess-ID in eine eigene Variable. Diese PIDs übergeben Sie wait, das die Ausführung der Schleife solange anhält, bis alle PIDs aus der Prozesstabelle entfernt sind.
In der Praxis handelt es sich bei den Hintergrundprozessen oft um Datenbank-Daemons, Webserver oder Ähnliches. Das Beispiel aus Listing 9 verwendet stattdessen zwei kleine Shellskripte (Listing 10 und Listing 11), die jeweils lediglich eine Variable belegen und mittels sleep verschieden lange warten, damit Sie den Ablauf überhaupt beobachten können. Sobald die innerhalb der Schleifen gebildete Summe den Wert 30 übersteigt, terminiert die Schleife mittels break; Abbildung 6 zeigt den Ablauf.
Listing 9
#!/bin/bash
a=0
b=0
c=0
d=0
while true; do
echo -en "\nStart der Skripte: "; date +%H:%M:%S
./a.sh &
PID1=$!
./b.sh &
PID2=$!
echo "Warten auf die Prozesse $PID1 und $PID2."
wait $PID1 $PID2
echo -en "\nSkripte fertig: "; date +%H:%M:%S
# Belegen und Berechnen der Variablen
a=$(cat a.txt)
b=$(cat b.txt)
c=$(echo $a + $b | bc)
d=$(echo $d + $c | bc)
echo "Zwischensumme: $d"
# Abbruch wenn d > 30
if [ $d -gt 30 ]; then
break
fi
done
echo "Summe: $d"
Listing 10
#!/bin/bash
#a.sh
n=8
for (( i=0; i<${n}; i++ )); do
echo -n "a "
sleep 1
done
echo $n > a.txt
Listing 11
#!/bin/bash
#b.sh
n=3
for (( i=0; i<${n}; i++ )); do
echo -n "b "
sleep 1
done
echo $n > b.txt
Menüs
Ein Textmenü für Terminalsitzungen zeigt das Beispiel in Listing 12. Darin läuft eine While-Endlosschleife, die im Anschluss an eine Auswahl mit break terminiert. Daher können Sie weitere Befehle nach der Schleife im Skript abarbeiten lassen. Das Skript lässt sich außerdem als eine Art Baukasten benutzen und nach Ihren Wünschen ändern sowie erweitern.
Listing 12
#!/bin/sh
while true; do
clear
echo " 1 Editor Nano"
echo " 2 Htop"
echo " 3 Root-Anmeldung"
echo " 4 Plattenbelegung /home"
echo " 9 ENDE"
echo " ---------------"
echo -n " "
read pnr
if [ $pnr -eq 1 ]; then
nano
elif [ $pnr -eq 2 ]; then
htop
elif [ $pnr -eq 3 ]; then
su -
elif [ $pnr -eq 4 ]; then
df -h /home; sleep 3
elif [ $pnr -eq 9 ]; then
break
fi
done
echo "Programmende angefordert"
Mit Hilfsprogrammen wie yad ergänzen Sie Shellprogramme um eine Bedienung per GUI. Die Fähigkeiten gehen hier weit über eine Menüsteuerung hinaus und reichen bis hin zu komfortablen Masken für Datenbankanwendungen. Das Beispiel aus Listing 13 verdeutlicht die grundlegende Vorgehensweise, die Abbildung 7 zeigt das kleine GUI-Skript im Betrieb.
Das Menü residiert in einer Endlosschleife, eine Variable nimmt die einzelnen Menüpunkte in der Form Punkt1!Punkt2... auf. Die Variable aktion nimmt das Ergebnis der Auswahl in Yad auf, der Inhalt lautet bei der Auswahl des ersten Punktes Punkt1|. Mithilfe des Befehls cut befreit das Skript den Programmnamen vom anhängenden Trennzeichen und startet die Anwendung via exec im Hintergrund. So können Sie weiter auf das Menü zugreifen und andere Programme starten. Falls Sie das nicht möchten, lassen Sie am Ende der exec-Zeile das Zeichen & weg.
Im Yad-Formular (--form) erfolgt die Auswahl in der Anweisung --field, genauer spezifiziert durch die Option CB (für “Checkbox”). Für das Beenden der Endlosschleife werten Sie den Exit-Code entsprechend aus, den Sie dazu den entsprechend vorbelegten --button-Anweisungen entnehmen. Möchten Sie das Skript auch per [Esc] beenden können, müssen Sie neben dem Exit-Code 1 auch noch die 252 beachten.
Das Beispiel verwendet als Aktionen das Spiel Xbomb, den Taschenrechner Xcalc, den Editor Xedit sowie das Terminalprogramm Xterm. Hier können Sie ganz nach Gusto auch weitere oder andere Programmaufrufe eintragen und auch die Überschriften nach Ihren Wünschen abändern. Falls Sie das Skript mittels Fernsitzung ausführen wollen, müssen Sie bei der Verbindungsaufnahme auch gleich für die notwendige Display-Umleitung sorgen (ssh -X ...).
Listing 13
#!/bin/bash
while true; do
# Menue-Inhalt, Feldtrenner="!", beliebig erweiterbar
aktionen="xbomb!xcalc!xedit!xterm"
aktion=$(yad --title="Programmstarter mit Yad" \
--text="Programm aus der Auswahl starten." \
--form --field="Aktion: ":CB $aktionen \
--button="Abbruch":1 --button="Starten":0)
# Exit-Code auswerten
ec=$(echo $?)
if [ $ec -eq 1 ]; then
# Skript-Ende, falls Schalter 'Abbruch' gedrueckt
exit
elif [ $ec -eq 252 ]; then
# Skript-Ende mit [Esc]
exit
fi
# Auslesen der gewählten Aktion
aktion=$(echo $aktion | cut -d \| -f1)
# Aufruf des gewählten Programms
exec $aktion &
done
Beispiel: Aktionen nach Datum/Zeit ohne cron/at auslösen
Nicht immer müssen Sie at oder cron nutzen, um eine Aktion nach Datum oder Zeit auszuführen – auch per Shellskript lässt sich ein Programm nach einer vorgegebenen Zeit beenden. Das Beispiel zeigt eine Lösung, die einen Webbrowser stets zur vollen nächsten Stunde diskussionsfrei beendet. Sie können das noch weiterspinnen, indem Sie stattdessen gleich die ganze Sitzung des Fräulein Tochters respektive des Herrn Sohns beenden oder sogar den Rechner ganz herunterfahren. Alternativ taugt der Lösungsansatz auch für ein kleines “Internet-Café” im Vereinsheim.
Listing 14 zeigt den Ablauf. Zuerst werden die Start- und Endzeit festgelegt. Der Einfachheit halber agiert das Shellskript ungerecht: Wer zur Minute 01 startet, bekommt mehr Zeit als der Benutzer, der erst später loslegt. Das Skript startet den Webbrowser (hier Iceweasel) und legt dessen Prozess-ID in einer Variablen ab. In der While-Schleife am Ende nimmt das Skript alle 59 Sekunden einen “Uhrenvergleich” vor. Stimmen angegebene Schlussstunde und aktuelle Stunde überein, beendet das Skript den Webbrowser-Prozess und anschließend sich selbst.
Listing 14
#!/bin/sh
# Stunde des Anwendungsstarts
startstunde=$(date +%H)
# Eine Stunde spaeter,
# Tagesueberlauf beruecksichtigt
if [ $startstunde -eq 23 ]; then
endestunde=0
else
endestunde=$(echo $startstunde + 1 | bc)
fi
# Programm starten ...
iceweasel &
# ... und PID auslesen
pid=$(echo $!)
# Kontrollschleife
while true; do
# Aktuelle Stunde
stunde=$(date +%H)
# Uebereinstimmung: Webbrowser beenden
if [ $stunde -eq $endestunde ]; then
kill -9 $pid
exit
fi
# 59 Sekunden warten
sleep 59
done
Der Autor
Harald Zisler beschäftigt sich seit den frühen 1990-ern mit FreeBSD und Linux. Zu Technik- und EDV-Themen verfasst er Zeitschriftenbeiträge und Bücher; frisch erschienen ist gerade beim Rheinwerk-Verlag die dritte Auflage von “Computer-Netzwerke”.
Glossar
-
Shebang
-
Die Zeichenkombination
#!am Anfang eines Skripts bewirkt, dass die enthaltenen Kommandos mit dem angegebenen Programm, oft einer bestimmten Shell oder auch den Perl- und Python-Interpretern, ausgeführt werden.










