Sdílet prostřednictvím


Všechno, co jste chtěli vědět o hashtables

Chci udělat krok zpět a mluvit o hashtables. Použiju je celou dobu. Vyučovala jsem někoho o nich po naší schůzce skupiny uživatelů včera večer a uvědomila jsem si, že jsem o nich měla stejné nejasnosti jako on. Hashtables jsou v PowerShellu opravdu důležité, takže je dobré je dobře pochopit.

Poznámka:

Původní verze tohoto článku se objevila na blogu napsaného @KevinMarquette. Tým PowerShellu děkujeme Kevinovi za sdílení tohoto obsahu s námi. Prosím, podívejte se na jeho blog na PowerShellExplained.com.

Hashovací tabulka jako kolekce věcí

Chci, abyste nejprve viděli Hashtable jako kolekci podle tradiční definice Hashtable. Tato definice vám poskytne základní znalosti o tom, jak fungují, když se později použijí pro pokročilejší věci. Přeskočení tohoto porozumění je často zdrojem nejasností.

Co je pole?

Než se pustím do toho, co je hashovací tabulka , musím nejprve zmínit pole . Pro účely této diskuze je pole seznam nebo kolekce hodnot nebo objektů.

$array = @(1,2,3,5,7,11)

Jakmile budete mít položky v poli, můžete je buď použít foreach k iteraci seznamu, nebo pomocí indexu získat přístup k jednotlivým prvkům v poli.

foreach($item in $array)
{
    Write-Output $item
}

Write-Output $array[3]

Hodnoty můžete také aktualizovat pomocí indexu stejným způsobem.

$array[2] = 13

Právě jsem seznámil jen stručně s poli, ale to by je mělo zasadit do správného kontextu, když přecházím na hašovací tabulky.

Co je hašovací tabulka?

Začnem základním technickým popisem toho, co jsou hashovací tabulky obecně, než se přesunu na jiné způsoby, jak je PowerShell používá.

Hashtable je datová struktura, podobně jako pole, kromě toho, že každou hodnotu (objekt) ukládáte pomocí klíče. Jedná se o jednoduché úložiště ve formátu klíč/hodnota. Nejprve vytvoříme prázdnou tabulku hash.

$ageList = @{}

Všimněte si, že k definování hešovací tabulky se používají složené závorky místo obyčejných závorek. Pak přidáme položku pomocí klíče, jako je tato:

$key = 'Kevin'
$value = 36
$ageList.Add( $key, $value )

$ageList.Add( 'Alex', 9 )

Jméno osoby je klíč a jeho věk je hodnota, kterou chci uložit.

Použití závorek pro přístup

Po přidání hodnot do hashovatelné tabulky je můžete pomocí stejného klíče vrátit zpět (místo použití číselného indexu, jaký byste měli pro pole).

$ageList['Kevin']
$ageList['Alex']

Když chci Kevinův věk, používám jeho jméno pro přístup k němu. Tento přístup můžeme použít také k přidání nebo aktualizaci hodnot do hashovatelné tabulky. To se podobá použití výše uvedené Add() metody.

$ageList = @{}

$key = 'Kevin'
$value = 36
$ageList[$key] = $value

$ageList['Alex'] = 9

Existuje další syntaxe, kterou můžete použít pro přístup k hodnotám a aktualizaci hodnot, které proberem v další části. Pokud přecházíte do PowerShellu z jiného programovacího jazyka, měly by tyto příklady odpovídat tomu, jak jste možná dříve použili hashtabulek.

Vytváření hashovatelných tabulek s hodnotami

Zatím jsem pro tyto příklady vytvořil prázdnou hashovací tabulku. Klíče a hodnoty můžete předem naplnit při jejich vytváření.

$ageList = @{
    Kevin = 36
    Alex  = 9
}

Jako vyhledávací tabulka

Skutečnou hodnotou tohoto typu hashtable je, že je můžete použít jako vyhledávací tabulku. Tady je jednoduchý příklad.

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

$server = $environments[$env]

V tomto příkladu zadáte prostředí pro $env proměnnou a vyberete správný server. Můžete použít switch($env){...} pro takovýto výběr, ale hashovací tabulka je dobrá možnost.

