Szkriptblokk létrehozása futásidőben

Vegyünk egy egyszerű példát: matematikai műveleteket szeretnénk elvégeztetni két paraméterként megadott számmal. Első próbálkozás:

function művelet1 {

param(

    [double] $x,

    [string] $művelet,

    [double] $y

)

    switch($művelet){

        "+" {$x + $y}

        "/" {$x / $y}

        "*" {$x * $y}

        "%" {$x % $y}

        "-" {$x - $y}

    }

}

Működés szempontjából nem rossz:

PS C:\> művelet1 1 + 2

3

PS C:\> művelet1 5 * 2

10

Viszont a kódban egyszerűsítésért kiáltanak az ismételt szerkezetű sorok a switch kifejezésben. Nézzük meg azt, hogy összerakjuk a teljes műveletet egy kifejezésben és hajtsuk végre azt:

function művelet2 {

param(

    [double] $x,

    [string] $művelet,

    [double] $y

)

    & "$x $művelet $y"   

}

Sajnos azonban ezt futtatva hibát kapunk:

PS C:\> művelet2 1 + 2

& : The term '1 + 2' is not recognized as the name of a cmdlet, function, scri

pt file, or operable program. Check the spelling of the name, or if a path was

 included, verify that the path is correct and try again.

At line:7 char:7

+     & "$x $művelet $y"

+       ~~~~~~~~~~~~~~~~

    + CategoryInfo          : ObjectNotFound: (1 + 2:String) [], CommandNotFo

   undException

    + FullyQualifiedErrorId : CommandNotFoundException

Biztonsági okokból a PowerShell sztringeket nem enged csak úgy futtatni. Az egyik megoldás, hogy az Invoke-Expression cmdletet használjuk, mint ahogy azt a 1.4.15 Végrehajtás (., &, Invoke-Expression) fejezetben láttuk:

function művelet3 {

param(

    [double] $x,

    [string] $művelet,

    [double] $y

)

    Invoke-Expression "$x $művelet $y"   

}

 Másik lehetőség, hogy scriptblock adattípust építünk és azt hajtjuk végre:

function művelet4 {

param(

    [double] $x,

    [string] $művelet,

    [double] $y

)

    & ([scriptblock]::Create("$x $művelet $y"))

}

A fenti példa ténylegesen nem sokban különbözik az Invoke-Expression-ös megoldástól, mindkettőben az $x és $y sztringként helyettesítődik be a kifejezésbe, majd az egész kifejezés újra kiértékelődik.

Profibb lenne, ha az $x és $y nem menne át az ide-oda alakításon, hanem értékadással kerülne be a kifejezésbe. Ehhez az utolsó megoldást tudjuk továbbfejleszteni:

function művelet5 {

param(

    [double] $x,

    [string] $művelet,

    [double] $y

)

    $sb = [scriptblock]::Create("`$args[0] $művelet `$args[1]")

    &$sb $x $y

}

 

 

A szkriptblokkok használata sok egyéb előnnyel jár az Invoke-Expression-höz képest. Nézzük a következő példát:

PS C:\> $a = 1

PS C:\> $első = {$a}

PS C:\> $a = 2

PS C:\> $második = {$a}

PS C:\>

PS C:\> &$első

2

PS C:\> &$második

2

Létrehoztam egy $a változót 1 értékkel, majd egy $első változót, mely egy szkriptblokkot tartalmaz, ami csak egyszerűen kiírja az $a változót. Majd megismételtem ugyanezt, csak az $a-nak 2-őt adtam, majd a $második szkriptblokkot hoztam létre.

Látható, hogy amikor futtattam a két szkriptblokkot, akkor mindkettő 2-t adott vissza, azaz hiába hoztam létre az $első-t akkor, amikor még $a értéke 1 volt, mivel ezek a szkriptblokkok a külső környezettel szimbiózisban élnek együtt, ezért mindig az aktuális $a értékét adják vissza, ebben nincsen semmi meglepő.

Azonban ezt lehet másképp is:

PS C:\> $a = 1

PS C:\> $első = {$a}.GetNewClosure()

PS C:\> $a = 2

PS C:\> $második = {$a}.GetNewClosure()

PS C:\>

PS C:\> &$első

1

PS C:\> &$második

2

PS C:\> $a = 10

PS C:\> &$első

1

PS C:\> &$második

2

A fenti példában majdnem ugyanazt csináltam, mint korábban, azzal a különbséggel, hogy a szkriptblokkok létrehozásakor azon melegükben a GetNewClosure() metódust hívtam meg. Ezzel elszakítottam őket az őket definiáló környezettől olyan módon, hogy egy pillanatfelvétel erejéig még átadtam nekik, amire szükségük volt, jelen esetben a $a-t. De innentől kezdve akármit csinálok az $a-val, őket ez nem zavarja.

Érdekes módon, még ekkor is kapcsolatba tudunk kerülni a bennük tárolt $a-val! Bruce Payette elárulta nekünk a PowerShell in Action könyvében:

PS C:\> & $első.Module set-variable a 11

PS C:\> & $második.Module set-variable a 12

PS C:\> &$első

11

PS C:\> &$második

12

Nem mondom, hogy ezt magamtól is kitaláltam volna, illetve nem nagyon találkoztam ezzel a trükkel korábban, de jó tudni, hogy ilyen is van. Az is kiderül itt, hogy a GetNewClosure()-al létrehozott szkrtipblokk valójában egy modul, méghozzá a dinamikus fajta.

Ez mindjárt beindította a fantáziámat! Lehet, hogy ezzel a módszerrel szkriptmodulok változóiba is be tudunk nyúlni? Nézzük meg! Van egy egyszerű modulom, hangsúlyozom, hogy egy PSM1 fájlom:

$script:t = 6

function hatszorozó ($x){

    $t * $x

}

Ebben ugye egy hatszorozó nevű függvényem. Import után úgy műküdik, ahogy elvárjuk:

PS C:\> Import-Module C:\PowerShell\Könyv\moduleclosure.psm1

PS C:\> hatszorozó 5

30

Vegyük elő Bruce trükkjét:

PS C:\> & (Get-Module moduleclosure) Get-Variable t -value

6

PS C:\> & (Get-Module moduleclosure) Set-Variable t -value 5

PS C:\> hatszorozó 5

25

Egyrészt ki tudtuk olvasni a modul saját változóját, másrészt át is tudtam írni és ezzel a hatszorozó függvény immáron csak ötszöröző lett.



Word To HTML Converter