Természetesen a függvény1-nek nem sok értelme van, de például készítsünk egy olyan függvényt, ami kiszámítja egy kör területét. Nem egy konkrét körére gondolok, hanem olyan függvényt szeretnék, amibe paraméterként átadhatom az aktuális kör sugarát, és ebből számítja a területet:
[3] PS C:\> function körterület ($sugár)
>> {
>> $sugár*$sugár*[math]::Pi
>> }
>>
[4] PS C:\> körterület 12
452,38934211693
[5] PS C:\> körterület 1
3,14159265358979
Ez a formátum hasonlatos a hagyományos programnyelvek függvénydefinícióihoz, de a függvényem meghívásának módja kicsit más. Nem kell zárójelet használni, és ha több paraméterem lenne, azok közé nem kell vessző, mert ugye a PowerShellben a vessző a tömb elemeinek szétválasztására való!
Nézzünk egy újabb függvényt, egy négyszög területét akarom kiszámoltatni:
[12] PS C:\> function négyszög ($x, $y)
>> { $x*$y }
>>
[13] PS C:\> négyszög 5 6
30
Mi van akkor, ha egy négyzetnek a területét akarom kiszámoltatni, aminek nincs külön x és y oldala, csak x:
[14] PS C:\> négyszög 7
0
Hát ez nem sikerült, hiszen a függvényem két paramétert várt, az $y nem kapott értéket, így a szorzat értéke 0 lett. Lehetne persze a függvényen belül egy IF feltétellel megvizsgálni, hogy vajon a $y kapott-e értéket, és ha nem, akkor x2-et számolni. Ennél egyszerűbben is lehet ezt elérni a paraméterek inicializálásával:
[15] PS C:\> function négyszög ($x, $y = $x)
>> { $x*$y }
>>
[16] PS C:\> négyszög 7
49
Itt a [15]-ös sorban az $y változónak átadom az $x értékét. Ez az értékadás azonban csak akkor jut érvényre, ha a függvény meghívásakor én nem adok neki külön értéket, azaz továbbra is működik a téglalap területének kiszámítása is:
[17] PS C:\> négyszög 8 9
72
Megjegyzés
Mi lenne, ha a négyszög függvény meghívására a hagyományos, zárójeles formátumot használnám most:
[22] PS C:\> négyszög(2,3)
Cannot convert "System.Object[]" to "System.Int32".
At line:2 char:6
+ { $x*$ <<<< y }
Meg is kaptam a hibát, hiszen a függvényem nincs felkészülve egy kételemű tömb területének kiszámítására.
A paramétereknek nem csak másik változó adhat alapértéket, hanem tehetünk oda egy konstanst is:
[18] PS C:\> function üdvözlet ($név = "Tibi")
>> { "Szia $név!" }
>>
[19] PS C:\> üdvözlet Zsófi
Szia Zsófi!
[20] PS C:\> üdvözlet
Szia Tibi!
Hasonlóan, ahogy a változóknál is definiálhatunk típust, a függvények paramétereinél is jelezhetem, hogy milyen típusú adatot várok. Ez a függvény hibás működésének lehetőségét csökkentheti.
Nézzük a körterület-számoló függvényemet, mit tesz, ha szövegként adok be neki sugarat:
[33] PS C:\> körterület "2"
222222
Elég érdekesen alakul ez a geometria a PowerShellben! De eddigi ismereteink alapján rájöhetünk, hogy mi történt. Ugye a függvényünk belsejében ez található:
$sugár*$sugár*[math]::Pi
Az első $sugár a PowerShell szabályai szerint lehet „2” (szövegként). A második már nem, viszont működik az automatikus típuskonverzió, tehát csinál belőle 2-t (szám). Idáig kapunk „22”-t, azaz az első sztringemet („2”) kétszer egymás mellett. Majd jön a PI, ugyan az nem egész, de itt is jön a típuskonverzió, lesz belőle 3, azaz immár a „22”-t rakja egymás mellé háromszor, így lesz belőle „222222”.
Na, ezt nem szeretnénk, ezért tegyük típusossá a sugarat:
[42] PS C:\> function körterület ([double] $sugár)
>> {
>> $sugár*$sugár*[math]::Pi
>> }
>>
[43] PS C:\> körterület "2"
12,5663706143592
Itt már helyes irányba tereltük a PowerShell típuskonverziós automatizmusát, rögtön a paraméterátadásnál konvertálja a szöveges 2-t számmá, innentől már nincs félreértés.
Megjegyzés
Az is helyes eredményt adott volna, ha felcseréltük volna a tényezők sorrendjét:
[math]::Pi *$sugár*$sugár
De elegánsabb és „hibatűrőbb”, ha inkább típusjelzőkkel látjuk el a paramétereket.
Az előző körterület függvényemnél, ha a paraméter nem konvertálható [double] típussá, akkor hibát kapunk, anélkül, hogy mi ezt leprogramoztuk volna:
[19] PS C:\> körterület "nagy"
körterület :
Cannot process argument transformation on parameter 'sugár'. Cann
ot convert
value "nagy" to type "System.Double". Error: "Input
string was not
in a correct
format."
At line:1
char:11
+ körterület
<<<< "nagy"
+ CategoryInfo : InvalidData: (:) [körterület],
ParameterBindin.
..mationException
+ FullyQualifiedErrorId :
ParameterArgumentTransformationError,körterület
Viszont elképzelhető olyan paraméterérték is, ami bár nem okoz problémát közvetlenül se a típusos paraméternek, se a függvény belsejének, de mi mégis szeretnénk hibaként kezelni. A körterület példámnál tekintsük hibának, ha valaki negatív számot ad meg sugárnak. A fentihez hasonló formátumú (piros betűk) hibaüzenetet írattathatunk ki a throw kulcsszó segítségével:
[22] PS C:\>function körterület ([double] $sugár)
>> {
>> if ($sugár -lt 0)
>> { throw "Pozitív számot kérek!" }
>> $sugár*$sugár*[math]::Pi
>> }
>>
[23] PS C:\>körterület -2
Pozitív
számot kérek!
At line:4
char:17
+ { throw <<<< "Pozitív számot kérek!" }
+ CategoryInfo : OperationStopped: (Pozitív számot
kérek!:String
) [], RuntimeException
+ FullyQualifiedErrorId : Pozitív számot
kérek!
De nem csak ilyen esetben lehet szükség hibakezelésre, hanem akkor is, ha valaki nem ad meg paramétert. Egy paraméterkötelező vagy opcionális voltát majd a 2.5 Fejlett függvények – script cmdletek fejezetben, a fejlett függvények függvénydefiníciójában tudjuk majd igazán profi módon beállítani, de most itt egyszerűen a throw segítségével végezzük ezt el:
[29] PS C:\>function körterület ([double] $sugár = $(throw "kötelező paramét
er"))
>> {
>> if ($sugár -lt 0)
>> { throw "Pozitív számot kérek!" }
>> $sugár*$sugár*[math]::Pi
>> }
>>
[30] PS C:\>körterület
kötelező
paraméter
At line:1
char:47
+ function
körterület ([double] $sugár = $(throw <<<< "kötelező paraméter"))
+ CategoryInfo : OperationStopped: (kötelező
paraméter:String) [
], RuntimeException
+ FullyQualifiedErrorId : kötelező
paraméter
[31] PS C:\>körterület 5
78,5398163397448
Látszik, hogy már az értékadásnál használhatjuk a hibajelzést, mint alapértéket. Ha nem ad a felhasználó paraméterként értéket, akkor végrehajtódik a hibakezelés, viszont ha ad értéket, akkor természetesen erre nem kerül sor.
Előfordulhat olyan is, hogy nem tudjuk előre, hogy hány paraméterünk lesz egy függvényben. Például szeretnénk kiszámolni tetszőleges darabszámú sztring hosszának az átlagát. Ilyenkor dilemmába kerülhetünk, hiszen hány paramétert soroljunk fel? Hogyan inicializáljuk ezeket?
Szerencsére nem kell ezen töprengenünk, mert a PowerShell készít automatikusan egy $args nevű változót, amely tartalmazza a függvénynek átadott valamennyi olyan paramétert, amelyet úgy adunk át, hogy nem névvel hivatkozunk rájuk:
[25] PS C:\> function átlaghossz
>> {
>> $hossz = 0
>> if($args)
>> {
>> foreach($arg in $args)
>> {$hossz += $arg.length}
>> $hossz = $hossz/$args.count
>> }
>> $hossz
>> }
>>
[26] PS C:\> átlaghossz "Egy" "kettő" "három" "négy"
4,25
Az átlaghossz függvényemnek ezek alapján nincs explicit paramétere, azaz névvel nem is lehet hivatkozni a paramétereire, viszont implicit módon az $args tömbön keresztül természetesen megkapja mindazt a paramétert, amelyet átadunk neki. Ebben az esetben azonban nehezebben tudjuk kézben tartani a paraméterek típusát, ezt is majd a fejlett függvényekkel tudjuk elvégezni ( 2.5 Fejlett függvények – script cmdletek fejezet).
Mi van az $args változóval, ha vannak explicit paramétereink is? Nézzünk erre egy példát:
[46] PS C:\> function mondatgenerátor ($eleje, $vége)
>> {
>> write-host $eleje, $args, $vége
>> }
>>
[47] PS C:\> mondatgenerátor "Egy" "kettő" "három" "négy"
Egy három négy kettő
Láthatjuk, hogy úgy működik a PowerShell, ahogy vártuk, a nevesített paraméterek nem kerülnek bele az $args tömbbe, így a fenti függvényemben egyetlen argumentum sem lett duplán kiírva.
Eddig az összes függvénypéldámban a függvények meghívásakor a paraméterátadás pozíció alapján történt. Ez egyszerűen azt jelenti, hogy az első paraméter átadódik a függvénydefiníció paraméterei között felsorolt első változónak, a második paraméter a második változónak és így tovább. Ha több paramétert adunk át, mint amennyit a függvény definíciójában felsoroltunk, akkor ezek az extra paraméterek az $args változóba kerülnek bele.
Mi van akkor, ha egy függvénynek opcionális paramétere is van, és nem akarok neki értéket adni? Ezt eggyel több szóköz leütésével jelezzem? Alakítsuk át ennek szemléltetésére az előző „mondatgenerátor” függvényemet:
[48] PS C:\> function mondatgenerátor ($eleje, $közepe, $vége)
>> {
>> write-host "Eleje: $eleje közepe: $közepe vége: $vége"
>> }
>>
[49] PS C:\> mondatgenerátor egy kettő három
Eleje: egy közepe: kettő vége: három
[50] PS C:\> mondatgenerátor egy három
Eleje: egy közepe: három vége:
Itt három explicit paramétert fogad a függvény. Ha tényleg három paraméterrel hívom meg, akkor minden paraméter a helye alapján kerül a megfelelő helyre. Ha a második paramétert ki szeretném hagyni, akkor ezt hiába akarom jelezni eggyel több szóközzel ([50]-es sor), ez természetesen a PowerShell parancsértelmezőjét nem hatja meg, így más módszerhez kell folyamodnom, ez pedig a név szerinti paraméterátadás:
[55] PS C:\> mondatgenerátor -eleje egy -vége három
Eleje: egy közepe: vége: három
Így már tényleg minden a helyére került. A név szerinti paraméterátadásnál is számos gépelést segítő lehetőséget biztosít számunkra a PowerShell:
[56] PS C:\> mondatgenerátor -e egy -v három
Eleje: egy közepe: vége: három
[57] PS C:\> mondatgenerátor -e:egy -v:három
Eleje: egy közepe: vége: három
Az [56]-os sorban látható, hogy a nevekre olyan röviden elég hivatkozni, ami még egyértelművé teszi, hogy melyik paraméterre is gondolunk. Az [57]-es sorban meg az látható, hogy a PowerShell elfogadja a sok egyéb parancssori környezetben megszokott paraméterátadási szintaxist is, azaz hogy kettőspontot teszünk a paraméternév után.
A hely szerinti és pozíció szerinti paraméterhivatkozást vegyíthetjük is:
[58] PS C:\> mondatgenerátor -v: három egy -k: kettő
Eleje: egy közepe: kettő vége: három
Itt a szabály az, hogy a PowerShell először kiveszi a függvényhívásból a nevesített paramétereket, majd hely szerint feltölti a többi explicit paramétert, és ha még mindig marad paraméter, azt berakja az $args változóba.
Nézzük, hogy hogyan lehet még paramétereket átadni. Vegyük példa gyanánt az előző mondatgenerátor függvényt. Egy változóban vannak tömbként az egyes paraméterek, vajon hogyan lehet a tömb elemeit átadni rendre az egyes paraméterek gyanánt?
[2] PS C:\> $v = "a", "b", "c"
[3] PS C:\> mondatgenerátor $v
Eleje: a b c közepe: vége:
A [2]-es sorban látható a paramétereke tartalmazó tömböm, de ha ezt adom át a mondatgenerátoromnak, akkor nem azt kapom, amit szerettem volna. Az átadott változó mint egy egység adódott át az első, „eleje” paraméterként, és a többi paraméternek nem jutott érték.
A PowerShell 2.0-ban bevezettek egy új operátort, a „szétpasszírozás” operátorát (angolul splatting – szétfröccsenés) , amit pont arra találtak ki, hogy egy tömbváltozó elemeit adja át rendre a függvény vagy cmdlet paramétereiként:
[4] PS C:\> mondatgenerátor @v
Eleje: a közepe: b vége: c
Ez pont azt az eredményt adta, amit szerettem volna. Enélkül elég bonyolult lett volna ezt a fajta paraméterátadást megoldani.
Nézzük, hogy hogyan lehet név alapján átadni a paramétereket a szétpasszírozás operátorával. Ilyenkor a változóba hashtáblát kell tenni:
[7] PS C:\> $v = @{ közepe = "b"; vége = "c"; eleje = "a"}
[8] PS C:\> $v
Name Value
---- -----
eleje a
közepe b
vége c
[9] PS C:\> mondatgenerátor @v
Eleje: a közepe: b vége: c
A fenti példában láthatjuk, hogy a paraméterek nem a sorrendjük, hanem a nevük alapján lettek átadva.
Sok függvénynél előfordul olyan paraméter, amelynek csak két állapota van: szerepeltetjük a paramétert vagy sem. Ez nagyon hasonlít a [bool] adattípushoz, de feleslegesen sokat kellene gépelni, ha tényleg [bool]-t használnánk:
függvény -bekapcsolva
$true
Sokkal egyszerűbb lenne csak ennyit írni:
függvény -bekapcsolva
Erre találták ki a [switch] adattípust, amelyet elsődlegesen pont ilyen paraméterek esetében használunk.
[60] PS C:\> function lámpa ([switch] $bekapcsolva)
>> {
>> if($bekapcsolva) {"Világos van!"}
>> else {"Sötét van!"}
>> }
>>
[61] PS C:\> lámpa
Sötét van!
[62] PS C:\> lámpa -b
Világos van!
A fenti „lámpa” függvényemet, ha kapcsoló nélkül használom, akkor „sötétet” jelez (az IF ELSE ága), ha van kapcsoló, akkor „világos”. Azaz, ha szerepel a paraméterek között a kapcsoló, akkor változójának értéke $true lesz, ha nem szerepel a kapcsoló, akkor változójának értéke $false lesz.
Megjegyzés
Korábban volt arról szó, hogy a paraméterek név szerinti hivatkozása mellett akár lehet szóközzel, akár kettősponttal értéket adni. Ez a [switch] paramétertípusnál nem mindegy:
[63] PS C:\old> lámpa -b $false
Világos van!
[64] PS C:\old> lámpa -b:$false
Sötét van!
A [63]-as sorban szóközzel választottam el a paramétertől az értéket, ezt a függvényem figyelmen kívül hagyta és a kapcsolónak $true értéket adott. A [64]-es sorban kettősponttal adtam a paraméternek értéket, itt már az elvárt módon $false értéket vett fel a kapcsolóm.
Ha valaki olyan programozói háttérrel rendelkezik, ahol megszokta, hogy a függvénydefinícióknál van egy külön deklarációs rész, akkor használhatják a param kulcsszót a függvény paramétereinek deklarálásához:
[2] PS I:\>function get-random
>> {
>> param ([double] $max = 1)
>> $rnd = new-object random
>> $rnd.NextDouble()*$max
>> }
>>
[3] PS I:\>get-random 1000
315,589330306085
Itt a get-random függvényem neve után nem szerepel semmilyen paraméter, hanem beköltöztettem a függvénytörzsbe, itt viszont kötelezően egy param kulcsszóval kell jelezni a paramétereket. Ez akkor is hasznos lehet, ha bonyolultabb kifejezésekkel adok kezdőértéket a paramétereimnek, mert ezzel a külön deklarációs résszel sokkal olvashatóbb lesz a függvényem.
Ennek paraméterezési lehetőségnek egyébként nem is itt, hanem majd a szrkipteknél lesz még inkább jelentősége.
Természetesen a paraméterek ellenőrzése akkor a legegyszerűbb, ha eleve csak a kívánalmainknak megfelelő értékeket lehetne átadni. A típus megfelelőségének vizsgálatához már láthattuk, egyszerűen csak egy típusjelölőt kell a paraméter neve előtt szerepeltetni. De vajon milyen lehetőségünk van, ha még további megszorításokat szeretnénk tenni?
Ennek egyik lehetősége, hogy a függvényünk törzsében saját programkóddal ellenőrizzük az érték helyességét. Például nézzünk a korábban már látott körterület kiszámító függvényt:
[22] PS C:\>function körterület ([double] $sugár)
>> {
>> if ($sugár -lt 0)
>> { throw "Pozitív számot kérek!" }
>> $sugár*$sugár*[math]::Pi
>> }
>>
Itt én ellenőrzöm, hogy csak pozitív számot lehessen megadni sugárnak. A .NET keretrendszerben azonban vannak kifejezetten ilyen jellegű, paraméterellenőrzésre szolgáló osztályok is. Ilyen szabályokat hozzá is rendelhetünk változókhoz, erre szolgál a változók tulajdonságai között az Attributes tulajdonság:
[1] PS C:\> $a = 1
[2] PS C:\> Get-Variable a | fl
Name : a
Description :
Value : 1
Options : None
Attributes : {}
Alaphelyzetben ez a tulajdonság üres, vajon hogyan és mivel lehet feltölteni? Nézzük meg, hogy milyen metódusai vannak ennek az attribútumnak:
[8] PS C:\> ,(Get-Variable a).Attributes | gm
TypeName: System.Management.Automation.PSVariableAttributeCollection
Name MemberType Definition
---- ---------- ----------
Add Method System.Void Add(Attribute item)
Clear Method System.Void Clear()
Contains Method System.Boolean Contains(Attribute item)
CopyTo Method System.Void CopyTo(Attribute[] array...
Equals Method System.Boolean Equals(Object obj)
GetEnumerator Method System.Collections.Generic.IEnumerat...
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
get_Count Method System.Int32 get_Count()
get_Item Method System.Attribute get_Item(Int32 index)
IndexOf Method System.Int32 IndexOf(Attribute item)
Insert Method System.Void Insert(Int32 index, Attr...
Remove Method System.Boolean Remove(Attribute item)
RemoveAt Method System.Void RemoveAt(Int32 index)
set_Item Method System.Void set_Item(Int32 index, At...
ToString Method System.String ToString()
Item ParameterizedProperty System.Attribute Item(Int32 index) {...
Count Property System.Int32 Count {get;}
Tehát van neki Add metódusa, ezzel lehet ellenőrzési objektumokat hozzáadni ehhez az attribútumhoz. A PowerShell MSDN weboldalán:
És a Reflector programmal a System.Management.Automation névtérbe beásva ezeket az ellenőrzési osztályokat lehet megtalálni:
Osztály
neve |
Felhasználási terület |
||
System.Management.Automation.ValidateArgumentsAttribute
|
Szülő
osztály |
||
System.Management.Automation.ValidateCountAttribute |
Attribútumok számának ellenőrzése |
||
System.Management.Automation.ValidateEnumeratedArgumentsAttribute |
Szülő osztály |
||
System.Management.Automation.ValidateLengthAttribute |
Hossz ellenőrzése (pl.: sztring,
tömb) |
||
System.Management.Automation.ValidatePatternAttribute |
Regex minta ellenőrzése (sztring) |
||
System.Management.Automation.ValidateRangeAttribute |
Értéktartomány ellenőrzése (pl.:
int, double) |
||
System.Management.Automation.ValidateSetAttribute |
Értéklista ellenőrzése (sztring) |
||
System.Management.Automation.ValidateNotNullAttribute |
Nem null érték ellenőrzése |
||
System.Management.Automation.ValidateNotNullOrEmptyAttribute |
Nem null és nem üresség
ellenőrzése |
Nézzünk ezek használatára néhány példát. Az elsőben szeretnék olyan változót használni, amely csak 1 és 5 darab karakter közötti hosszúságú szöveget fogad el:
[87] PS C:\old>$vl = New-Object System.Management.Automation.ValidateLengthA
ttribute 1,5
[88] PS C:\old>$szó = "rövid"
[89] PS C:\old>(get-variable szo).attributes.add($vl)
[90] PS C:\old>$szó="nagyonhosszú"
Cannot validate because of invalid value (nagyonhosszú) for variable szo.
At line:1 char:5
+ $szo= <<<< "nagyonhosszú"
A [87]-es sorban definiálom a VaildateLengthAttribute osztály egy objektumát. Majd létrehozok egy $szó nevű változót „rövid” tartalommal. Ennek a változónak az Attributes tulajdonságához hozzáadom az előbb létrehozott ellenőrzési objektumot. A [90]-es sorban a változómnak egy 5 karakternél hosszabb értéket szeretnék adni, de erre hibát kaptam.
Nézzünk egy hasonló példát az üresség vizsgálatára:
[94] PS C:\old>$s2="valami"
[95] PS C:\old>$vnne = New-Object System.Management.Automation.ValidateNotNu
llOrEmptyAttribute
[96] PS C:\old>(get-variable s2).Attributes.add($vnne)
[97] PS C:\old>$s2=""
Cannot validate because of invalid value () for variable s2.
At line:1 char:4
+ $s2= <<<< ""
Ha egy ilyen ellenőrzési attribútummal látunk el egy változót, akkor az megmutatkozik a változó tulajdonságai között is:
[98] PS C:\old>Get-Variable s2 | fl *
Name : s2
Description :
Value : valami
Options : None
Attributes : {System.Management.Automation.ValidateNotNullOrEmptyAttribute
}
Ezekből az ellenőrzési objektumokból egyszerre többet is lehet egy változóhoz rendelni.
Sajnos ezen ellenőrzési objektumok felhasználásának egyik fő nehézsége az, hogy ahhoz, hogy ilyen szabályt egy változóhoz rendelhessük, ahhoz addigra már a változónak olyan értékkel kell rendelkeznie, amelyre teljesül az ellenőrzési feltétel. Azaz, ha például egy NotNull típusú ellenőrzést szeretnénk hozzárendelni egy változóhoz, addigra már valamilyen értékkel kell rendelkeznie a változónak, különben nem engedi a szabályt hozzárendelni. Így a függvények paramétereinél történő felhasználásuk kicsit körülményes:
[9] PS C:\> function nemnull ($p)
>> {
>> $pteszt = 1
>> $vnn = New-Object System.Management.Automation.ValidateNotNullAttribu
te
>> (Get-Variable pteszt).Attributes.Add($vnn)
>>
>> $pteszt = $p
>> }
>>
[10] PS C:\> nemnull 100
[11] PS C:\> nemnull
Cannot validate because of invalid value () for variable pteszt.
At line:6 char:12
+ $pteszt <<<< = $p
Azaz a függvény törzsében létrehozok egy tesztelési célokat szolgáló $pteszt változót, ami kielégíti azt a feltételt, hogy nem üres az értéke, és ez a változó hordozza az ellenőrzési objektumot is. A függvény paraméterének ellenőrzésére a $p változó értékét átadom ennek a tesztelési célra létrehozott változónak. Amikor a függvényemet paraméter nélkül hívtam meg, akkor hibát kaptam. Itt igazából csak annyit spóroltam, hogy nem nekem kell lekezelni és kiírni a hibát, ezt a PowerShell helyettem elvégezte.
Az igazi, profi paraméterellenőrzést a PowerShell 2.0 a fejlett függvényeknél már lehetővé teszi, erről a 2.5 Fejlett függvények – script cmdletek fejezetben lesz szó.