ForEach-Object cmdlet

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.

Párhuzamos Foreach-Object

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.



Word To HTML Converter