To se ještě zlepší, když dynamicky sestavíte vyhledávací tabulku, aby ji bylo možné později použít. Zvažte použití tohoto přístupu, když potřebujete něco vzájemně porovnávat. Myslím, že bychom to pozorovali ještě více, kdyby PowerShell nebyl tak výkonný ve filtrování dat v rámci pipeline s Where-Object. Pokud jste někdy v situaci, kdy záleží na výkonu, je potřeba tento přístup zvážit.

Neřeknu, že je to rychlejší, ale hodí se do pravidla pokud na výkonu záleží, otestujte to.

Vícenásobný výběr

Obecně se považuje, že hashtable je pár klíč/hodnota, kde zadáte jeden klíč a získáte jednu hodnotu. PowerShell umožňuje zadat pole klíčů pro získání více hodnot.

$environments[@('QA','DEV')]
$environments[('QA','DEV')]
$environments['QA','DEV']

V tomto příkladu používám stejnou vyhledávací hashovací tabulku jako výše a k získání shody poskytuji tři různé typy polí. Toto je skrytý klenot v PowerShellu, o které většina lidí neví.

Iterace hašovacích tabulek

Vzhledem k tomu, že hashovatelná tabulka je kolekce párů klíč/hodnota, iterujete ji jinak než u pole nebo normálního seznamu položek.

První věc, které si všimnete, je, že pokud přesměrujete svůj hashtable pomocí kanálu, tento kanál s ním zachází jako s jedním objektem.

PS> $ageList | Measure-Object
count : 1

I když Count vlastnost udává, kolik hodnot obsahuje.

PS> $ageList.Count
2

Tento problém můžete obejít pomocí Values vlastnosti, pokud potřebujete jenom hodnoty.

PS> $ageList.Values | Measure-Object -Average
Count   : 2
Average : 22.5

Často je užitečnější vytvořit výčet klíčů a použít je pro přístup k hodnotám.

PS> $ageList.Keys | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_, $ageList[$_]
    Write-Output $message
}
Kevin is 36 years old
Alex is 9 years old

Tady je stejný příklad se smyčkou foreach(){...} .

foreach($key in $ageList.Keys)
{
    $message = '{0} is {1} years old' -f $key, $ageList[$key]
    Write-Output $message
}

Každý klíč v hashovatelné tabulce procházíme a pak ho použijeme pro přístup k hodnotě. Jedná se o běžný vzor při práci s hashtables jako kolekcí.

GetEnumerator()

To nás přivádí k GetEnumerator() iteraci přes naši hashovací tabulku.

$ageList.GetEnumerator() | ForEach-Object{
    $message = '{0} is {1} years old!' -f $_.Key, $_.Value
    Write-Output $message
}

Enumerátor vám poskytne každý pár klíč/hodnota jeden za druhým. Byl navržen speciálně pro tento případ použití. Děkuji Marku Krausovi , že mi to připomínám.

Špatná Enumerace

Jedním z důležitých podrobností je, že při vytváření výčtu nemůžete upravit hashovací tabulku. Pokud začneme s naším základním $environments příkladem:

$environments = @{
    Prod = 'SrvProd05'
    QA   = 'SrvQA02'
    Dev  = 'SrvDev12'
}

A pokus o nastavení každého klíče na stejnou hodnotu serveru selže.

$environments.Keys | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

An error occurred while enumerating through a collection: Collection was modified;
enumeration operation may not execute.
+ CategoryInfo          : InvalidOperation: tableEnumerator:HashtableEnumerator) [],
 RuntimeException
+ FullyQualifiedErrorId : BadEnumeration

To také selže, i když to vypadá, že by mělo být také v pořádku:

foreach($key in $environments.Keys) {
    $environments[$key] = 'SrvDev03'
}

Collection was modified; enumeration operation may not execute.
    + CategoryInfo          : OperationStopped: (:) [], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException

Trikem této situace je klonování klíčů před provedením výčtu.

$environments.Keys.Clone() | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

Poznámka:

Nelze naklonovat hašovací tabulku obsahující jeden klíč. PowerShell vyvolá chybu. Místo toho převedete vlastnost Klíče na pole a pak iterujete přes pole.

@($environments.Keys) | ForEach-Object {
    $environments[$_] = 'SrvDev03'
}

Hašovací tabulka jako kolekce vlastností

Zatím byl typ objektů, které jsme umístili do naší hashtable, stejný typ objektu. Používal jsem věky ve všech těch příkladech a klíčem bylo jméno osoby. Je to skvělý způsob, jak se na ni podívat, když má vaše kolekce objektů název. Dalším běžným způsobem použití hashtables v PowerShellu je uložení kolekce vlastností, kde klíč je název vlastnosti. V tomto dalším příkladu se k této myšlence připojím.

