Schleifen in der Shell

Aus LinuxUser 06/2015

Schleifen in der Shell

© alhovik, 123RF

Kreislauf

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.

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

Abbildung 2: Der Ablauf des Skripts aus Listing 3.

Abbildung 2: Der Ablauf des Skripts aus Listing 3.

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

Abbildung 3: Der Ablauf des Shellskripts aus Listing 4.

Abbildung 3: Der Ablauf des Shellskripts aus Listing 4.

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.

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

Abbildung 5: Der Ablauf des Skripts aus Listing 8.

Abbildung 5: Der Ablauf des Skripts aus Listing 8.

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

Abbildung 6: Der Ablauf des Skripts aus Listing 9.

Abbildung 6: Der Ablauf des Skripts aus Listing 9.

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

Abbildung 7: Das mit Yad erstellte grafische Menü aus Listing 13.

Abbildung 7: Das mit Yad erstellte grafische Menü aus Listing 13.

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.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDF
LinuxUser 06/2015 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben