A strukturált adatszerkezetek esetében nagyon praktikusan használható adattípus a hashtable , vagy magyarul talán szótárnak vagy asszociatív tömbnek lehetne hívni.
[9] PS C:\> $hash = @{ Név = "Soós Tibor"; Cím = "Budapest"; "e-mail"="soostibo
r@citromail.hu"}
[10] PS C:\> $hash
Name Value
---- -----
Név Soós Tibor
e-mail soostibor@citromail.hu
Cím Budapest
A hashtábla jelölése tehát egy kukac-kapcsos zárójelpár (@{} ) jellel és egy lezáró kapcsos zárójellel (}) jellel történik. Belül kulcs=érték párokat kell elhelyezni. A kulcs nevét csak akkor kell idézőjelezni, ha az valami speciális karaktert (pl. szóköz, kötőjel, stb.) tartalmaz. Egyébként a kulcs nem feltétlenül szöveg, akármilyen adattípus lehet, de azért általában szövegeket szoktunk használni. Az érték megadásánál az eddig megszokott formákat kell alkalmazni.
Hogyan tudok vajon egy újabb személyt felvenni ebbe a hashtáblába? Nézzük meg ehhez a hashtábla tagjellemzőit:
[12] PS C:\> $hash | Get-Member | ft -wrap
TypeName: System.Collections.Hashtable
Name MemberType Definition
---- ---------- ----------
Add Method System.Void Add(Object key, Object
value)
Clear Method System.Void Clear()
Clone Method System.Object Clone()
Contains Method System.Boolean Contains(Object key
)
ContainsKey Method System.Boolean ContainsKey(Object
key)
ContainsValue Method System.Boolean ContainsValue(Objec
t value)
CopyTo Method System.Void CopyTo(Array array, In
t32 arrayIndex)
Equals Method System.Boolean Equals(Object obj)
GetEnumerator Method System.Collections.IDictionaryEnum
erator GetEnumerator()
GetHashCode Method System.Int32 GetHashCode()
GetObjectData Method System.Void GetObjectData(Serializ
ationInfo info, StreamingContext c
ontext)
GetType Method System.Type GetType()
get_Count Method System.Int32 get_Count()
get_IsFixedSize Method System.Boolean get_IsFixedSize()
get_IsReadOnly Method System.Boolean get_IsReadOnly()
get_IsSynchronized Method System.Boolean get_IsSynchronized(
)
get_Item Method System.Object get_Item(Object key)
get_Keys Method System.Collections.ICollection get
_Keys()
get_SyncRoot Method System.Object get_SyncRoot()
get_Values Method System.Collections.ICollection get
_Values()
OnDeserialization Method System.Void OnDeserialization(Obje
ct sender)
Remove Method System.Void Remove(Object key)
set_Item Method System.Void set_Item(Object key, O
bject value)
ToString Method System.String ToString()
Item ParameterizedProperty System.Object Item(Object key) {ge
t;set;}
Count Property System.Int32 Count {get;}
IsFixedSize Property System.Boolean IsFixedSize {get;}
IsReadOnly Property System.Boolean IsReadOnly {get;}
IsSynchronized Property System.Boolean IsSynchronized {get
;}
Keys Property System.Collections.ICollection Key
s {get;}
SyncRoot Property System.Object SyncRoot {get;}
Values Property System.Collections.ICollection Val
ues {get;}
Láthatjuk, az Add metódust, így nézzük meg, azzal mit kapunk:
[13] PS C:\> $hash.Add("Név","Fájdalom Csilla")
Exception calling "Add" with "2"
argument(s): "Item has already been added. Ke
y in dictionary: 'Név' Key being added: 'Név'"
At line:1 char:10
+ $hash.Add <<<<
("Név","Fájdalom Csilla")
+
CategoryInfo : NotSpecified: (:)
[], MethodInvocationException
+
FullyQualifiedErrorId : DotNetMethodException
Hát erre bizony hibajelzést kaptam, mert nem szereti ez az adatszerkezet, ha ugyanolyan kulccsal még egy értéket akarok felvenni. Csak megváltoztatni tudom az adott kulcshoz tartozó értéket:
[17] PS C:\> $hash.set_Item("Név","Fájdalom Csilla")
[18] PS C:\> $hash
Name Value
---- -----
e-mail soostibor@citromail.hu
Cím Budapest
Név Fájdalom Csilla
[19] PS C:\> $hash["Név"]="Beléd Márton"
[20] PS C:\> $hash
Name Value
---- -----
e-mail soostibor@citromail.hu
Cím Budapest
Név Beléd Márton
[21] PS C:\> $hash.Név = "Pandacsöki Boborján"
[22] PS C:\> $hash
Name Value
---- -----
e-mail soostibor@citromail.hu
Cím Budapest
Név Pandacsöki Boborján
A fenti példában látszik, hogy három különböző szintaxissal is lehet módosítani értékeket ([17], [19] és [21]-es sorok).
Lekérdezni az értékeket is a fenti lehetőségekhez hasonlóan lehet:
[27] PS C:\> $hash.Név
Pandacsöki Boborján
[28] PS C:\> $hash["Név"]
Pandacsöki Boborján
[29] PS C:\> $hash.get_item("Név")
Pandacsöki Boborján
Szóval a lehetőségek elég széles tárháza áll rendelkezésre. Külön lekérhetjük a hashtábla kulcsait és értékeit is:
[32] PS C:\> $hash.keys
adat
Cím
Név
[33] PS C:\> $hash.values
soostibor@citromail.hu
12345
Budapest
Pandacsöki Boborján
Ha a hashtáblát a @{} jelekkel hozzuk létre, akkor a kulcsok kis-nagybetű érzéketlen, azaz:
PS C:\> $h = @{}
PS C:\> $h.a = 5
PS C:\> $h.A
5
Tehát a fenti $h.a-t meg lehet kapni a $h.A-val is. Ha kis-nagybetű érzékeny hashtáblát szeretnénk, akkor hozzuk létre a hashtáblát a New-Object cmdlet segítségével így:
PS C:\> $hcs = New-Object -TypeName hashtable
PS C:\> $hcs.a = 10
PS C:\> $hcs.A
PS C:\> $hcs.A = 20
PS C:\> $hcs
Name Value
---- -----
A 20
a 10
De visszatérve a problémámhoz, hogyan lehet még egy ember adatait berakni ebbe a hashtáblába? Egy hashtáblába sehogy, de nagyon egyszerűen létre tudunk hozni egy hashtábla-tömböt, méghozzá abból a System.Collections.ArrayList fajtából, amit tudunk bővíteni:
[37] PS C:\> $névjegyek = New-Object system.collections.arraylist
[38] PS C:\> $névjegyek.Add(@{Név="Soós Tibor";
"e‑mail"="soostibor@citromail.hu";Cím="Budapest"})
0
[39] PS C:\> $névjegyek.Add(@{Név="Fájdalom Csilla";
"e‑mail"="fcs@citromail.hu";Cím="Zamárdi"})
1
[40] PS C:\> $névjegyek
Name Value
---- -----
e-mail soostibor@citromail.hu
Cím Budapest
Név Soós Tibor
e-mail fcs@citromail.hu
Cím Zamárdi
Név Fájdalom Csilla
[41] PS C:\> $névjegyek.count
2
[42] PS C:\> $névjegyek[0]
Name Value
---- -----
e-mail soostibor@citromail.hu
Cím Budapest
Név Soós Tibor
[43] PS C:\> $névjegyek[1]
Name Value
---- -----
e-mail fcs@citromail.hu
Cím Zamárdi
Név Fájdalom Csilla
[44] PS C:\> $névjegyek[1].Név
Fájdalom Csilla
[45] PS C:\> $névjegyek[1]."e-mail"
fcs@citromail.hu
Így ezzel a hashtábla-tömbbel adatbázis-szerű alkalmazási területek adatleképezési igényeit is nagyon jól ki lehet elégíteni.
Hashtáblák definiálására a 2.0-ás PowerShellben egy új cmdlet is rendelkezésünkre áll, hogy még egyszerűbben definiálhassunk ilyen adattípust, ez pedig a ConvertFrom-StringData :
[58] PS C:\> $hashstring = @"
>> első = Ez itt az első érték
>> második = az érték részt nem is kell idézőjelezni
>> harmadik = soostibor@citromail.hu
>> negyedik = 4
>> ötödik = 2009.11.21
>> "@
>>
[59] PS C:\> $hash = ConvertFrom-StringData $hashstring
[60] PS C:\> $hash
Name Value
---- -----
negyedik 4
második az érték részt nem is kell idézőjelezni
ötödik 2009.11.21
harmadik soostibor@citromail.hu
első Ez itt az első érték
[61] PS C:\> $hash.első
Ez itt az első érték
[62] PS C:\> ($hash.negyedik).gettype().fullname
System.String
[63] PS C:\> ($hash.ötödik).gettype().fullname
System.String
Mint ahogy látható, a hashtábla egy többsoros sztringből képződik. A sztring egyes sorait úgy kell felépíteni, hogy a baloldalon legyen egy kulcsnév, aztán egy egyenlőségjel és jobb oldalon egy érték. Vigyázat, ezen a módon csak szöveges értékeket tudunk a hashtáblába felvenni. Hiába adtam meg számnak és dátumnak kinéző sorokat a negyedik és ötödik kulcsként, ahogy a [62]-es és [63]-as sorokban látszik ezek mind egyszerű szövegként lettek betöltve a hashtáblába.
Fontos megjegyezni, hogy a hashtáblák kiíratásakor az egyes kulcsok nem előre meghatározott módon íródnak ki. Maga a „hash” szó, azaz magyarul „kivonat” onnan jön az adattípus nevében, hogy a belső tárolási módja ezeknek úgy történik, hogy a kulcsból képződik egy kivonat, ami alapján lesznek az adatok ténylegesen tárolva, és ez alapján gyorsan tudja ellenőrizni a .NET keretrendszer, hogy az adott kulcs már létezik-e, illetve az adatok kiolvasásában, a kulcsok megkeresésében is van szerepe.
Alaphelyzetben tehát össze-vissza az elemek sorrendje:
PS C:\> $hash = @{"ab" = 1; "bb" = 2; "cb" = 3; "db" = 4}
PS C:\> $hash
Name Value
---- -----
db 4
cb 3
ab 1
bb 2
Hogyan lehetne ABC rendben kiíratni a hashtábla adatait? Próbálkozzunk a Sort-Object-el direktben:
PS C:\> $hash | Sort-Object -Property Name
Name Value
---- -----
db 4
cb 3
ab 1
bb 2
Nem történt semmi! Ennek magyarázata az, hogy a hashtábla a futószalagon egy egységként, egy darabban vándorol, annak ellenére, hogy látszólag itt is több objektum látszik a konzolon, de ez csak az ellenség megtévesztése:
PS C:\> @($hash).count
1
Ahhoz, hogy a hashtábla elemeit sorba tudjuk rendezni, fel kell darabolni azt. Erre a nagyon hasznos GetEnumerator() metódus áll rendelkezésre:
PS C:\> @($hash.GetEnumerator()).count
4
Ezzel tényleg soronként 1-1 objektumot kapunk Name és Value tulajdonságokkal és így már könnyedén sorba tudjuk rendezni az elemeket:
PS C:\> $hash.GetEnumerator() | Sort-Object -Property Name
Name Value
---- -----
ab 1
bb 2
cb 3
db 4
De ennek végén már nem hashtábla az objektumunk! Ha mindenképpen fontos a sorrendiség, akkor használhatjuk a .NET keretrendszer egy másik osztályát, a System.Collections.SortedList -et, ami nagyon hasonlít a hashtáblához:
[54] PS C:\> $sl = new-object system.collections.sortedlist
[55] PS C:\> $sl.ab = 1
[56] PS C:\> $sl.bb = 2
[57] PS C:\> $sl.cd = 3
[58] PS C:\> $sl.db = 4
[59] PS C:\> $sl
Name Value
---- -----
ab 1
bb 2
cd 3
db 4
A fő különbség, hogy itt a kulcsok abc rendjében jelennek meg az elemek, sőt, ha beillesztek egy új elemet, akkor azt is természetesen az abc sorrendben jeleníti meg:
[62] PS C:\> $sl.aa = "új elem"
[63] PS C:\> $sl
Name Value
---- -----
aa új elem
ab 1
bb 2
cd 3
db 4
Ahogy a típusos tömbnél láttuk az általános (generic) adattípust, úgy a hashtáblának is van ilyenje. Ezzel olyan speciális hashtáblát tudunk létrehozni, melynek akár a kulcs mezőjének, akár az érték mezőjének típusát meg tudjuk szabni.
[14] PS C:\> $genhash = New-Object "collections.generic.dictionary[string,int]"
[15] PS C:\> $genhash.elem1 = 1
[16] PS C:\> $genhash.elem2 = "valami"
The value "valami" is not of type
"System.Int32" and cannot be used in this ge
neric collection.
Parameter name: value
At line:1 char:10
+ $genhash. <<<< elem2 = "valami"
+
CategoryInfo : InvalidOperation:
(:) [], RuntimeException
+
FullyQualifiedErrorId : PropertyAssignmentException
A fenti példában olyan hashtáblát hoztam létre, ahol a kulcs mező csak sztring lehet, és az értékek meg csak egészek. Ha egész helyett egy szöveget akartam betölteni, akkor hibát kaptam.
A PowerShell 3.0 verziótól kezdve a szótár adattípusnak van egy beadási sorrendet megtartó változata, amit az [ordered] típusjelölővel tudunk definiálni:
PS C:\> $sorrendtartó = [ordered] @{}
PS C:\> $sorrendtartó.egy = 1
PS C:\> $sorrendtartó.kettő = 2
PS C:\> $sorrendtartó.három = 3
PS C:\> $sorrendtartó
Name Value
---- -----
egy 1
kettő 2
három 3
Látható, hogy itt a sima hashtáblához képest az elemek nem véletlenszerűen, hanem megadásuk sorrendjében lesznek kiolvasva. Ennek a típusnak a hivatalos neve:
PS C:\> $sorrendtartó.GetType().fullname
System.Collections.Specialized.OrderedDictionary
A PowerShell 3.0-tól kezdődően néhány apróbb változás is megjelent a hashtáblák kezelésében. Például nem olyan egyszerű azonosnak látszó, csak típusában különböző címkékkel adatokat bevinni:
PS C:\> $hash3 = @{1 = "int"; "1" = "string"}
At line:1 char:23
+ $hash3 = @{1 = "int"; "1" = "string"}
+ ~~~
Duplicate keys '1' are not allowed in hash literals.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordE
xception
+ FullyQualifiedErrorId : DuplicateKeyInHashLiteral
Ez a 2.0-ás PowerShellben még simán ment, annak ellenére, hogy itt is természetesen helyesen kezeli a típusokat:
PS C:\> $hash3 = @{1 = "int"}
PS C:\> $hash3.GetEnumerator()[0].name.gettype().fullname
System.Int32
PS C:\> $hash3 = @{"1" = "int"}
PS C:\> $hash3.GetEnumerator()[0].name.gettype().fullname
System.String
Azér PowerShell 3.0-ban is ki tudjuk erőltetni ezt a működést, csak a szám formátumú kulcsot zárójelbe kell tenni:
PS C:\> $h = @{(1) = "int"; "1" = "string"}