Přístup na základě vlastností

Použití přístupu na základě vlastností mění dynamiku hashtable a způsob jejich použití v PowerShellu. Tady je náš obvyklý příklad z výše uvedeného zpracování klíčů jako vlastností.

$ageList = @{}
$ageList.Kevin = 35
$ageList.Alex = 9

Stejně jako výše uvedené příklady tento příklad tyto klíče přidá, pokud ještě neexistují v hashtable. V závislosti na tom, jak jste definovali klíče a jaké jsou vaše hodnoty, je to buď trochu podivné, nebo to perfektně sedí. Příklad věkového seznamu funguje až do tohoto okamžiku skvěle. Potřebujeme nový příklad, aby to do budoucna působilo správně.

$person = @{
    name = 'Kevin'
    age  = 36
}

K $person můžeme přidat atributy a přistupovat k nim takto.

$person.city = 'Austin'
$person.state = 'TX'

Najednou se tato tabulka hashů začne chovat jako objekt. Stále je to kolekce věcí, takže všechny výše uvedené příklady stále platí. Přistupujeme k tomu jen z jiného pohledu.

Kontrola klíčů a hodnot

Ve většině případů můžete hodnotu otestovat následujícím způsobem:

if( $person.age ){...}

Je to jednoduché, ale byl zdrojem mnoha chyb pro mě, protože jsem přehlédl jeden důležitý detail v mé logice. Začal jsem ho používat k otestování, jestli byl klíč přítomen. Pokud hodnota byla $false nebo nula, tento příkaz by se neočekávaně vrátil $false .

if( $person.age -ne $null ){...}

Tento problém se dá obejít u nulových hodnot, ale ne pro $null vs. neexistující klíče. Ve většině případů nemusíte toto rozlišení rozlišovat, ale existují metody, kdy to uděláte.

if( $person.ContainsKey('age') ){...}

Máme ContainsValue() také pro situaci, kdy potřebujete otestovat hodnotu, aniž byste znali klíč nebo iterovali celou kolekci.

Odebrání a vymazání klíčů

Pomocí Remove() metody můžete klíče odebrat.

$person.Remove('age')

Když jim přiřadíte hodnotu $null, zůstane vám klíč, který má hodnotu $null.

Běžným způsobem, jak vymazat hashovací tabulku, je jednoduše ji inicializovat jako prázdnou hashovací tabulku.

$person = @{}

I když to funguje, zkuste místo toho použít metodu Clear() .

$person.Clear()

Toto je jeden z těch případů, kdy použití metody vytváří samodokumentující se kód a zpřehledňuje záměry kódu.

Všechny zábavné věci

Seřazené hashovací tabulky

Ve výchozím nastavení nejsou hashtable seřazené (ani seřazené). V tradičním kontextu nezáleží na pořadí, když pro přístup k hodnotám vždy používáte klíč. Můžete zjistit, že chcete, aby vlastnosti zůstaly v pořadí, v jakém je definujete. Naštěstí existuje způsob, jak to udělat pomocí klíčového ordered slova.

$person = [ordered]@{
    name = 'Kevin'
    age  = 36
}

Když teď vytvoříte výčet klíčů a hodnot, zůstanou v daném pořadí.

Vnořené hashovací tabulky

Při definování hashovací tabulky na jednom řádku můžete páry klíč/hodnota oddělit středníkem.

$person = @{ name = 'kevin'; age = 36; }

To přijde vhod, pokud je vytváříte na potrubí.

Vlastní výrazy v běžných příkazech potrubí

Existuje několik rutin, které podporují použití tzv. hash tabulek pro vytvoření uživatelských nebo počítaných vlastností. Běžně to můžete vidět s Select-Object a Format-Table. Hashtables mají speciální syntaxi, která vypadá takto v plném rozsahu.

$property = @{
    Name = 'TotalSpaceGB'
    Expression = { ($_.Used + $_.Free) / 1GB }
}

To Name je to, co by cmdlet označil jako daný sloupec. Jedná se Expression o blok skriptu, který se spustí, kde $_ je hodnota objektu na kanálu. Tady je skript v akci:

$drives = Get-PSDrive | where Used
$drives | Select-Object -Property Name, $property

Name     TotalSpaceGB
----     ------------
C    238.472652435303

