Eine der absolut grundlegendsten Funktionen einer Programmiersprache sind neben If- und Else-Abfragen wohl Schleifen. Nur mit diesen ist es möglich eine Aktion so oft zu wiederholen bis eine bestimmte Bedingung zutrifft oder nicht mehr gegeben ist. In der PowerShell kann man die gängigen For-Loops, ForEach-Loops, (Do-)While und (Do-)Until-Schleifen verwenden. In Batch-Dateien gibt es eigentlich nur For-Schleifen und den Rest muss man sich selber bauen. In diesem Artikel zeige ich für PowerShell und Batch wie es funktioniert.
Grundsätzliches zu Schleifen bzw. Loops
Die Nutzung von Schleifen (Englisch: Loops) dürfte in jeder Programmiersprache ein elementares Steuerelement sein, das benötigt wird um Abläufe zu wiederholen bis ein gewünschtes Ergebnis erreicht wird. Außer bei einer meist eher unbeabsichtigt erstellten Endlosschleife sind Schleifen an eine zu erfüllende Bedingung geknüpft. Ist diese erreicht wird die Schleife wieder verlassen und der nachfolgende Programm- oder Skriptcode wird ausgeführt.
Kurzüberblick der Schleifen in PowerShell
Wer nur kurz eine Auffrischung braucht wie genau die Schleifen in PowerShell zu nutzen sind findet hier eine Übersicht:
While-Schleife in PowerShell
1 2 3 4 |
while ($true){ # Anweisungsblock write-host bla } |
Do-While-Schleife in PowerShell
1 2 3 4 |
do { # Anweisungsblock write-host bla } while ($true) |
Do-Until-Schleife in PowerShell
1 2 3 4 |
do { # Anweisungsblock write-host bla } until ($true) |
ForEach-Schleife in PowerShell
1 2 3 4 |
foreach ($var in $vars) { # Anweisungsblock write-host $var } |
For-Schleife in PowerShell
1 |
for ($i=1; $i -le 10; $i++) {$i,"`n"} |
Schleifen in Batch-Dateien bzw. der Kommandozeile
In Batch-Dateien ist vieles nicht vorgesehen gewesen und mit Bordmitteln auch nicht möglich. So gibt es keine standardmäßigen Möglichkeiten Schleifen mit beliebigen Abbruchbedingungen einzusetzen. Außer einer For-Schleife steht nichts zur Auswahl. Ich benötigte eine Schleife mit einer bestimmten maximalen Anzahl an Durchläufen. Diese sollte aber beim erwarteten Ergebnis auch schon früher abbrechen. Das lässt sich mit einer For-Schleife nicht realisieren. Denn man kann aus dieser nicht per goto-Befehl „ausbrechen“. Somit würde die Maximalanzahl an Durchläufen immer durchgeführt werden. Auch wenn das eigentliche Ziel schon beim ersten Versuch erreicht wurde. Mit PowerShell wäre das einfach gewesen. Das ist aber trotzdem kein Problem.
Glücklicherweise kann man sich auch in einer so rudimentären „Sprache“ wie bei Batch-Dateien die anderen Schleifenarten wie While– und Until-Schleifen einfach selber bauen. Zumindest solange man If-Abfragen und Sprungmarken mittels goto nutzen kann. Dies ist aus Performancegründen aber eigentlich zu unterlassen. Denn dieses Herumgespringe im Code erhöht wohl die CPU-Last. Lediglich der Komfort leidet natürlich und der Code ist unübersichtlicher als es in einer vernünftigen Programmiersprache der Fall wäre. Aber who cares?
In folgendem Beispiel ist eine Zählschleife mit einfachen Mitteln umgesetzt worden:
1 2 3 4 5 6 7 8 |
set /a x=1 :while if %x% leq 5 ( echo %x% set /a x+=1 goto :while ) :end-while |
Diese Schleife produziert die gleiche Ausgabe wie folgende For-Schleife:
1 |
for /L %%x IN (1 1 5) DO echo %%x |
Nämlich die einfache Ausgabe des je Durchlauf aktuellen Wertes der Zählvariable:
1 2 3 4 5 |
1 2 3 4 5 |
In PowerShell sähe das sehr einfach so aus:
1 2 3 4 |
do { $count = $count +1 write-host $count } until ($count-eq 5) |
… oder so als For-Schleife:
1 |
for ($i=1; $i -le 5; $i++) {$i,"`n"} |
Schleifen in der PowerShell
Da es sich bei der PowerShell um eine moderne Skriptsprache handelt, hat man es hier einfacher als in der alten Kommandozeile. Die meisten Schleifentypen stehen einem grundsätzlich von Hause aus zur Verfügung und müssen nicht durch eigene Konstruktionen nachgebaut werden. Das wäre auch gar nicht so einfach möglich, da in der PowerShell bewusst auf Sprungmarken bzw. den goto-Befehl, wie man ihn aus Batch-Dateien kennt, verzichtet worden ist. Hierdurch soll „Spaghetti“-Code eingedämmt werden. Dieser entsteht unweigerlich wenn man im Code wild hin- und herspringt.
While-Schleifen
Eine While-Schleife wiederholt die Anweisungen solange die Prüfung der Bedingung „True“ als Wert zurückgibt. Ist also direkt am Anfang die Bedingung „False“, wird die Schleife nicht ein einziges Mal durchlaufen. Es handelt sich hierbei um eine sogenannte „kopfgesteuerte“ Schleife, weil die Prüfung der Bedingung direkt am Anfang der Schleife stattfindet. Dies kann man auch schön in dem Ablaufplan sehen.
Mit einer if-Abfrage, einer Hilfsvariable und goto ließe sich das in jeder beliebigen Programmiersprache z. B. so realisieren:
1 2 3 4 5 6 7 8 |
set /a x=true :while if %x%=true ( echo Anweisungsblock if ... ( set /a x=false ) goto :while ) :end-while |
…oder ohne die Hilfsvariable, wenn eine Prüfung auf eine Bedingung nicht zu umfangreich ist und sich der Code so etwas vereinfachen lässt:
1 2 3 4 5 6 |
:while if ... ( echo Anweisungsblock goto :while ) :end-while |
In der PowerShell gibt es stattdessen die Möglichkeit ohne Hilfsmittel eine While-Schleife zu verwenden:
1 2 3 4 |
while ($true){ # Anweisungsblock write-host bla } |
Auf jeden Fall muss sichergestellt werden, dass die Bedingung irgendwann „False“ wird. Ansonsten hat man eine Endlosschleife erstellt.
Do-While-Schleifen
Im Gegensatz zur While-Schleife wird eine Do-While-Schleife mindestens einmal durchlaufen. Solange bis die Bedingung „False“ ist. Hierfür muss man nur das vorherige Beispiel umstellen. Der Anweisungsblock befindet sich nun vor der Bedingungsprüfung, also vor dem if-Block. Die Prüfung der Bedingung findet nun am „Fuß“ der Schleife statt. Es handelt sich also um eine „fußgesteuerte“ Schleife.
Auch diese lässt sich auf die gleiche Weise wie die While-Schleife in Batch realisieren:
1 2 3 4 5 6 7 8 |
set /a x=true :do-while echo Anweisungsblock if %x%=true ( if ... ( set /a x=false ) goto :do-while ) :end-while |
Das ganze geht natürlich auch bei der Do-While-Schleife ohne Hilfsvariable und wirkt dann aufgeräumter:
1 2 3 4 5 6 |
:while echo Anweisungsblock if ... ( goto :while ) :end-while |
In der PowerShell kann man vergleichsweise einfach eine Do-While-Schleife nutzen, bspw. so:
1 2 3 4 |
do { # Anweisungsblock write-host bla } while ($true) |
Auch bei der Do-Variante Fall muss sichergestellt werden, dass die Bedingung irgendwann „False“ wird, um keine Endlosschleife zu bauen.
Until-Schleifen
Um eine Until-Schleife zu realisieren, müsste man theoretisch nur die Bedingungsprüfung in der While-Schleife umstellen. Die Schleife muss in diesem Fall solange laufen wie die Bedingung „False“ oder eher bis sie „True“ zurück liefert. Denn eine Until-Schleife stoppt sobald die Bedingung „True“ wird. Es handelt sich um eine „kopfgesteuerte“ Schleife.
Bei der umgekehrten While-Schleife wird aber kein richtiger Abbruch der Schleife genutzt. Vielmehr wird immer noch eine While-Schleife genutzt, die einfach nur andersherum prüft. Deshalb habe ich mich für folgende explizitere Abbruch-Variante entschieden:
1 2 3 4 5 |
:until if ... ( goto :end-until ) echo Anweisungsblock goto :until :end-until |
In der PowerShell gibt es tatsächlich keine normale Until-Schleife! Eine solche Anweisung wird mit einer Fehlermeldung quittiert:
1 2 3 4 |
until ($true) { # Anweisungsblock write-host bla } |
Das kann unter Umständen doof sein, da der Anweisungsblock der Do-Until-Schleife auf jeden Fall einmal ausgeführt wird. Selbst wenn die Abbruchbedingung schon beim erstmaligen Durchlauf der Schleife erfüllt ist. Denn die Überprüfung findet erst im Schleifenfuß, also am Ende, statt. Wenn die Schleife in dem Fall gar nicht durchlaufen werden soll, kann man sich nur auf unschöne Arten behelfen:
Entweder man setzt eine if-Abfrage um die Schleife herum um eine Ausführung auszuschließen, falls die Abbruchbedingung bereits gegeben ist:
1 2 3 4 5 6 7 |
if (!($test -eq 10)){ do { write-host $test $test=$test+1 } until ($test -eq 10) } |
Oder man nutzt einen ebenfalls unschönen break-Befehl in der Schleife um aus dieser auszubrechen:
1 2 3 4 5 |
do { if ($test -eq 10){ break } write-host $test $test=$test+1 } until ($test -eq 10) |
Schöner finde ich es in einem solchen Fall stattdessen auf eine While-Schleife zurückzugreifen und die Bedingung einfach so umzubauen, dass es auch mit diesem Schleifentypen realisiert werden kann. Für das vorherige Do-Until-Beispiel eventuell so:
1 2 3 4 |
while ((!$test -eq 10)) { write-host $test $test=$test+1 } |
In diesem Beispiel ist noch zu bedenken, dass man sich eventuell eine Endlosschleife baut, falls der Wert der Variable $test bereits beim ersten Schleifendurchlauf bei 11 liegt. Dann würde die Schleife endlos laufen und immer weiter hochzählen. Für eine Zählschleife ist das Konstrukt also eher nicht geeignet, sondern wenn man z. B. einen String untersucht.
Do-Until-Schleifen
Für eine Do-Until-Schleife muss man nur, wie schon bei der Do-While-Schleife, den Anweisungsblock vor die Bedingungsprüfung setzen und erhält dann eine „fußgesteuerte“ Schleife. Dieser wird dann mindestens einmal durchlaufen:
1 2 3 4 5 |
:do-until echo Anweisungsblock if ... ( goto :end-until ) goto :do-until :end-until |
In der PowerShell kann man vergleichsweise einfach eine Do-Until-Schleife nutzen, bspw. so:
1 2 3 4 |
do { # Anweisungsblock write-host bla } until ($true) |
Auch aus der Do-Variante kann man versehentlich eine Endlosschleife machen, falls die Abbruchbedingung niemals eintritt.
Welche Schleife wann nutzen?
Ich stelle mir selber die Frage, wann man welchen Schleifentyp nutzen sollte und vermutlich gibt es darauf verschiedene Antworten und Präferenzen. Dies ist vermutlich immer sehr individuell entsprechend der zu erledigenden Aufgabe zu entscheiden. Grundsätzlich kann man viele Dinge, die man mit einer While-Schleife machen würde z. B. auch mit einer Until-Schleife erledigen und umgekehrt.
Die Entscheidung sollte wohl danach getroffen werden ob man eher eine Abbruchbedingung (Until) oder eine Ausführungsbedingung (While) benötigt. Wichtiger ist vermutlich die Frage ob man die fuß- oder die kopfgesteuerte Variante nutzen sollte und ob dadurch eine zumindest einmalige Ausführung des Anweisungsblocks gewünscht ist.
Fazit
Auch wenn sich die Schleifen in der Kommandozeile basteln lassen, stellt die PowerShell ein komfortableres und moderneres Skriptumfeld dar. Wenn möglich sollte man die PowerShell bevorzugen um „saubere“ Skripte zu schreiben und „schöne“ Varianten der verschiedenen Schleifentypen verwenden zu können.
Lässt sich dies z. B. aufgrund eines veralteten Zielsystems nicht realisieren, sollte man versuchen die selbstgebastelten While– oder Until-Schleifen aufgrund der höheren CPU-Last zu vermeiden. Vermutlich ist das in den meisten Fällen aufgrund der vorhandenen Leistung des Systems aber nicht so ein große Problem.
Goto in C#, Python, PHP, Excel VBA, Bash, PowerShell nutzen?
Batch: if und else - Syntax verstehen
While- und Until-Schleifen in PowerShell und Batch
Wie lange läuft mein PC schon? – PC Laufzeit auslesen unter Windows 10
Wochentag in einer Batch-Datei ermitteln
CMD/Powershell: Ping-Ergebnis mit Errorlevel auswerten
Skript um Zeit-basiert Dateien zu tauschen
Die Gefahr durch mit Adminrechten ausgeführte Skripte
Powershell und CMD: Clear um Konsole zu leeren?
Java-Anwendungen über CMD oder Powershell starten
Kommandozeile aus Explorer in geöffnetem Ordner
Umlaute in Batch-Dateien – immer wieder schön