Az előzőekben bemutatott FOREACH egy PowerShell kulcsszó ciklus létrehozására. Azonban létezik egy másik FOREACH is, ami a Foreach-Object cmdlet álneve:
[25] PS C:\> get-alias foreach
CommandType Name Definition
----------- ---- ----------
Alias foreach ForEach-Object
Honnan tudja a PowerShell hogy egy parancssorban most melyiket is akarom használni: a kulcsszót vagy a cmdletet? Nagyon egyszerűen: ha a foreach után egy zárójel „(” van, akkor kulcsszóval van dolga, ha „{” bajusz, akkor alias. Másrészt, a kulcsszavak mindig a kifejezések elején kell, hogy álljanak. A kifejezések eleje vagy a parancssor eleje, vagy pontosvessző (;), vagy nyitó zárójel „(”. A csőjel (|) nem számít kifejezés elejének.
Mire lehet használni ezt a foreach-object cmdletet? Míg a foreach kulcsszó tömbjének, amin végigmegy, készen kell állnia teljes egészében, mire a vezérlés ráadódik, addig a ForEach-Object cmdlet csővezetékből érkező elemeket dolgoz fel, azaz ha már egy bemeneti elem elkészült, akkor azt már át is veszi és elvégzi a kijelölt műveletet:
[1] PS C:\> 1,2,3 | ForEach-Object {$_ * 3}
3
6
9
A fenti példában nem a $foreach változón keresztül értem el az aktuális elemet, hanem a szokásos $_ változón.
Megjegyzés:
Van egy érdekessége ennek a cmdletnek. Figyeljük meg a help-ben a szintaxisát:
[2] PS C:\> get-help foreach-object
NAME
ForEach-Object
SYNOPSIS
Performs an operation against each item in a collection of input objects.
SYNTAX
ForEach-Object [-Process] <ScriptBlock[]> [-Begin <ScriptBlock>] [-End <Sc
riptBlock>] [-InputObject <PSObject>] [-RemainingScripts <ScriptBlock[]>]
[-Confirm] [-WhatIf] [<CommonParameters>]
ForEach-Object [-MemberName] <String> [-ArgumentList <Object[]>] [-InputOb
ject <PSObject>] [-Confirm] [-WhatIf] [<CommonParameters>]
...
Hoppá! Nem is egy, hanem három szkriptblokkot lehet átadni neki paraméterként, kicsit hasonlóan, mint ahogy a FOR ciklusnál is három rész vezérli a ciklust. Itt a három rész szerepe: mit tegyen az első elem érkezése előtt, mit tegyen az éppen aktuális elemek érkezésekor, mit tegyen az utolsó elem érkezése után.
Nézzünk erre egy példát:
[13] PS C:\> "egy","kettő","három" | foreach-object {"----eleje----"} {$_} {"----vége----"}
----eleje----
egy
kettő
három
----vége----
Ebben a példában nem is írtam ki a paraméterek neveit, azaz a pozíció alapján rendelte hozzá az egyes szkriptblokkokat a PowerShell. Ha csak egy szkriptblokkot adunk meg az a Process szekcióhoz sorolódik.
A fenti megjegyzésben (PowerShell 3.0-tól kezdődően) látható, hogy egy második szintaxisa is van a ForEach-Object-nek. Ezzel a gyakoribb, egyszerűbb kifejezéseket tömörebben lehet összerakni. Például:
PS C:\> "egy", "kettő" | ForEach-Object -MemberName length
3
5
PS C:\> "egy", "kettő" | ForEach-Object -MemberName contains -ArgumentList g
True
False
Ez első példa hagyományosan így nézne ki:
PS C:\> "egy", "kettő" | ForEach-Object {$_.length}
A második meg így:
PS C:\> "egy", "kettő" | ForEach-Object {$_.contains("g")}
Azaz akár tulajdonságokat, akár metódusokat egyszerűen el tudunk érni.
PowerShell 7.0-ban megjelent a párhuzamos feldolgozást lehetővé tevő Foreach‑Object ‑Parallel :
PS C:\> get-date; 10..1|ForEach-Object -Parallel {Start-Sleep $_; Write-Host $_}; Get-Date
2020. április 3. 22:32:47
6
7
8
9
10
2
3
1
4
5
2020. április 3. 22:32:58
A fenti példában két időkiíratás között 10-től 1-ig visszafelé nyomom be a számokat a csőbe, és a szám értékének megfelelő másodpercig várakozom, majd kiírom a számot. Az eredményből látszik, hogy nem a 10 hullott ki legelőször, hanem a 6-os. Ez azt jelenti, hogy a 10, 9,8,7,6 párhuzamosan hajtódott végre, azaz 5 szálon futott alaphelyzetben. Ezek közül a 6 a legkisebb, úgyhogy az hullott ki először. Azt a szálat rögtön elfoglalta az 5. A következő kihulló elem a 7-es, annak a helyére a 4 lép be, stb. Mivel a második etap mindegyike 11 másodperc múlva hullik ki, így a második 5 elem szinte egyszerre jelenik meg, sorrendjük nem igazán számítható ki, a fenti példában is kicsit össze-vissza estek ki. Összefoglalva, az összesen 45 másodperc helyett 12 másodpercre volt szükség a végrehajtásra (nem 11, mert van egy kis overhead).
A párhuzamos szálak száma szabályozható a -ThrottleLimit paraméterrel:
PS C:\> 10..1|ForEach-Object -Parallel {Start-Sleep $_; Write-Host $_} -ThrottleLimit 4
7
8
9
10
3
6
4
5
1
2
Itt látszik, hogy 4 szál futott együtt. Lehet megadni a időlimitet is a -TimeoutSeconds paraméterrel:
PS C:\>
10..1|ForEach-Object -Parallel {Start-Sleep $_; Write-Host $_} -ThrottleLimit 4
-TimeoutSeconds 14
7
8
9
10
3
6
5
4
InvalidOperation: The pipeline has been stopped.
InvalidOperation: The pipeline has been stopped.
Látszik, hogy a timeout a szál teljes életciklusára vonatkozik, azaz 14 másodperc esetében a 7,8,9,10 másodperc várakozás még belefért, az utána jövő 3-6 szintén, de az 1-2 az már nem. Ugyan 7+3+2 az csak 12, de mint mondtam van egy kis „felkészülési időre” szüksége. Azaz csak akkor érdemes ilyen paralel futtatást használni, ha legalább 10-15 másodpercig tart a feladat, illetve nem érdemes több szálat indítani, mint ahány processzor mag van a számítógépben.
Megjegyzés
Ebben a párhuzamos Foreach-Object-ben a szkriptblokk nem ugyanúgy működik, mint a hagyományos változatban. Itt egy külön scope-ban van a szkriptblokk, nem lát rá a külső környezet változóira:
PS C:\> $a = "Kakukk"
PS C:\> 1..2 | ForEach-Object -Parallel {Write-Host "$_ : $a"}
2 :
1 :
Látszik, hogy a szkriptblokknak nincs tudomása a kívül létrehozott $a változóról.