Umístil(a) jsem to do proměnné, ale mohlo by to být snadno definováno inline a při té příležitosti můžete zkrátit Name na n a Expression na e.

$drives | Select-Object -Property Name, @{n='TotalSpaceGB';e={($_.Used + $_.Free) / 1GB}}

Osobně se mi nelíbí, jak dlouhé to činí příkazy a často to podporuje některé špatné chování, ke kterým se nebudu vyjadřovat. Pravděpodobněji vytvořím novou hashovatelnou tabulku nebo pscustomobject se všemi poli a vlastnostmi, které chci místo použití tohoto přístupu ve skriptech. Ale tam je hodně kódu, co to dělá, takže jsem chtěl, abys o tom věděl. O vytvoření pscustomobject mluvím později.

Vlastní výraz řazení

Kolekci můžete snadno seřadit, pokud mají objekty data, podle kterých chcete data seřadit. Data můžete buď přidat do objektu před řazením, nebo vytvořit vlastní výraz pro Sort-Object.

Get-ADUser | Sort-Object -Property @{ e={ Get-TotalSales $_.Name } }

V tomto příkladu vezmu seznam uživatelů a pomocí nějakého vlastního cmdletu získám další informace jen pro řazení.

Seřazení seznamu hashovatelných tabulek

Pokud máte seznam hashovacích tabulek, které chcete seřadit, zjistíte, že Sort-Object vaše klíče nepovažuje za atributy. Můžeme to vyřešit pomocí vlastního třídicího výrazu.

$data = @(
    @{name='a'}
    @{name='c'}
    @{name='e'}
    @{name='f'}
    @{name='d'}
    @{name='b'}
)

$data | Sort-Object -Property @{e={$_.name}}

Použití hashtabulky u příkazů cmdlet

Toto je jedna z mých oblíbených věcí o hashtables, které mnoho lidí nezjišťuje brzy. Myšlenka spočívá v tom, že místo poskytnutí všech vlastností rutině na jednom řádku je můžete nejprve zabalit do hashovatelné tabulky. Pak můžete předat hashovací tabulku funkci zvláštním způsobem. Tady je příklad vytvoření oboru DHCP normálním způsobem.

Add-DhcpServerV4Scope -Name 'TestNetwork' -StartRange '10.0.0.2' -EndRange '10.0.0.254' -SubnetMask '255.255.255.0' -Description 'Network for testlab A' -LeaseDuration (New-TimeSpan -Days 8) -Type "Both"

Bez použití splattingu musí být všechny tyto věci definovány na jednom řádku. Buď se posune mimo obrazovku, nebo se zalomí kdekoli to uzná za vhodné. Teď to porovnejte s příkazem, který používá splatting.

$DHCPScope = @{
    Name          = 'TestNetwork'
    StartRange    = '10.0.0.2'
    EndRange      = '10.0.0.254'
    SubnetMask    = '255.255.255.0'
    Description   = 'Network for testlab A'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type          = "Both"
}
Add-DhcpServerV4Scope @DHCPScope

Použití znaménka @ místo $ vyvolává operaci splat.

Věnujte chvíli ocenění, jak snadno se čte tento příklad. Jedná se o úplně stejný příkaz se všemi stejnými hodnotami. Druhá je jednodušší pochopit a udržovat do budoucna.

Používám splatting, kdykoli se příkaz stane příliš dlouhý. Definuji příliš dlouho jako takové, které způsobí posun okna doprava. Pokud pro funkci narazím na tři vlastnosti, je možné, že ji přepíšu pomocí rozpínaného hashtable.

Použití splattingu pro volitelné parametry

Jedním z nejběžnějších způsobů použití splattingu je zpracování volitelných parametrů, které pocházejí z nějakého jiného místa ve skriptu. Řekněme, že mám funkci, která zahrnuje volání Get-CimInstance, jež má volitelný argument $Credential.

$CIMParams = @{
    ClassName = 'Win32_BIOS'
    ComputerName = $ComputerName
}

if($Credential)
{
    $CIMParams.Credential = $Credential
}

Get-CimInstance @CIMParams

Začnem vytvořením hashovatelného objektu s běžnými parametry. Pak přidám $Credential, pokud existuje. Protože tady využívám splatting, potřebuji mít v kódu volání Get-CimInstance pouze jednou. Tento vzor návrhu je velmi čistý a dokáže snadno zpracovat spoustu volitelných parametrů.

