Home PowerShell Grundlagen PowerShell ForEach und ForEach-Object
formats

PowerShell ForEach und ForEach-Object

Die Windows PowerShell hat wie alles seine Ecken und Kanten. Eine Kante über die viele PowerShell Neulinge stolpern ist die Tatsache:

Es gibt in der PowerShell 2 Verschieden ForEach!

  • Es gibt in der PowerShell Einmal das ForEach-Object { … } Cmdlet
  • Und es gibt in der PowerShell die ForEach() { … } Schleife.
  • Gemeinerweise hat das ForEach-Object { … } Cmdlet einen Alias der ForEach heisst!

Da kommt man schnell durcheinander!

Ich möchte hier erklären wann und warum man welches ForEach einsetzt.

Alle Arten von ForEach werden eingesetzt um, für jedes einzelne Objekt aus einer Menge von Objekten, Aktionen ausführen zu können.
Ein Beispiel von zwei Befehlszeilen. Eine Befehlszeile mit der ForEach Schleife und die andere Befehlszeile mit dem Alias des ForEach-Object Cmdlet.

Beide Befehlszeilen oben erzeugen dasselbe Ergebnis. Die internen Arbeitsweisen sind aber völlig verschieden.
ForEach-Object ist ein Cmdlet und wird in der Pipeline eingesetzt.
.
Die ForEach() Schleife wird nicht in der Pipeline eingesetzt.
(man kann die ForEach() Schleife auch in der Pipeline einsetzen dies ist aber kein Standard).
Beide ForEach Konstrukte sind unterschiedlich im Speicherverbrauch und in der Ausführungsgeschwindigkeit.

Speicherverbrauch

Einige Objekte, z.B. aus dem Active Directory, können sehr viele Daten enthalten und dem entsprechend viel Speicher verbrauchen.
Damit die ForEach() Schleife arbeiten kann, muss zuerst ein Array (Liste) mit allen Objekten im Speicher erstellt werden.
Im oberen Beispiel holt Get-ChildItem erst alle Datei und Ordner Objekte komplett in den Speicher, bevor die Schleife anfängt zu arbeiten.
Bei einer sehr großen Anzahl von (großen) Objekten kann dies den Speicher überlasten.
.
In der Pipeline wird ein Objekt sofort nach dem erzeugen oder Bearbeiten an das nächste Cmdlet weitergegeben. Ausnahmen sind hier z.B. das Sort-Object Cmdlet  das die Objekte sammeln muss um sie zu sortieren.
In unserem Beispiel wird jedes Objekt das Get-ChildItem erzeugt sofort einzeln an das ForEach-Object Cmdlet weitergegeben.
Dies verbraucht sehr wenig Arbeitsspeicher während der Abarbeitung.
Hier kann man sehr große Objekt mengen ohne Speicherprobleme verarbeiten.

Performanz

Bei dem ForEach Cmdlet wird vor dem verarbeiten eine Pipeline im Speicher aufgebaut.
Dadurch dass das ForEach Cmdlet die Objekte einzeln holt und weitergibt kann es diesen Vorgang nicht gut optimieren.
.
Dadurch dass die ForEach Schleife alle Objekte auf einmal holt, kann die Schleife diesen Vorgang gut optimieren. Danach werden alle Objekte aus dem schnellen Speicher heraus verarbeitet.
Somit arbeitet die ForEach Schleife oft schneller als das ForEach-Object Cmdlet.
.
Bei einer geringen Anzahl an Objekten (kleine  Datenmenge) ist es egal wann man das ForEach-Object Cmdlet und wann die ForEach Schleife nimmt.

Übersichtlichkeit

Das ForEach-Object Cmdlet nutzt intern die Variable $_ (ab PowerShell 3.0 auch $PSItem) um das Aktuell zu verarbeitenden Objekt zu repräsentieren.
Die ForEach Schleife nutzt einen sprechenden Variablen Namen in dem das aktuelle Objekt zu finden ist.
.
Wenn man ForEach verschachteln muss wird es mit dem ForEach-Object Cmdlet etwas unübersichtlich.
.
Die ForEach Schleife benötigt oft vor und nach der Bearbeitung Hilfs-Variablen z.B. für die Ausgabe der Ergebnisse in einem Array.
Das ForEach-Object Cmdlet gibt das Ergebnis einfach in der Pipeline weiter.

Ablauf Steuerung

Man kann den Ablauf einer ForEach Schleife mit den  Schlüsselwörtern ‘break’ und ‘continue’ steuern.

Ergebnis:
2
4
6
8

Die Pipeline arbeitet in 3 Phasen, auch bei dem ForEach-Object Cmdlet.
.
In der ersten Phase wird von jedem Cmdlet der Begin-Block ausgeführt.
In der zweiten Phase werden alle Objekte in der Pipeline weitergereicht und abgearbeitet.
Wenn alle Cmdlets in der Pipeline alle Objekt verarbeitet haben, dann wird in der dritten Phase der End-Block ausgeführt.
Das  ForEach-Object Cmdlet hat deshalb die 3 Scriptblöcke –Begin {} –Process{} und –End {}.
Wenn kein expliziter Scriptblock angegeben wird, wird immer der Process-Block genutzt.
Ein vergleichbares Steuerelement wie das Schlüsselwort ‘break’ gibt es nicht in der Pipeline. Bei einem ‘break’ würde die gesamte Pipeline die Arbeit abbrechen müssen.
Im Prozess-Block kann man das Schlüsselwort ‘return’ genauso einsetzen wie das Schlüsselwort ‘continue’ bei einer ForEach Schleife:

Ergebnis:
2
4
6
8

Setzt man die Schlüsselwörter ‘break’ oder ‘continue’ versehentlich im ForEach-Object Cmdlet ein, wird die Arbeit der Pipeline beendet!

Fazit

Nun könnte man auf die Idee kommen, man braucht das ForEach-Object Cmdlet nur wenn man Speicher-hungrige Aktionen durchführt.
Wie ich am Anfang schon geschrieben habe arbeitet das ForEach-Object Cmdlet in der Pipeline. Es gibt einige Cmdlets, die am liebsten Pipeline eingaben mögen, wie z.B. das Export-CSV Cmdlet.
hier kann das ForEach-Object Cmdlet seine Fähigkeiten gut einsetzen.

P.S.

Die Pipeline selbst arbeitet ja auch schon wie ein ForEach.
Schaut euch mal dieses Beispiel an. ;-)

Links:

Dieser Post ist eine (verbesserte) Übersetzung von dem guten Blog Post von Kirk Munro
Essential PowerShell: Understanding foreach
http://poshoholic.com/2007/08/21/essential-powershell-understanding-foreach/
Essential PowerShell: Understanding foreach (Addendum)
http://poshoholic.com/2007/08/31/essential-powershell-understanding-foreach-addendum/
Sehr tiefgehende ForEach Performanz Diskussion im Blog von Brendon Shell:
Why use foreach vs foreach-object.
http://bsonposh.com/archives/327
PowerShell: Return, ForEach,ForEach-Object and a pipe
http://www.jbmurphy.com/2011/11/29/powershell-return-foreachforeach-object-and-a-pipe/

 
 Share on Facebook Share on Twitter Share on Reddit Share on LinkedIn
Kommentare deaktiviert  comments