Szkriptkönyvtárak, futtatási információk ($myinvocation)

Munkánk során valószínű jó néhány hasznos függvényt készítünk, amelyeket rendszeresen használni szeretnénk. Ezekhez úgy férünk hozzá legegyszerűbben, ha ezeket a függvényeket szkriptfájlokban elmentjük egy könyvtárba, majd a sok kis szkriptfájlunkat egy „beemelő”, „include” jellegű központi szkripttel lefuttatjuk (vagy modult készítünk belőlük, lásd 2.2 Modulok fejezetet.

Ennek modellezésére készítettem egy „scripts” könyvtárat, amelyben három szkriptem három függvényt definiál. Ezen kívül van egy include.ps1 szkriptem, ami csak annyit csinál, hogy a saját könyvtárában levő másik három szkriptet meghívja „dotsourcing” jelleggel, azaz úgy, hogy a szkriptek által definiált függvények bárhonnan elérhetők, meghívhatók legyenek.

[17] PS C:\powershell2\egyik> Get-ChildItem C:\powershell2\scripts

 

 

    Directory: Microsoft.PowerShell.Core\FileSystem::C:\powershell2\scripts

 

 

Mode           LastWriteTime       Length Name

----           -------------       ------ ----

-a---   2008.04.19.    12:31           42 fv1.ps1

-a---   2008.04.19.    12:31           45 fv2.ps1

-a---   2008.04.19.    12:31           45 fv3.ps1

-a---   2008.04.19.    12:32           40 include.ps1

 

 

[18] PS C:\powershell2\egyik> get-content C:\powershell2\scripts\fv1.ps1

function fv1

{

        "Első függvény"

}

[19] PS C:\powershell2\egyik> get-content C:\powershell2\scripts\include.ps1

 

. .\fv1.ps1

. .\fv2.ps1

. .\fv3.ps1

Ez egyes függvények nagyon egyszerűek, csak annyit írnak ki, hogy hányadik függvényről van szó. Ez így külön-külön nagyon szépnek és logikusnak tűnik, próbáljuk meg futtatni az include.ps1 szkriptünket az „egyik” nevű könyvtárból:

[22] PS C:\powershell2\egyik> C:\powershell2\scripts\include.ps1

The term '.\fv1.ps1' 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 C:\_munka\powershell2\scripts\include.ps1:3 char:2

+ . <<<<  .\fv1.ps1

    + CategoryInfo          : ObjectNotFound: (.\fv1.ps1:String) [], CommandN

   otFoundException

    + FullyQualifiedErrorId : CommandNotFoundException

 

The term '.\fv2.ps1' 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 C:\_munka\powershell2\scripts\include.ps1:4 char:2

+ . <<<<  .\fv2.ps1

    + CategoryInfo          : ObjectNotFound: (.\fv2.ps1:String) [], CommandN

   otFoundException

    + FullyQualifiedErrorId : CommandNotFoundException

 

The term '.\fv3.ps1' 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 C:\_munka\powershell2\scripts\include.ps1:5 char:2

+ . <<<<  .\fv3.ps1

    + CategoryInfo          : ObjectNotFound: (.\fv3.ps1:String) [], CommandN

   otFoundException

    + FullyQualifiedErrorId : CommandNotFoundException

Valami nem jó! Nyomozzunk utána, cseréljük le az include.ps1 belsejét egy olyan vizsgálatra, amely megmutatja, hogy mit érez a szkript aktuális könyvtárnak. Amíg nem találom meg a hibát, a függvényeket definiáló szkriptek hívását kikommenteztem:

#. .\fv1.ps1

#. .\fv2.ps1

#. .\fv3.ps1

Get-Location

Ezt futtatva a következőket kapjuk:

[23] PS C:\powershell2\egyik> C:\powershell2\scripts\include.ps1

 

Path

----

C:\powershell2\egyik

Kiderült a hiba oka, annak ellenére, hogy az include.ps1 a scripts könyvtárban fut, számára is az aktuális könyvtár az „egyik”. Hogyan lehetne azt megoldani, hogy az include.ps1 számára a saját könyvtára legyen az aktuális?

Szerencsére van egy $MyInvocation  nevű automatikus változó, amely a szkriptek számára a futtatásukkal kapcsolatos információkat árulja el. Nézzük is ezt meg, módosítottam az include.ps1 szkriptemet:

#. .\fv1.ps1

#. .\fv2.ps1

#. .\fv3.ps1

$MyInvocation

Ezt futtatva kapjuk a következőket:

[30] PS C:\powershell2\egyik> C:\powershell2\scripts\include.ps1

 

 

MyCommand        : include.ps1

ScriptLineNumber : 1

OffsetInLine     : -2147483648

ScriptName       :

Line             : C:\powershell2\scripts\include.ps1

PositionMessage  :

                   At line:1 char:34

                   + C:\powershell2\scripts\include.ps1 <<<<

InvocationName   : C:\powershell2\scripts\include.ps1

PipelineLength   : 1

PipelinePosition : 1

Ha átlépünk a szülőkönyvtárba, és onnan hívjuk meg a szkriptet a következőképpen alakul a $MyInvocation értéke:

[31] PS C:\powershell2\egyik> cd ..

C:\powershell2

[32] PS C:\powershell2> .\scripts\include.ps1

 

 

MyCommand        : include.ps1

ScriptLineNumber : 1

OffsetInLine     : -2147483648

ScriptName       :

Line             : .\scripts\include.ps1

PositionMessage  :

                   At line:1 char:21

                   + .\scripts\include.ps1 <<<<

InvocationName   : .\scripts\include.ps1

PipelineLength   : 1

PipelinePosition : 1

Ebből az látszik, hogy se a Line, se az InvocationName tulajdonság nem a szkript tényleges elérési útját tartalmazza, hanem azt, ahonnan meghívtuk. Így ezekkel a tulajdonságokkal nem tudunk dolgozni, mert ezek sem adnak iránymutatást arra vonatkozólag, hogy hol van ténylegesen az include.ps1, és vele együtt a függvényeimet tartalmazó szkriptek.

Nézzük, hogy vajon a MyCommand -nak vannak-e tulajdonságai:

#. .\fv1.ps1

#. .\fv2.ps1

#. .\fv3.ps1

$MyInvocation.MyCommand

A futtatás eredménye:

[34] PS C:\powershell2> .\scripts\include.ps1 | fl

 

 

Path        : C:\powershell2\scripts\include.ps1

Definition  : C:\powershell2\scripts\include.ps1

Name        : include.ps1

CommandType : ExternalScript

Itt már van egy sokat sejtető Path tulajdonság, ami egy teljes elérési út, így remény van rá, hogy ebből kiindulva már jól el fogjuk tudni érni a függvényeket tartalmazó szkripteket.

Létezik egy split-path  cmdlet, amellyel le tudjuk választani egy elérési útról a könyvtár-hierarchia részt, ennek segítségével már el tudjuk készíteni az univerzális, bárhonnan működő include.ps1 szkriptünket:

Push-Location

Set-Location (split-path $MyInvocation.MyCommand.Path)

. .\fv1.ps1

. .\fv2.ps1

. .\fv3.ps1

Pop-Location

És a futtatásának eredménye:

[41] PS C:\powershell2> .\scripts\include.ps1

[42] PS C:\powershell2> fv1

The term 'fv1' is not recognized as the name of a cmdlet, function, script fil

e, or operable program. Check the spelling of the name, or if a path was inclu

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

At line:1 char:4

+ fv1 <<<<

    + CategoryInfo          : ObjectNotFound: (fv1:String) [], CommandNotFoun

   dException

    + FullyQualifiedErrorId : CommandNotFoundException

Na, most mi a hiba? Hát az, hogy bár jól lefutott az include.ps1, de a betöltött függvényszkriptek csak az ő szintjére lettek „dotsource”-olva. Ahhoz, hogy a globális scope-ból is elérhessük ezeket a függvényeket, magát az include.ps1-et is dotsource-szal kell meghívni ([43]-as sorban a plusz pont és szóköz a prompt után):

[43] PS C:\powershell2> . .\scripts\include.ps1

[44] PS C:\powershell2> fv1

Első függvény

[45] PS C:\powershell2> fv2

Második függvény

Így már tökéletesen működik a szkriptkönyvtárunk.

A fenti példában a $myinvocation.mycommand.definition tulajdonságot is használhattuk volna, az is pontosan megadja a szkripünk tényleges helyét.

PowerShell 3.0 óta azonban egy még egyszerűbb módszer is rendelkezésünkre áll, ez pedig a $PSScriptRoot  változó, azaz már három lehetőségünk van, hogy a szkript saját helyének meghatározására:

$PSScriptRoot

 

Split-Path $MyInvocation.MyCommand.Definition

 

Split-Path $MyInvocation.MyCommand.Path

Ha ezt futtatjuk, akkor mindhárom módszer eredményét látjuk:

PS C:\> C:\PSKönyv\psscriptroot.ps1

C:\PSKönyv

C:\PSKönyv

C:\PSKönyv

Ha magára a szkriptünk teljes elérési útjára vagyunk kíváncsiak, azt PowerShell 3.0 utántól a $PSCommandPath  változón keresztül is elérhetjük:

function CheckVars {

    $PSCommandPath

}

 

CheckVars

Ha ezt futtatjuk:

PS C:\> C:\PowerShell\CheckNewVars.ps1

C:\PowerShell\CheckNewVars.ps1

Azaz nem kell a $myinvonvocation-el bajlódni, bár ha visszafelé kompatibilis szkripteket szeretnénk írni, kerülnünk kell az újdonságok használatát.

A $MyInvocation felhasználása parancssor-elemzésre

Nézzük kicsit alaposabban meg ezt a $myinvocation  változót. Ha interaktívan szeretnénk megnézni, akkor ezt kapjuk:

[1] PS C:\> "kakukk"; $myinvocation

kakukk

 

 

MyCommand        : "kakukk"; $myinvocation

ScriptLineNumber : 0

OffsetInLine     : 0

ScriptName       :

Line             :

PositionMessage  :

InvocationName   :

PipelineLength   : 2

PipelinePosition : 1

Látszik, hogy a MyCommand property tartalmazza, hogy mi is az éppen futtatott parancs. Mivel ezt ritkán használjuk interaktívan, nézzük meg, hogy egy szkriptből futtatva mit ad. Maga a szkript nagyon egyszerű:

$myinvocation | fl

És a kimenet:

[7] PS C:\> C:\powershell2\scripts\get-mycommand.ps1

 

 

MyCommand        : get-mycommand.ps1

ScriptLineNumber : 1

OffsetInLine     : -2147483648

ScriptName       :

Line             : C:\powershell2\scripts\get-mycommand.ps1

PositionMessage  :

                   At line:1 char:40

                   + C:\powershell2\scripts\get-mycommand.ps1 <<<<

InvocationName   : C:\powershell2\scripts\get-mycommand.ps1

PipelineLength   : 1

PipelinePosition : 1

Nézzük meg, hogy egy függvényben hogyan alakul ez a változó:

[8] PS C:\> function get-myinvocation {$myinvocation | fl *}

[9] PS C:\> "kakukk" | get-myinvocation

 

 

MyCommand        : get-myinvocation

ScriptLineNumber : 1

OffsetInLine     : -2147483648

ScriptName       :

Line             : "kakukk" | get-myinvocation

PositionMessage  :

                   At line:1 char:27

                   + "kakukk" | get-myinvocation <<<<

InvocationName   : get-myinvocation

PipelineLength   : 1

PipelinePosition : 1

A fenti példákból látszik, hogy elsősorban a MyCommand és a Line tulajdonság hasznos. Ha csak a szűken vett futtatási környezetre vagyunk kíváncsi, akkor a MyCommand kell nekünk, ha a teljes parancssor, akkor Line.

Ezt felhasználva készítsünk egy olyan függvényt, ami megismétli valahányszor az előtte formailag csővezetékként megadott kifejezést ilyen formában:

kifejezés | repeat-commandline 5

Az ezt megvalósító függvény:

[1] PS C:\> function repeat-commandline ([int] $x = 2)

>> {

>>     $s = $myinvocation.line

>>     $last = $s.LastIndexOf("|")

>>     if ($last -lt 0) {throw "Nincs mit ismételni!"}

>>     $cropped = $s.substring(0,$last)

>>     for ($r = 0; $r -lt $x; $r++)

>>     {

>>         invoke-expression $cropped

>>     }

>> }

>> 

[2] PS C:\> "többször" | repeat-commandline 5

többször

többször

többször

többször

többször

A függvény lényegi része a $myinvocation automatikus változó Line tulajdonságának felhasználása. Nekünk itt a teljes parancssor kell, így a Line tulajdonságot használom.  Megkeresem az utolsó csőjelet, (ha nincs ilyen benne, akkor a LastIndexOf metódus eredménye -1 lesz, ilyenkor hibát jelzek) és csonkolom odáig a kifejezést, majd egy ciklussal végrehajtom ezt annyiszor, amennyi a függvénynek átadott paraméter.



Word To HTML Converter