Abyste byli spravedliví, mohli byste napsat příkazy, které umožňují $null hodnoty parametrů. Prostě nemáte vždy kontrolu nad ostatními příkazy, které voláte.

Více splatů

Do stejné rutiny můžete splatovat několik hashovatelných tabulek. Pokud se k našemu původnímu příkladu splattingu znovu podíváme:

$Common = @{
    SubnetMask  = '255.255.255.0'
    LeaseDuration = (New-TimeSpan -Days 8)
    Type = "Both"
}

$DHCPScope = @{
    Name        = 'TestNetwork'
    StartRange  = '10.0.0.2'
    EndRange    = '10.0.0.254'
    Description = 'Network for testlab A'
}

Add-DhcpServerv4Scope @DHCPScope @Common

Tuto metodu použijem, když mám společnou sadu parametrů, které předávám spoustě příkazů.

Splatting pro čistý kód

Není nic špatného na splattingu jednoho parametru, pokud to přispívá k čistějšímu kódu.

$log = @{Path = '.\logfile.log'}
Add-Content "logging this command" @log

Splatting spustitelných souborů

Splatting funguje také u některých spustitelných souborů, které používají /param:value syntaxi. Robocopy.exeNapříklad má některé parametry, jako je tento.

$robo = @{R=1;W=1;MT=8}
robocopy source destination @robo

Nevím, jestli je to opravdu tak užitečné, ale přišlo mi to zajímavé.

Přidání hashovacích tabulek

Hashtables podporují operátor sčítání pro kombinování dvou hashtables.

$person += @{Zip = '78701'}

To funguje jenom v případě, že tyto dvě hashovací tabulky nesdílí klíč.

Vnořené hashovací tabulky

Hashtables můžeme použít jako hodnoty uvnitř hashtable.

$person = @{
    name = 'Kevin'
    age  = 36
}
$person.location = @{}
$person.location.city = 'Austin'
$person.location.state = 'TX'

Začal jsem se základní hashtable obsahující dva klíče. Přidal jsem klíč nazvaný location s prázdnou tabulkou hash. Pak jsem do této location hashtable přidal poslední dvě položky. Můžeme to udělat i v textu.

$person = @{
    name = 'Kevin'
    age  = 36
    location = @{
        city  = 'Austin'
        state = 'TX'
    }
}

Tím se vytvoří stejná hashovatelná tabulka, kterou jsme viděli výše, a stejným způsobem má přístup k vlastnostem.

$person.location.city
Austin

Existuje mnoho způsobů, jak přistupovat ke struktuře objektů. Tady je další způsob, jak se podívat na vnořenou hashovací tabulku.

$people = @{
    Kevin = @{
        age  = 36
        city = 'Austin'
    }
    Alex = @{
        age  = 9
        city = 'Austin'
    }
}

To kombinuje koncept použití hashtablů jako kolekce objektů a kolekce vlastností. Hodnoty jsou stále snadno přístupné, i když jsou vnořené, a to jakýmkoli způsobem, který upřednostňujete.

PS> $people.kevin.age
36
PS> $people.kevin['city']
Austin
PS> $people['Alex'].age
9
PS> $people['Alex']['City']
Austin

Mám tendenci používat vlastnost tečkou, když s ní zacházím jako s vlastností. To jsou obecně věci, které jsem v kódu definoval staticky a znám je zpaměti. Pokud potřebuji procházet seznam nebo programově přistupovat ke klíčům, použijte hranaté závorky k zadání názvu klíče.

foreach($name in $people.Keys)
{
    $person = $people[$name]
    '{0}, age {1}, is in {2}' -f $name, $person.age, $person.city
}

Možnost vnořit hashtables vám dává spoustu flexibility a možností.

Zobrazení vnořených hashtables

Jakmile začnete vnořovat hashovací tabulky, budete potřebovat snadný způsob, jak se na ně podívat z konzoly. Když použiji poslední hashovací tabulku, získám výstup, který vypadá takto a zachází pouze do určité hloubky.

PS> $people
Name                           Value
----                           -----
Kevin                          {age, city}
Alex                           {age, city}

Můj oblíbený příkaz pro podívání se na tyto záležitosti je ConvertTo-Json, protože je velmi přehledný a často používám JSON na jiných záležitostech.

PS> $people | ConvertTo-Json
{
    "Kevin":  {
                "age":  36,
                "city":  "Austin"
            },
    "Alex":  {
                "age":  9,
                "city":  "Austin"
            }
}

