Alles wat u wilde weten over hashtables
Ik wil een stap terug doen en praten over hashtables. Ik gebruik ze altijd. Ik leerde iemand over hen na onze vergadering van de gebruikersgroep gisteravond en ik realiseerde me dat ik dezelfde verwarring had over hen als hij had. Hashtables zijn erg belangrijk in PowerShell, dus het is goed om er een goed begrip van te hebben.
Notitie
De oorspronkelijke versie van dit artikel verscheen op het blog geschreven door @KevinMarquette. Het PowerShell-team bedankt Kevin voor het delen van deze inhoud met ons. Bekijk zijn blog op PowerShellExplained.com.
Hashtable als een verzameling dingen
Ik wil dat u eerst een Hashtable ziet als een verzameling in de traditionele definitie van een hashtabel. Deze definitie geeft u een fundamenteel inzicht in hoe ze werken wanneer ze later worden gebruikt voor geavanceerdere dingen. Het overslaan van dit begrip is vaak een bron van verwarring.
Wat is een matrix?
Voordat ik aan de slag ga met wat een Hashtable is, moet ik eerst matrices vermelden. Voor deze discussie is een matrix een lijst of verzameling waarden of objecten.
$array = @(1,2,3,5,7,11)
Zodra u uw items in een matrix hebt opgenomen, kunt u de foreach lijst herhalen of een index gebruiken om toegang te krijgen tot afzonderlijke elementen in de matrix.
foreach($item in $array)
{
Write-Output $item
}
Write-Output $array[3]
U kunt waarden ook op dezelfde manier bijwerken met behulp van een index.
$array[2] = 13
Ik heb zojuist het oppervlak op matrices gekrabd, maar dat moet ze in de juiste context plaatsen terwijl ik naar hashtables ga.
Wat is een hashtabel?
Ik begin met een eenvoudige technische beschrijving van wat hashtables zijn, in de algemene zin, voordat ik naar de andere manieren ga waarop PowerShell ze gebruikt.
Een hashtabel is een gegevensstructuur, vergelijkbaar met een matrix, behalve dat u elke waarde (object) opslaat met behulp van een sleutel. Het is een basisarchief voor sleutel/waarde. Eerst maken we een lege hashtabel.
$ageList = @{}
U ziet dat accolades, in plaats van haakjes, worden gebruikt om een hashtabel te definiëren. Vervolgens voegen we een item toe met behulp van een sleutel als volgt:
$key = 'Kevin'
$value = 36
$ageList.add( $key, $value )
$ageList.add( 'Alex', 9 )
De naam van de persoon is de sleutel en hun leeftijd is de waarde die ik wil opslaan.
De haken gebruiken voor toegang
Zodra u uw waarden aan de hashtabel hebt toegevoegd, kunt u ze weer uittrekken met dezelfde sleutel (in plaats van een numerieke index te gebruiken zoals u voor een matrix zou hebben).
$ageList['Kevin']
$ageList['Alex']
Als ik kevins leeftijd wil, gebruik ik zijn naam om het te openen. We kunnen deze methode ook gebruiken om waarden toe te voegen aan of bij te werken in de hashtabel. Dit is net zoals het gebruik van de add() bovenstaande functie.
$ageList = @{}
$key = 'Kevin'
$value = 36
$ageList[$key] = $value
$ageList['Alex'] = 9
Er is nog een syntaxis die u kunt gebruiken voor het openen en bijwerken van waarden die ik in een latere sectie zal behandelen. Als u vanuit een andere taal naar PowerShell komt, moeten deze voorbeelden passen bij de wijze waarop u hashtables eerder hebt gebruikt.
Hashtables maken met waarden
Tot nu toe heb ik een lege hashtabel gemaakt voor deze voorbeelden. U kunt de sleutels en waarden vooraf invullen wanneer u ze maakt.
$ageList = @{
Kevin = 36
Alex = 9
}
Als opzoektabel
De werkelijke waarde van dit type hashtabel is dat u ze kunt gebruiken als opzoektabel. Hier volgt een eenvoudig voorbeeld.
$environments = @{
Prod = 'SrvProd05'
QA = 'SrvQA02'
Dev = 'SrvDev12'
}
$server = $environments[$env]
In dit voorbeeld geeft u een omgeving op voor de $env variabele en kiest u de juiste server. U kunt een switch($env){...} voor een selectie als deze gebruiken, maar een hashtabel is een leuke optie.
Dit wordt nog beter wanneer u de opzoektabel dynamisch bouwt om deze later te gebruiken. Denk dus na over het gebruik van deze benadering wanneer u iets kruislings moet raadplegen. Ik denk dat we dit nog meer zouden zien als PowerShell niet zo goed was in het filteren op de pijp met Where-Object. Als u ooit in een situatie bent waarin de prestaties van belang zijn, moet deze aanpak worden overwogen.
Ik zal niet zeggen dat het sneller is, maar het past wel in de regel van If performance matters, test het.
Meervoudige selectie
Over het algemeen beschouwt u een hashtabel als een sleutel-waardepaar, waarbij u één sleutel opgeeft en één waarde opgeeft. Met PowerShell kunt u een matrix met sleutels opgeven om meerdere waarden op te halen.
$environments[@('QA','DEV')]
$environments[('QA','DEV')]
$environments['QA','DEV']
In dit voorbeeld gebruik ik dezelfde opzoekhashtabel van boven en geef drie verschillende matrixstijlen op om de overeenkomsten op te halen. Dit is een verborgen juweeltje in PowerShell waar de meeste mensen zich niet van bewust zijn.
Hashtables herhalen
Omdat een hashtabel een verzameling sleutel-waardeparen is, gaat u er anders overheen dan voor een matrix of een normale lijst met items.
Het eerste wat u moet zien is dat als u uw hashtable doorsluist, de pijp het als één object behandelt.
PS> $ageList | Measure-Object
count : 1
Hoewel de .count eigenschap aangeeft hoeveel waarden deze bevat.
PS> $ageList.count
2
U kunt dit probleem omzeilen door de .values eigenschap te gebruiken als u alleen de waarden nodig hebt.
PS> $ageList.values | Measure-Object -Average
Count : 2
Average : 22.5
Het is vaak handiger om de sleutels op te sommen en te gebruiken om toegang te krijgen tot de waarden.
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
Hier volgt hetzelfde voorbeeld met een foreach(){...} lus.
foreach($key in $ageList.keys)
{
$message = '{0} is {1} years old' -f $key, $ageList[$key]
Write-Output $message
}
We lopen elke sleutel in de hashtabel en gebruiken deze vervolgens om toegang te krijgen tot de waarde. Dit is een algemeen patroon bij het werken met hashtables als een verzameling.
GetEnumerator()
Dat brengt ons naar GetEnumerator() voor het herhalen van onze hashtable.
$ageList.GetEnumerator() | ForEach-Object{
$message = '{0} is {1} years old!' -f $_.key, $_.value
Write-Output $message
}
De enumerator geeft u elk sleutel-waardepaar een na een. Het is speciaal ontworpen voor deze use case. Bedankt aan Mark Kraus om me eraan te herinneren.
BadEnumeration
Een belangrijk detail is dat u een hashtabel niet kunt wijzigen terwijl deze wordt geïnventariseerd. Als we beginnen met ons basisvoorbeeld $environments :
$environments = @{
Prod = 'SrvProd05'
QA = 'SrvQA02'
Dev = 'SrvDev12'
}
En het instellen van elke sleutel op dezelfde serverwaarde mislukt.
$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
Dit mislukt ook, ook al lijkt het erop dat het goed zou moeten zijn:
foreach($key in $environments.keys) {
$environments[$key] = 'SrvDev03'
}
Collection was modified; enumeration operation may not execute.
+ CategoryInfo : OperationStopped: (:) [], InvalidOperationException
+ FullyQualifiedErrorId : System.InvalidOperationException
De truc voor deze situatie is om de sleutels te klonen voordat u de opsomming uitvoert.
$environments.Keys.Clone() | ForEach-Object {
$environments[$_] = 'SrvDev03'
}
Hashtable als een verzameling eigenschappen
Tot nu toe waren het type objecten dat we in onze hashtabel hebben geplaatst, allemaal hetzelfde type object. Ik gebruikte leeftijden in al die voorbeelden en de sleutel was de naam van de persoon. Dit is een geweldige manier om het te bekijken wanneer uw verzameling objecten elk een naam heeft. Een andere veelgebruikte manier om hashtables in PowerShell te gebruiken, is het opslaan van een verzameling eigenschappen waarbij de sleutel de naam van de eigenschap is. Ik ga dat idee in dit volgende voorbeeld bekijken.
Toegang op basis van eigenschappen
Het gebruik van op eigenschappen gebaseerde toegang verandert de dynamiek van hashtables en hoe u deze kunt gebruiken in PowerShell. Hier volgt ons gebruikelijke voorbeeld hierboven, waarbij de sleutels als eigenschappen worden behandeld.
$ageList = @{}
$ageList.Kevin = 35
$ageList.Alex = 9
Net als in de bovenstaande voorbeelden voegt dit voorbeeld die sleutels toe als deze nog niet bestaan in de hashtabel. Afhankelijk van hoe u uw sleutels hebt gedefinieerd en wat uw waarden zijn, is dit een beetje vreemd of een perfecte pasvorm. Het voorbeeld van de leeftijdslijst heeft tot nu toe uitstekend gewerkt. We hebben hier een nieuw voorbeeld voor nodig om ons vooruit te voelen.
$person = @{
name = 'Kevin'
age = 36
}
En we kunnen dergelijke kenmerken $person toevoegen en openen.
$person.city = 'Austin'
$person.state = 'TX'
Plotseling begint deze hashtable zich te voelen en te fungeren als een object. Het is nog steeds een verzameling dingen, dus alle bovenstaande voorbeelden zijn nog steeds van toepassing. We benaderen het gewoon vanuit een ander oogpunt.
Controleren op sleutels en waarden
In de meeste gevallen kunt u gewoon testen op de waarde met een dergelijke waarde:
if( $person.age ){...}
Het is eenvoudig, maar is de bron van veel bugs voor mij geweest, omdat ik één belangrijk detail in mijn logica over het hoofd had gezien. Ik begon het te gebruiken om te testen of er een sleutel aanwezig was. Wanneer de waarde of nul was $false , zou die instructie onverwacht worden geretourneerd $false .
if( $person.age -ne $null ){...}
Dit werkt rond dat probleem voor nulwaarden, maar niet $null versus niet-bestaande sleutels. Meestal hoeft u dat onderscheid niet te maken, maar er zijn functies voor wanneer u dat doet.
if( $person.ContainsKey('age') ){...}
We hebben ook een ContainsValue() situatie waarin u moet testen op een waarde zonder de sleutel te kennen of de hele verzameling te herhalen.
Sleutels verwijderen en wissen
U kunt sleutels verwijderen met de .Remove() functie.
$person.remove('age')
Als u ze een $null waarde toewijst, blijft u met een sleutel met een $null waarde.
Een veelvoorkomende manier om een hashtabel te wissen, is door deze te initialiseren in een lege hashtabel.
$person = @{}
Probeer in plaats daarvan de clear() functie te gebruiken.
$person.clear()
Dit is een van deze exemplaren waarbij met behulp van de functie zelfdocumenterende code wordt gemaakt en de intenties van de code zeer schoon worden gemaakt.
Alle leuke dingen
Geordende hashtables
Hashtables worden standaard niet geordend (of gesorteerd). In de traditionele context maakt de volgorde niet uit wanneer u altijd een sleutel gebruikt om toegang te krijgen tot waarden. Mogelijk wilt u dat de eigenschappen in de volgorde blijven waarin u ze definieert. Gelukkig is er een manier om dat te doen met het ordered trefwoord.
$person = [ordered]@{
name = 'Kevin'
age = 36
}
Wanneer u nu de sleutels en waarden opsommen, blijven ze in die volgorde.
Inline-hashtabellen
Wanneer u een hashtabel op één regel definieert, kunt u de sleutel/waardeparen scheiden met een puntkomma.
$person = @{ name = 'kevin'; age = 36; }
Dit komt handig als u ze op de pijp maakt.
Aangepaste expressies in algemene pijplijnopdrachten
Er zijn enkele cmdlets die het gebruik van hashtables ondersteunen om aangepaste of berekende eigenschappen te maken. U ziet dit vaak met Select-Object en Format-Table. De hashtables hebben een speciale syntaxis die er als volgt uitziet wanneer deze volledig is uitgevouwen.
$property = @{
name = 'totalSpaceGB'
expression = { ($_.used + $_.free) / 1GB }
}
De name cmdlet zou die kolom labelen. Het expression is een scriptblok dat wordt uitgevoerd waar $_ de waarde van het object in de pijp is. Hier volgt dat script in actie:
$drives = Get-PSDrive | Where Used
$drives | Select-Object -Property name, $property
Name totalSpaceGB
---- ------------
C 238.472652435303
Ik heb dat in een variabele geplaatst, maar het kan gemakkelijk inline worden gedefinieerd en je kunt in- en uitkoren name tot n en expressione met het moment dat je eraan zit.
$drives | Select-Object -property name, @{n='totalSpaceGB';e={($_.used + $_.free) / 1GB}}
Ik vind het persoonlijk niet leuk hoe lang dat opdrachten maakt en het bevordert vaak een aantal slechte gedragingen waar ik niet in zal komen. Ik maak waarschijnlijk een nieuwe hashtabel of pscustomobject met alle velden en eigenschappen die ik wil in plaats van deze benadering in scripts te gebruiken. Maar er is veel code die dit doet, dus ik wilde dat je er rekening mee moet houden. Ik heb het over het maken van een pscustomobject later tijdstip.
Aangepaste sorteerexpressie
Het is eenvoudig om een verzameling te sorteren als de objecten de gegevens bevatten waarop u wilt sorteren. U kunt de gegevens toevoegen aan het object voordat u het sorteert of een aangepaste expressie voor Sort-Objectmaken.
Get-ADUser | Sort-Object -Parameter @{ e={ Get-TotalSales $_.Name } }
In dit voorbeeld neem ik een lijst met gebruikers en gebruik ik een aantal aangepaste cmdlets om aanvullende informatie op te halen voor de sortering.
Een lijst met hashtables sorteren
Als u een lijst met hashtabellen hebt die u wilt sorteren, zult u merken dat Sort-Object de sleutels niet als eigenschappen worden behandeld. We kunnen dit afronden met behulp van een aangepaste sorteerexpressie.
$data = @(
@{name='a'}
@{name='c'}
@{name='e'}
@{name='f'}
@{name='d'}
@{name='b'}
)
$data | Sort-Object -Property @{e={$_.name}}
Hashtables splatting bij cmdlets
Dit is een van mijn favoriete dingen over hashtables die veel mensen niet vroeg ontdekken. Het idee is dat u in plaats van alle eigenschappen aan een cmdlet op één regel op te geven, ze eerst in een hashtabel kunt inpakken. Vervolgens kunt u de hashtabel op een speciale manier aan de functie geven. Hier volgt een voorbeeld van het maken van een DHCP-bereik op de normale manier.
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"
Zonder splatting te gebruiken, moeten al die dingen op één regel worden gedefinieerd. Het schuift van het scherm of loopt terug waar het voelt. Vergelijk dat nu met een opdracht die gebruikmaakt van 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
Het gebruik van het @ teken in plaats van het $ is wat de splat-bewerking aanroept.
Neem even de tijd om te waarderen hoe gemakkelijk dat voorbeeld is om te lezen. Ze zijn exact dezelfde opdracht met dezelfde waarden. De tweede is gemakkelijker te begrijpen en te onderhouden.
Ik gebruik splatting op elk moment dat de opdracht te lang wordt. Ik definieer te lang als mijn venster naar rechts schuift. Als ik drie eigenschappen voor een functie heb bereikt, is de kans groot dat ik deze herschrijf met behulp van een geplatte hashtabel.
Splatting voor optionele parameters
Een van de meest voorkomende manieren waarop ik splatting gebruik, is het omgaan met optionele parameters die afkomstig zijn van een andere plaats in mijn script. Stel dat ik een functie heb waarmee een Get-CIMInstance aanroep met een optioneel $Credential argument wordt verpakt.
$CIMParams = @{
ClassName = 'Win32_Bios'
ComputerName = $ComputerName
}
if($Credential)
{
$CIMParams.Credential = $Credential
}
Get-CIMInstance @CIMParams
Ik begin met het maken van mijn hashtabel met algemene parameters. Dan voeg ik de $Credential als het bestaat toe.
Omdat ik hier splatting gebruik, hoef ik de aanroep Get-CIMInstance maar één keer in mijn code te hebben. Dit ontwerppatroon is zeer schoon en kan gemakkelijk veel optionele parameters verwerken.
Om eerlijk te zijn, kunt u uw opdrachten schrijven om waarden voor parameters toe te staan $null . U hebt alleen niet altijd controle over de andere opdrachten die u aanroept.
Meerdere splats
U kunt meerdere hashtables naar dezelfde cmdlet splatten. Als we teruggaan naar ons oorspronkelijke splattingsvoorbeeld:
$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
Ik gebruik deze methode als ik een gemeenschappelijke set parameters heb die ik doorgeeft aan veel opdrachten.
Splatting voor schone code
Er is niets mis met het afplatten van één parameter als u codereiniger maakt.
$log = @{Path = '.\logfile.log'}
Add-Content "logging this command" @log
Uitvoerbare bestanden splatting
Splatting werkt ook op sommige uitvoerbare bestanden die gebruikmaken van een /param:value syntaxis. Robocopy.exeheeft bijvoorbeeld enkele parameters zoals deze.
$robo = @{R=1;W=1;MT=8}
robocopy source destination @robo
Ik weet niet dat dit zo nuttig is, maar ik vond het interessant.
Hashtables toevoegen
Hashtables ondersteunen de opteloperator voor het combineren van twee hashtables.
$person += @{Zip = '78701'}
Dit werkt alleen als de twee hashtables geen sleutel delen.
Geneste hashtabellen
We kunnen hashtables gebruiken als waarden in een hashtabel.
$person = @{
name = 'Kevin'
age = 36
}
$person.location = @{}
$person.location.city = 'Austin'
$person.location.state = 'TX'
Ik begon met een eenvoudige hashtabel met twee sleutels. Ik heb een sleutel toegevoegd die is aangeroepen location met een lege hashtabel. Vervolgens heb ik de laatste twee items toegevoegd aan die location hashtabel. We kunnen dit allemaal ook inline doen.
$person = @{
name = 'Kevin'
age = 36
location = @{
city = 'Austin'
state = 'TX'
}
}
Hierdoor wordt dezelfde hashtabel gemaakt die we hierboven hebben gezien en hebben toegang tot de eigenschappen op dezelfde manier.
$person.location.city
Austin
Er zijn veel manieren om de structuur van uw objecten te benaderen. Hier volgt een tweede manier om een geneste hashtabel te bekijken.
$people = @{
Kevin = @{
age = 36
city = 'Austin'
}
Alex = @{
age = 9
city = 'Austin'
}
}
Dit combineert het concept van het gebruik van hashtables als een verzameling objecten en een verzameling eigenschappen. De waarden zijn nog steeds eenvoudig toegankelijk, zelfs wanneer ze zijn genest met behulp van de gewenste benadering.
PS> $people.kevin.age
36
PS> $people.kevin['city']
Austin
PS> $people['Alex'].age
9
PS> $people['Alex']['City']
Austin
Ik gebruik de punteigenschap meestal als ik het als een eigenschap behandel. Dat zijn over het algemeen dingen die ik statisch in mijn code heb gedefinieerd en ik ken ze van boven aan mijn hoofd. Als ik de lijst of programmatisch toegang moet krijgen tot de sleutels, gebruik ik de haken om de sleutelnaam op te geven.
foreach($name in $people.keys)
{
$person = $people[$name]
'{0}, age {1}, is in {2}' -f $name, $person.age, $person.city
}
Als u hashtables kunt nesten, hebt u veel flexibiliteit en opties.
Geneste hashtables bekijken
Zodra u hashtabellen gaat nesten, hebt u een eenvoudige manier nodig om ze vanuit de console te bekijken. Als ik die laatste hashtable neem, krijg ik een uitvoer die er als volgt uitziet en het gaat alleen zo diep:
PS> $people
Name Value
---- -----
Kevin {age, city}
Alex {age, city}
Mijn opdracht voor het bekijken van deze dingen is ConvertTo-JSON omdat het erg schoon is en ik vaak JSON op andere dingen gebruik.
PS> $people | ConvertTo-Json
{
"Kevin": {
"age": 36,
"city": "Austin"
},
"Alex": {
"age": 9,
"city": "Austin"
}
}
Zelfs als u JSON niet kent, zou u moeten kunnen zien wat u zoekt. Er is een Format-Custom opdracht voor gestructureerde gegevens zoals deze, maar ik vind de JSON-weergave nog steeds beter.
Objecten maken
Soms hoeft u alleen maar een object te hebben en een hashtabel te gebruiken om eigenschappen vast te houden, wordt de taak niet uitgevoerd. Meestal wilt u de sleutels zien als kolomnamen. Een pscustomobject maakt dat makkelijk.
$person = [pscustomobject]@{
name = 'Kevin'
age = 36
}
$person
name age
---- ---
Kevin 36
Zelfs als u deze niet als eerste pscustomobject maakt, kunt u deze altijd later casten wanneer dat nodig is.
$person = @{
name = 'Kevin'
age = 36
}
[pscustomobject]$person
name age
---- ---
Kevin 36
Ik heb al gedetailleerde schrijfbewerkingen voor pscustomobject die je moet lezen na deze. Het bouwt voort op veel van de dingen die hier zijn geleerd.
Hashtables lezen en schrijven naar bestand
Opslaan in CSV
Problemen met het verkrijgen van een hashtabel om op te slaan in een CSV is een van de problemen waarnaar ik hierboven verwijst. Converteer de hashtabel naar een pscustomobject en sla deze op de juiste manier op in CSV. Dit helpt als u met een pscustomobject kolomvolgorde begint, zodat de kolomvolgorde behouden blijft. Maar u kunt het indien nodig naar een pscustomobject inline casten.
$person | ForEach-Object{ [pscustomobject]$_ } | Export-CSV -Path $path
Nogmaals, bekijk mijn write-up over het gebruik van een pscustomobject.
Een geneste hashtabel opslaan in bestand
Als ik een geneste hashtabel moet opslaan in een bestand en het vervolgens opnieuw moet lezen, gebruik ik de JSON-cmdlets om dit te doen.
$people | ConvertTo-JSON | Set-Content -Path $path
$people = Get-Content -Path $path -Raw | ConvertFrom-JSON
Er zijn twee belangrijke punten over deze methode. Ten eerste is dat de JSON meerdere regels is geschreven, dus ik moet de -Raw optie gebruiken om deze terug te lezen in één tekenreeks. De tweede is dat het geïmporteerde object geen .[hashtable] Het is nu een [pscustomobject] en dat kan problemen veroorzaken als u deze niet verwacht.
Kijk naar diep geneste hashtables. Wanneer u deze converteert naar JSON, krijgt u mogelijk niet de verwachte resultaten.
@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json
{
"a": {
"b": {
"c": "System.Collections.Hashtable"
}
}
}
Gebruik de parameter Diepte om ervoor te zorgen dat u alle geneste hashtabellen hebt uitgebreid.
@{ a = @{ b = @{ c = @{ d = "e" }}}} | ConvertTo-Json -Depth 3
{
"a": {
"b": {
"c": {
"d": "e"
}
}
}
}
Als u deze nodig hebt om te [hashtable] importeren, moet u de Export-CliXml en Import-CliXml opdrachten gebruiken.
JSON converteren naar Hashtable
Als u JSON moet converteren naar een [hashtable], is er een manier waarop ik dit moet doen met de JavaScriptSerializer in .NET.
[Reflection.Assembly]::LoadWithPartialName("System.Web.Script.Serialization")
$JSSerializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new()
$JSSerializer.Deserialize($json,'Hashtable')
Vanaf PowerShell v6 maakt JSON-ondersteuning gebruik van de NewtonSoft-JSON.NET en voegt hashtable-ondersteuning toe.
'{ "a": "b" }' | ConvertFrom-Json -AsHashtable
Name Value
---- -----
a b
PowerShell 6.2 heeft de parameter Depth toegevoegd aan ConvertFrom-Json. De standaarddiepte is 1024.
Rechtstreeks vanuit een bestand lezen
Als u een bestand hebt dat een hashtabel bevat met behulp van de PowerShell-syntaxis, kunt u het rechtstreeks importeren.
$content = Get-Content -Path $Path -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create( $content )
$scriptBlock.CheckRestrictedLanguage( $allowedCommands, $allowedVariables, $true )
$hashtable = ( & $scriptBlock )
Het importeert de inhoud van het bestand in een scriptblocken controleert vervolgens of het geen andere PowerShell-opdrachten bevat voordat het wordt uitgevoerd.
Wist u dat een modulemanifest (het psd1-bestand) slechts een hashtabel is?
Sleutels kunnen elk object zijn
Meestal zijn de sleutels alleen tekenreeksen. Dus we kunnen aanhalingstekens plaatsen om alles en het een sleutel maken.
$person = @{
'full name' = 'Kevin Marquette'
'#' = 3978
}
$person['full name']
Je kunt een aantal vreemde dingen doen die je misschien niet hebt gerealiseerd.
$person.'full name'
$key = 'full name'
$person.$key
Gewoon omdat je iets kunt doen, betekent het niet dat je moet. Die laatste ziet er net uit als een bug die wacht om te gebeuren en zou gemakkelijk verkeerd worden begrepen door iedereen die uw code leest.
Technisch gezien hoeft uw sleutel geen tekenreeks te zijn, maar ze zijn gemakkelijker te bedenken als u alleen tekenreeksen gebruikt. Indexering werkt echter niet goed met de complexe sleutels.
$ht = @{ @(1,2,3) = "a" }
$ht
Name Value
---- -----
{1, 2, 3} a
Het openen van een waarde in de hashtabel met de sleutel werkt niet altijd. Bijvoorbeeld:
$key = $ht.keys[0]
$ht.$($key)
a
$ht[$key]
a
Wanneer de sleutel een matrix is, moet u de $key variabele verpakken in een subexpressie, zodat deze kan worden gebruikt met lidtoegang (.)-notatie. U kunt ook matrixindex-notatie ([]) gebruiken.
Gebruiken in automatische variabelen
$PSBoundParameters
$PSBoundParameters is een automatische variabele die alleen in de context van een functie bestaat. Deze bevat alle parameters waarmee de functie is aangeroepen. Dit is niet precies een hashtabel, maar dicht genoeg om het als een te behandelen.
Dit omvat het verwijderen van sleutels en het splatten ervan naar andere functies. Als u merkt dat u proxyfuncties schrijft, bekijkt u deze.
Zie about_Automatic_Variables voor meer informatie.
PSBoundParameters gotcha
Een belangrijk ding om te onthouden is dat dit alleen de waarden bevat die als parameters worden doorgegeven. Als u ook parameters met standaardwaarden hebt, maar niet wordt doorgegeven door de aanroeper, $PSBoundParameters bevat deze waarden niet. Dit wordt vaak over het hoofd gezien.
$PSDefaultParameterValues
Met deze automatische variabele kunt u standaardwaarden toewijzen aan een cmdlet zonder de cmdlet te wijzigen. Bekijk dit voorbeeld.
$PSDefaultParameterValues["Out-File:Encoding"] = "UTF8"
Hiermee wordt een vermelding toegevoegd aan de $PSDefaultParameterValues hashtabel die wordt ingesteld UTF8 als de standaardwaarde voor de Out-File -Encoding parameter. Dit is sessiespecifiek, zodat u deze in uw $profile.
Ik gebruik dit vaak om vooraf waarden toe te wijzen die ik vaak typ.
$PSDefaultParameterValues[ "Connect-VIServer:Server" ] = 'VCENTER01.contoso.local'
Dit accepteert ook jokertekens, zodat u bulksgewijs waarden kunt instellen. Hier volgen enkele manieren waarop u dit kunt gebruiken:
$PSDefaultParameterValues[ "Get-*:Verbose" ] = $true
$PSDefaultParameterValues[ "*:Credential" ] = Get-Credential
Zie dit geweldige artikel over Automatische standaardwaarden van Michael Sorens voor een uitgebreidere uitsplitsing.
Regex-$Matches
Wanneer u de -match operator gebruikt, wordt er een automatische variabele $matches gemaakt met de resultaten van de overeenkomst. Als u subexpressies in uw regex hebt, worden deze subovereenkomsten ook vermeld.
$message = 'My SSN is 123-45-6789.'
$message -match 'My SSN is (.+)\.'
$Matches[0]
$Matches[1]
Benoemde overeenkomsten
Dit is een van mijn favoriete functies waar de meeste mensen niet over weten. Als u een benoemde regex-overeenkomst gebruikt, hebt u toegang tot die overeenkomst op naam voor de overeenkomsten.
$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
}
In het bovenstaande voorbeeld is het (?<Name>.*) een benoemde subexpressie. Deze waarde wordt vervolgens in de $Matches.Name eigenschap geplaatst.
Group-Object -AsHashtable
Een weinig bekende functie is Group-Object dat sommige gegevenssets voor u kunnen worden omgezet in een hashtabel.
Import-CSV $Path | Group-Object -AsHashtable -Property email
Hiermee voegt u elke rij toe aan een hashtabel en gebruikt u de opgegeven eigenschap als de sleutel om deze te openen.
Hashtables kopiëren
Een belangrijk ding om te weten is dat hashtables objecten zijn. En elke variabele is slechts een verwijzing naar een object. Dit betekent dat er meer werk nodig is om een geldige kopie van een hashtabel te maken.
Verwijzingstypen toewijzen
Wanneer u één hashtabel hebt en deze toewijst aan een tweede variabele, verwijzen beide variabelen naar dezelfde hashtabel.
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]
Dit markeert dat ze hetzelfde zijn omdat het wijzigen van de waarden in de ene waarde ook de waarden in de andere wijzigt. Dit geldt ook bij het doorgeven van hashtables aan andere functies. Als deze functies wijzigingen aanbrengen in die hashtabel, wordt uw origineel ook gewijzigd.
Ondiepe kopieën, één niveau
Als we een eenvoudige hashtabel hebben zoals hierboven, kunnen we een .Clone() ondiepe kopie maken.
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]
Hierdoor kunnen we enkele basiswijzigingen aanbrengen in een wijziging die niet van invloed zijn op de andere.
Ondiepe kopieën, genest
De reden waarom het een ondiepe kopie wordt genoemd, is omdat alleen de eigenschappen op basisniveau worden gekopieerd. Als een van deze eigenschappen een verwijzingstype is (zoals een andere hashtabel), wijzen die geneste objecten nog steeds naar elkaar.
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]
U kunt dus zien dat de verwijzing naar person de hashtabel niet is gekloond, hoewel ik de hashtabel heb gekloond. We moeten een diepe kopie maken om echt een tweede hashtabel te hebben die niet aan de eerste is gekoppeld.
Diepe kopieën
Er zijn een aantal manieren om een diepe kopie van een hashtabel te maken (en deze als hashtabel te behouden). Hier volgt een functie die PowerShell gebruikt om recursief een diepe kopie te maken:
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
}
}
}
Er worden geen andere referentietypen of matrices verwerkt, maar het is een goed uitgangspunt.
Een andere manier is om .Net te gebruiken om het te deserialiseren met behulp van CliXml , zoals in deze functie:
function Get-DeepClone
{
param(
$InputObject
)
$TempCliXmlString = [System.Management.Automation.PSSerializer]::Serialize($obj, [int32]::MaxValue)
return [System.Management.Automation.PSSerializer]::Deserialize($TempCliXmlString)
}
Voor extreem grote hashtables is de deserialiserende functie sneller wanneer deze wordt uitgeschaald. Er zijn echter enkele dingen die u moet overwegen bij het gebruik van deze methode. Omdat cliXml wordt gebruikt, is het geheugenintensief en als u enorme hashtables kloont, kan dat een probleem zijn. Een andere beperking van cliXml is er een dieptebeperking van 48. Als u een hashtabel hebt met 48 lagen geneste hashtabellen, mislukt het klonen en wordt helemaal geen hashtabel uitgevoerd.
Nog iets?
Ik heb snel veel grond bedekt. Mijn hoop is dat je iets nieuws leunt of het beter begrijpt telkens wanneer je dit leest. Omdat ik het volledige spectrum van deze functie heb behandeld, zijn er aspecten die op dit moment mogelijk niet op u van toepassing zijn. Dat is perfect ok en is een soort van verwachting, afhankelijk van hoeveel u met PowerShell werkt.