Mivel a PowerShellben nagyon sokszor gyűjteményekkel (collection) dolgozunk, így az alkotók praktikusnak találták ezek elemein történő műveletvégzést megkönnyítő ciklust is készíteni. Köszönet ezért nekik! A FOREACH kulcsszó segítségével olyan ciklust tudunk létrehozni, ahol nem nekünk kell nyilvántartani, számlálni és léptetni a ciklusváltozót, hanem a PowerShell ezt megteszi helyettünk:
[25] PS C:\> $egyveleg = 1,"szöveg",(get-date),@{egy=2}
[26] PS C:\> foreach($elem in $egyveleg){$elem.gettype()}
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
True True String System.Object
True True DateTime System.ValueType
True True Hashtable System.Object
Látszik a ciklus működési elve: az $elem változóba a PowerShell mindig betölti az aktuális tömbelemet az $egyveleg tömbből egészen addig, amíg van elem, és minden elem mellett a sor végén látható szkriptblokkot végrehajtja. Ugyanezt FOR ciklussal is meg tudnánk csinálni, de mennyivel többet kell gépelni, és eggyel több változóra is szükségünk van, plusz még az olvashatósága is sokkal nehézkesebb:
[27] PS C:\> $egyveleg = 1,"szöveg",(get-date),@{egy=2}
[28] PS C:\> for($i=0;$i -lt $egyveleg.length;$i++){$egyveleg[$i].GetType()}
...
Láthattunk a 1.3.3.1 Egyszerű tömbök fejezetben, hogy a PowerShell képes automatikusan „kifejteni” a tömböket (gyűjteményeket) például a get-member cmdletbe való becsövezéskor. A FOREACH ennek ellenkezőjét végzi, azaz egy skaláris (nem tömb) paraméterből képes automatikusan egyelemű tömböt készíteni, ha erre van szükség:
[29] PS I:\>$skalár = 1
[30] PS I:\>foreach($szám in $skalár){"Ez egy szám: $szám"}
Ez egy szám: 1
A fenti példában nem okozott a PowerShellnek problémát foreach ciklust futtatni egy nem tömb típusú változóra, automatikusan egy elemű tömbként kezelte.
A foreach ciklus belsejében a PowerShell automatikusan létrehoz egy $foreach változót, aminek a $foreach.current tulajdonsága az éppen aktuális elemet tartalmazza, így akár a [26]-os sorban levő példát másként is írhatnánk:
[53] PS C:\> foreach($elem in $egyveleg) {$foreach.current.gettype()}
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
True True String System.Object
True True DateTime System.ValueType
True True Hashtable System.Object
Nézzük meg, hogy ennek a $foreach változónak milyen tagjellemzői vannak:
[50] PS C:\> foreach($elem in "a"){,$foreach | get-member}
TypeName: System.Array+SZArrayEnumerator
Name MemberType Definition
---- ---------- ----------
Clone Method System.Object Clone()
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
get_Current Method System.Object get_Current()
MoveNext Method System.Boolean MoveNext()
Reset Method System.Void Reset()
ToString Method System.String ToString()
Current Property System.Object Current {get;}
Az igazán izgalmas számunkra a MoveNext() metódus, amellyel arra késztethetjük a foreach ciklust, hogy egy elemet kihagyjon:
[54] PS C:\> $tömb = 1,2,3,4,5,6,7
[55] PS C:\> foreach($elem in $tömb){$elem; $foreach.MoveNext()}
1
True
3
True
5
True
7
False
A fenti példában miután minden elemnél rögtön még egy elemet léptettünk, ezért csak minden második számot írtam ki. Ugyan a MoveNext() ad visszatérési $true vagy $false értéket attól függően, hogy van-e még elem vagy nincs, de ezt elnyomhatjuk a [void] típuskonverzióval:
[56] PS C:\> foreach($elem in $tömb){$elem; [void] $foreach.MoveNext()}
1
3
5
7
A $foreach változó másik fontos metódusa a Reset() , ezzel vissza lehet ugrani a ciklus első elemére:
[60] PS C:\> $első = $true
[61] PS C:\> foreach($elem in $tömb){$elem; if($első -and !$foreach.MoveNext()) {$foreach.Reset(); $első = $false}}
1
3
5
7
1
2
3
4
5
6
7
A fenti példában egy ciklussal kétszer is végig járom a $tömb tagjait, első menetben minden másodikat írom ki, a második menetben pedig az összes tagot. Itt is jól jött, hogy az –or feltétel második operandusa csak akkor kerül kiértékelésre, ha az első tag $false, ezzel értem el, hogy a második menetben már ne legyen végrehajtva a MoveNext().