I když json neznáte, měli byste být schopni zjistit, co hledáte. Format-Custom Existuje příkaz pro strukturovaná data, jako je tato, ale stále se mi líbí zobrazení JSON lépe.

Vytváření objektů

Někdy prostě potřebujete mít objekt a použití hašovací tabulky k uložení vlastností úlohu dostatečně nevyřeší. Nejčastěji se mají klíče zobrazovat jako názvy sloupců. Díky pscustomobject je to snadné.

$person = [pscustomobject]@{
    name = 'Kevin'
    age  = 36
}

$person

name  age
----  ---
Kevin  36

I když ho zpočátku jako pscustomobject nevytváříte, můžete ho později přetypovat, pokud je to nutné.

$person = @{
    name = 'Kevin'
    age  = 36
}

[pscustomobject]$person

name  age
----  ---
Kevin  36

Už mám podrobný článek pro pscustomobject, který byste si měli přečíst po tomto. Vychází z mnoha poznatků, které zde byly získány.

Čtení a zápis hashovatelných tabulek do souboru

Ukládání do CSV souboru

Problémy s uložením hešovací tabulky do souboru CSV jsou jedním z potíží, na které jsem odkazoval výše. Převeďte svou hashtable na pscustomobject a bude správně uložena do souboru CSV. Pomůže, když začnete s pscustomobject, aby se zachovalo pořadí sloupců. Pokud je to nutné, můžete ho přetypovat na pscustomobject jako inline vložku.

$person | ForEach-Object{ [pscustomobject]$_ } | Export-Csv -Path $path

Znovu se podívejte na můj článek o použití pscustomobject.

Uložení vnořené hashovací tabulky do souboru

Pokud potřebuji uložit vnořenou hashovací tabulku do souboru a pak ji znovu přečíst, použijem k tomu rutiny JSON.

$people | ConvertTo-Json | Set-Content -Path $path
$people = Get-Content -Path $path -Raw | ConvertFrom-Json

Tato metoda obsahuje dva důležité body. Nejprve je json napsaný ve víceřádkovém formátu, takže potřebuji použít -Raw možnost pro jeho čtení zpět do jednoho řetězce. Druhým bodem je, že importovaný objekt již není [hashtable]. Je to teď [pscustomobject] a to může způsobit problémy, jestliže to neočekáváte.

Sledujte hluboko vnořené hashovací tabulky. Když ho převedete na JSON, nemusí se zobrazit očekávané výsledky.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json

{
  "a": {
    "b": {
      "c": "System.Collections.Hashtable"
    }
  }
}

Pomocí parametru Depth se ujistěte, že jste rozšířili všechny vnořené hashtables.

@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json -Depth 3

{
  "a": {
    "b": {
      "c": {
        "d": "e"
      }
    }
  }
}

Pokud potřebujete, aby byl při importu [hashtable], musíte použít příkazy Export-CliXml a Import-CliXml.

Převod JSON na hashtable

Pokud potřebujete převést JSON na [hashtable], existuje jeden způsob, o kterém vím, že ho lze udělat s JavaScriptSerializer v .NET.

[Reflection.Assembly]::LoadWithPartialName("System.Web.Script.Serialization")
$JSSerializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new()
$JSSerializer.Deserialize($json,'Hashtable')

Počínaje PowerShellem v6 používá podpora JSONu NewtonSoft JSON.NET a přidává podporu pro hashtabule.

'{ "a": "b" }' | ConvertFrom-Json -AsHashtable

Name      Value
----      -----
a         b

PowerShell 6.2 přidal parametr Hloubka do ConvertFrom-Jsonsouboru . Výchozí hloubka je 1024.

Čtení přímo ze souboru

Pokud máte soubor, který obsahuje hashtable pomocí syntaxe PowerShellu, existuje způsob, jak ho přímo importovat.

$content = Get-Content -Path $Path -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create( $content )
$scriptBlock.CheckRestrictedLanguage( $allowedCommands, $allowedVariables, $true )
$hashtable = ( & $scriptBlock )

Naimportuje obsah souboru do scriptblocksouboru a pak zkontroluje, jestli neobsahuje žádné další příkazy PowerShellu, než ho spustí.

Mimochodem, věděli jste, že manifest modulu (.psd1 soubor) je jen hašovací tabulka?

Klíče můžou být libovolný objekt.

Ve většině případů jsou klíče jen řetězce. Můžeme dát uvozovky kolem čehokoli a proměnit to v klíč.

$person = @{
    'full name' = 'Kevin Marquette'
    '#' = 3978
}
$person['full name']

Můžete udělat nějaké divné věci, které jste si možná neuvědomili, že byste mohli dělat.

$person.'full name'

$key = 'full name'
$person.$key

Jen proto, že můžete něco udělat, neznamená to, že byste měli. Poslední vypadá jako chyba, která se brzy stane a bude snadno špatně pochopena kýmkoli, kdo čte váš kód.

Technicky vzato váš klíč nemusí být řetězec, ale je jednodušší uvažovat, pokud používáte jenom řetězce. Indexování s komplexními klíči ale nefunguje dobře.

$ht = @{ @(1,2,3) = "a" }
$ht

Name                           Value
----                           -----
{1, 2, 3}                      a

Přístup k hodnotě v hashovatelné tabulce pomocí jeho klíče nefunguje vždy. Například:

$key = $ht.Keys[0]
$ht.$($key)
a
$ht[$key]
a

Pokud je klíčem pole, musíte proměnnou $key zabalit do dílčího výrazu, aby ji bylo možné použít s notací přístupu člena (.). Nebo můžete použít notaci indexu pole ([]).

Používání v automatických proměnných

$PSBoundParameters

$PSBoundParameters je automatická proměnná, která existuje pouze v kontextu funkce. Obsahuje všechny parametry, se kterými byla funkce volána. Není to přesně hashovatelná tabulka, ale dostatečně blízko, abyste ji mohli považovat za takovou.

To zahrnuje odebrání klíčů a jejich rozdělení mezi jiné funkce. Pokud zjistíte, že píšete proxy funkce, podívejte se na tuto funkci podrobněji.

Další podrobnosti najdete v about_Automatic_Variables .

Nepředvídatelnost PSBoundParameters

Je důležité si uvědomit, že to zahrnuje pouze hodnoty, které se předávají jako parametry. Pokud máte také parametry s výchozími hodnotami, ale volající je nepředává, $PSBoundParameters neobsahuje tyto hodnoty. To se běžně přehlíží.

$PSDefaultParameterValues

Tato automatická proměnná umožňuje přiřadit výchozí hodnoty jakékoli rutině beze změny rutiny. Podívejte se na tento příklad.

$PSDefaultParameterValues["Out-File:Encoding"] = "UTF8"

Tímto se přidá položka do $PSDefaultParameterValues, která nastaví UTF8 jako výchozí hodnotu parametru Out-File -Encoding. Toto je specifické pro danou relaci, takže byste jej měli umístit do svého $PROFILE.

Používám to často k předběžnému přiřazení hodnot, které zadám poměrně často.

$PSDefaultParameterValues[ "Connect-VIServer:Server" ] = 'VCENTER01.contoso.local'

To také přijímá zástupné znaky, takže můžete hodnoty nastavit hromadně. Tady je několik způsobů, jak ho můžete použít:

$PSDefaultParameterValues[ "Get-*:Verbose" ] = $true
$PSDefaultParameterValues[ "*:Credential" ] = Get-Credential

Podrobnější rozpis najdete v tomto skvělém článku o automatických výchozích nastaveních od Michaela Sorense.

Regex $Matches

Při použití operátoru -match se vytvoří automatická proměnná $Matches s výsledky srovnání. Pokud máte v regulárním výrazu nějaké dílčí výrazy, zobrazí se také tyto dílčí shody.

$message = 'My SSN is 123-45-6789.'

$message -match 'My SSN is (.+)\.'
$Matches[0]
$Matches[1]

Pojmenované shody

Toto je jedna z mých oblíbených funkcí, o kterých většina lidí neví. Pokud použijete pojmenovanou shodu v regulárním výrazu, můžete k této shodě přistupovat podle názvu ve výsledcích shod.

$message = 'My Name is Kevin and my SSN is 123-45-6789.'

if($message -match 'My Name is (?<Name>.+) and my SSN is (?<SSN>.+)\.')
{
    $Matches.Name
    $Matches.SSN
}

V předchozím příkladu (?<Name>.*) je pojmenovaný dílčí výraz. Tato hodnota se pak umístí do $Matches.Name vlastnosti.

Group-Object -AsHashtable

Jednou z málo známých funkcí Group-Object je, že může některé datové sady převést na zatřiďovací tabulku.

Import-Csv $Path | Group-Object -AsHashtable -Property Email

Tím se každý řádek přidá do hashtable a použije zadanou vlastnost jako klíč pro přístup k němu.

Kopírování hashovatelných tabulek

Jedna důležitá věc, kterou je potřeba vědět, je, že hashovací tabulky jsou objekty. Každá proměnná je jen odkazem na objekt. To znamená, že vytvoření platné kopie hashovatelné tabulky trvá více práce.

Přiřazení typů odkazů

Pokud máte jednu hashovací tabulku a přiřadíte ji druhé proměnné, obě proměnné odkazují na stejnou hashovací tabulku.

PS> $orig = @{name='orig'}
PS> $copy = $orig
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [copy]

To zvýrazňuje, že jsou stejné, protože změna hodnot v jednom z nich také změní hodnoty v druhé. To platí také při předávání hashtables do jiných funkcí. Pokud tyto funkce změní tuto hashovací tabulku, změní se i vaše původní tabulka.

Mělké kopie, jedna úroveň

Pokud máme jednoduchou hashovací tabulku, jako je náš příklad výše, můžeme použít Clone() k vytvoření mělké kopie.

PS> $orig = @{name='orig'}
PS> $copy = $orig.Clone()
PS> $copy.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.name
PS> 'Orig: [{0}]' -f $orig.name

Copy: [copy]
Orig: [orig]

To nám umožní provést některé základní změny, které nemají vliv na druhý.

Mělké kopie, vnořené

Důvodem, proč se nazývá mělká kopie, je, že kopíruje pouze vlastnosti základní úrovně. Pokud je jednou z těchto vlastností referenční typ (například jiná hashtable), budou tyto vnořené objekty stále odkazovat na sebe.

PS> $orig = @{
        person=@{
            name='orig'
        }
    }
PS> $copy = $orig.Clone()
PS> $copy.person.name = 'copy'
PS> 'Copy: [{0}]' -f $copy.person.name
PS> 'Orig: [{0}]' -f $orig.person.name

Copy: [copy]
Orig: [copy]

Takže vidíte, že i když jsem naklonoval hašovací tabulku, odkaz na person nebyl naklonován. Potřebujeme vytvořit hloubkovou kopii, abychom skutečně měli druhou hashovací tabulku, která není propojená s prvním.

Hloubkové kopie

Existuje několik způsobů, jak vytvořit hloubkovou kopii hašovací tabulky (a zachovat ji jako hašovací tabulku). Tady je funkce využívající PowerShell k rekurzivnímu vytvoření hloubkové kopie:

function Get-DeepClone
{
    [CmdletBinding()]
    param(
        $InputObject
    )
    process
    {
        if($InputObject -is [hashtable]) {
            $clone = @{}
            foreach($key in $InputObject.Keys)
            {
                $clone[$key] = Get-DeepClone $InputObject[$key]
            }
            return $clone
        } else {
            return $InputObject
        }
    }
}

Nezpracuje žádné jiné odkazové typy nebo pole, ale je to dobrý výchozí bod.

Dalším způsobem je použití .NET k deserializaci pomocí CliXml, jak je vidět v této funkci:

function Get-DeepClone
{
    param(
        $InputObject
    )
    $TempCliXmlString = [System.Management.Automation.PSSerializer]::Serialize($obj, [int32]::MaxValue)
    return [System.Management.Automation.PSSerializer]::Deserialize($TempCliXmlString)
}

U extrémně velkých hashovacích tabulek je funkce deserializace rychlejší při škálování. Při použití této metody je však potřeba zvážit několik věcí. Vzhledem k tomu, že používá CliXml, vyžaduje mnoho paměti, a pokud klonujete obrovské hashové tabulky, může to způsobit problémy. Dalším omezením cliXml je omezení hloubky 48. To znamená, že pokud máte hašovací tabulku s 48 vrstvami vnořených hašovacích tabulek, klonování selže a výstupem nebude vůbec žádná hašovací tabulka.

Cokoli jiného?

Rychle jsem hodně pokročil. Doufám, že pokaždé, když si to přečteš, se naučíš něco nového nebo lépe porozumíš. Vzhledem k tomu, že jsem probral celé spektrum této funkce, existují aspekty, které se na vás právě teď nemusí vztahovat. To je naprosto v pořádku a podle toho, jak moc pracujete s PowerShellem, se očekává.