Semua yang ingin Anda ketahui tentang hashtable

Saya ingin mengambil langkah kembali dan berbicara tentang hashtables. Aku menggunakannya sepanjang waktu sekarang. Saya mengajar seseorang tentang mereka setelah pertemuan grup pengguna kami tadi malam dan saya menyadari bahwa saya memiliki kebingungan yang sama tentang mereka seperti yang dia miliki. Hashtable sangat penting di PowerShell sehingga ada baiknya untuk memiliki pemahaman yang kuat tentang mereka.

Catatan

Versi asli artikel ini muncul di blog yang ditulis oleh @KevinMarquette. Tim PowerShell berterima kasih kepada Kevin karena telah membagikan konten ini kepada kami. Silakan lihat blognya di PowerShellExplained.com.

Hashtable sebagai kumpulan hal-hal

Pertama-tama saya ingin Anda melihat Hashtable sebagai koleksi dalam definisi tradisional dari hashtable. Definisi ini memberi Anda pemahaman mendasar tentang cara kerjanya ketika digunakan untuk hal-hal yang lebih canggih nanti. Melewatkan pemahaman ini sering kali menjadi sumber kebingungan.

Apa itu array?

Sebelum saya melompat ke apa itu Hashtable , saya perlu menyebutkan array terlebih dahulu. Untuk tujuan diskusi ini, array adalah daftar atau kumpulan nilai atau objek.

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

Setelah memasukkan item ke dalam array, Anda dapat menggunakan foreach untuk melakukan iterasi di atas daftar atau menggunakan indeks untuk mengakses elemen individual dalam array.

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

Write-Output $array[3]

Anda juga dapat memperbarui nilai menggunakan indeks dengan cara yang sama.

$array[2] = 13

Saya hanya menggaruk permukaan pada array tetapi itu harus menempatkan mereka ke dalam konteks yang tepat saat saya pindah ke hashtables.

Apa itu hashtable?

Saya akan mulai dengan deskripsi teknis dasar tentang apa itu hashtable, dalam arti umum, sebelum saya beralih ke cara lain PowerShell menggunakannya.

Hashtable adalah struktur data, sama seperti array, kecuali Anda menyimpan setiap nilai (objek) menggunakan kunci. Ini adalah penyimpanan kunci/nilai dasar. Pertama, kita membuat hashtable kosong.

$ageList = @{}

Perhatikan bahwa kurung kurung kurung, digunakan untuk menentukan hashtable. Kemudian kita menambahkan item menggunakan kunci seperti ini:

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

$ageList.add( 'Alex', 9 )

Nama orang tersebut adalah kunci dan usianya adalah nilai yang ingin saya simpan.

Menggunakan tanda kurung untuk akses

Setelah menambahkan nilai ke hashtable, Anda dapat menariknya kembali menggunakan kunci yang sama (alih-alih menggunakan indeks numerik seperti yang Anda miliki untuk array).

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

Ketika aku ingin usia Kevin, aku menggunakan namanya untuk mengaksesnya. Kita dapat menggunakan pendekatan ini untuk menambahkan atau memperbarui nilai ke dalam hashtable juga. Ini sama seperti menggunakan fungsi di add() atas.

$ageList = @{}

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

$ageList['Alex'] = 9

Ada sintaks lain yang dapat Anda gunakan untuk mengakses dan memperbarui nilai yang akan saya bahas di bagian selanjutnya. Jika Anda datang ke PowerShell dari bahasa lain, contoh-contoh ini harus sesuai dengan bagaimana Anda mungkin telah menggunakan hashtable sebelumnya.

Membuat hashtable dengan nilai

Sejauh ini saya telah membuat hashtable kosong untuk contoh-contoh ini. Anda dapat mengisi kunci dan nilai sebelumnya saat membuatnya.

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

Sebagai tabel pencarian

Nilai nyata dari jenis hashtable ini adalah Anda dapat menggunakannya sebagai tabel pencarian. Berikut contoh sederhana.

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

$server = $environments[$env]

Dalam contoh ini, Anda menentukan lingkungan untuk $env variabel dan akan memilih server yang benar. Anda bisa menggunakan switch($env){...} untuk pilihan seperti ini tetapi hashtable adalah pilihan yang bagus.

Ini menjadi lebih baik ketika Anda secara dinamis membangun tabel pencarian untuk menggunakannya nanti. Jadi pikirkan tentang menggunakan pendekatan ini ketika Anda perlu merujuk silang sesuatu. Saya pikir kita akan melihat ini bahkan lebih jika PowerShell tidak begitu baik dalam pemfilteran pada pipa dengan Where-Object. Jika Anda pernah berada dalam situasi di mana performa penting, pendekatan ini perlu dipertimbangkan.

Saya tidak akan mengatakan bahwa itu lebih cepat, tetapi memang sesuai dengan aturan Jika performa penting, mengujinya.

Multiseleksi

Umumnya, Anda menganggap hashtable sebagai pasangan kunci/nilai, di mana Anda menyediakan satu kunci dan mendapatkan satu nilai. PowerShell memungkinkan Anda menyediakan array kunci untuk mendapatkan beberapa nilai.

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

Dalam contoh ini, saya menggunakan hashtable pencarian yang sama dari atas dan menyediakan tiga gaya array yang berbeda untuk mendapatkan kecocokan. Ini adalah permata tersembunyi di PowerShell yang tidak diketahui kebanyakan orang.

Iterasi hashtable

Karena hashtable adalah kumpulan pasangan kunci/nilai, Anda melakukan iterasi di atasnya secara berbeda dari yang Anda lakukan untuk array atau daftar item normal.

Hal pertama yang perlu diperhatikan adalah bahwa jika Anda menyalurkan hashtable Anda, pipa memperlakukannya seperti satu objek.

PS> $ageList | Measure-Object
count : 1

Meskipun .count properti memberi tahu Anda berapa banyak nilai yang dikandungnya.

PS> $ageList.count
2

Anda mengatasi masalah ini dengan menggunakan .values properti jika yang Anda butuhkan hanyalah nilai.

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

Seringkali lebih berguna untuk menghitung kunci dan menggunakannya untuk mengakses nilai.

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

Berikut adalah contoh yang sama dengan perulangan foreach(){...} .

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

Kami berjalan setiap kunci dalam hashtable dan kemudian menggunakannya untuk mengakses nilai. Ini adalah pola umum saat bekerja dengan hashtable sebagai koleksi.

GetEnumerator()

Itu membawa kita untuk GetEnumerator() iterasi di atas hashtable kita.

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

Enumerator memberi Anda setiap pasangan kunci/nilai satu demi satu. Ini dirancang khusus untuk kasus penggunaan ini. Terima kasih untuk Mark Kraus untuk mengingatkan saya tentang yang satu ini.

BadEnumeration

Salah satu detail penting adalah Anda tidak dapat memodifikasi hashtable saat sedang dijumlahkan. Jika kita mulai dengan contoh dasar $environments kita:

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

Dan mencoba mengatur setiap kunci ke nilai server yang sama gagal.

$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

Ini juga akan gagal meskipun sepertinya juga akan baik-baik saja:

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

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

Trik untuk situasi ini adalah mengkloning kunci sebelum melakukan enumerasi.

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

Hashtable sebagai kumpulan properti

Sejauh ini jenis objek yang kami tempatkan di hashtable kami adalah semua jenis objek yang sama. Saya menggunakan usia dalam semua contoh tersebut dan kuncinya adalah nama orang tersebut. Ini adalah cara yang bagus untuk melihatnya ketika koleksi objek Anda masing-masing memiliki nama. Cara umum lain untuk menggunakan hashtable di PowerShell adalah dengan menyimpan kumpulan properti di mana kuncinya adalah nama properti . Saya akan melangkah ke ide itu dalam contoh berikutnya.

Akses berbasis properti

Penggunaan akses berbasis properti mengubah dinamika hashtable dan bagaimana Anda dapat menggunakannya di PowerShell. Berikut adalah contoh kami yang biasa dari atas memperlakukan kunci sebagai properti.

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

Sama seperti contoh di atas, contoh ini menambahkan kunci tersebut jika belum ada di hashtable. Tergantung pada bagaimana Anda mendefinisikan kunci Anda dan apa nilai Anda, ini sedikit aneh atau cocok. Contoh daftar usia telah bekerja dengan baik hingga saat ini. Kita perlu contoh baru agar ini terasa benar ke depannya.

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

Dan kita dapat menambahkan dan mengakses atribut pada $person seperti ini.

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

Tiba-tiba hashtable ini mulai terasa dan bertindak seperti objek. Ini masih kumpulan hal-hal, jadi semua contoh di atas masih berlaku. Kami hanya mendekatinya dari sudut pandang yang berbeda.

Memeriksa kunci dan nilai

Dalam kebanyakan kasus, Anda hanya dapat menguji nilai dengan sesuatu seperti ini:

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

Ini sederhana tetapi telah menjadi sumber banyak bug bagi saya karena saya mengabaikan satu detail penting dalam logika saya. Saya mulai menggunakannya untuk menguji apakah ada kunci. Ketika nilainya $false atau nol, pernyataan tersebut akan kembali $false secara tak terduga.

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

Ini bekerja di sekitar masalah itu untuk nilai nol tetapi tidak $null vs kunci yang tidak ada. Sebagian besar waktu Anda tidak perlu membuat perbedaan itu tetapi ada fungsi ketika Anda melakukannya.

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

Kami juga memiliki ContainsValue() untuk situasi di mana Anda perlu menguji nilai tanpa mengetahui kunci atau iterasi seluruh koleksi.

Menghapus dan menghapus kunci

Anda dapat menghapus kunci dengan .Remove() fungsi .

$person.remove('age')

Menetapkan nilai kepada mereka $null hanya memberi Anda kunci yang memiliki $null nilai.

Cara umum untuk menghapus hashtable adalah dengan hanya menginisialisasinya ke hashtable kosong.

$person = @{}

Meskipun itu berhasil, cobalah untuk menggunakan fungsi sebagai gantinya clear() .

$person.clear()

Ini adalah salah satu instans di mana menggunakan fungsi membuat kode dokumentasi mandiri dan membuat niat kode sangat bersih.

Semua hal yang menyenangkan

Hashtable yang diurutkan

Secara default, hashtable tidak diurutkan (atau diurutkan). Dalam konteks tradisional, urutan tidak masalah ketika Anda selalu menggunakan kunci untuk mengakses nilai. Anda mungkin menemukan bahwa Anda ingin properti tetap dalam urutan yang Anda tentukan. Untungnya, ada cara untuk melakukannya dengan ordered kata kunci.

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

Sekarang ketika Anda menghitung kunci dan nilai, kunci dan nilai tetap dalam urutan tersebut.

Hashtable sebaris

Saat menentukan hashtable pada satu baris, Anda dapat memisahkan pasangan kunci/nilai dengan titik koma.

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

Ini akan berguna jika Anda membuatnya di pipa.

Ekspresi kustom dalam perintah alur umum

Ada beberapa cmdlet yang mendukung penggunaan hashtable untuk membuat properti kustom atau terhitung. Anda biasanya melihat ini dengan Select-Object dan Format-Table. Hashtable memiliki sintaks khusus yang terlihat seperti ini ketika sepenuhnya diperluas.

$property = @{
    name = 'totalSpaceGB'
    expression = { ($_.used + $_.free) / 1GB }
}

name adalah apa cmdlet akan melabeli kolom tersebut. expression adalah blok skrip yang dijalankan di mana $_ adalah nilai objek pada pipa. Berikut adalah skrip yang sedang beraksi:

$drives = Get-PSDrive | Where Used
$drives | Select-Object -Property name, $property

Name     totalSpaceGB
----     ------------
C    238.472652435303

Saya menempatkannya dalam variabel tetapi dapat dengan mudah didefinisikan sebaris dan Anda dapat mempersingkat name ke n dan expression ke e saat Anda berada di dalamnya.

$drives | Select-Object -property name, @{n='totalSpaceGB';e={($_.used + $_.free) / 1GB}}

Saya pribadi tidak suka berapa lama yang membuat perintah dan sering mempromosikan beberapa perilaku buruk yang tidak akan saya dapatkan. Saya lebih cenderung membuat hashtable baru atau pscustomobject dengan semua bidang dan properti yang saya inginkan alih-alih menggunakan pendekatan ini dalam skrip. Tapi ada banyak kode di luar sana yang melakukan ini jadi aku ingin kau menyadarinya. Saya berbicara tentang membuat pscustomobject nanti.

Ekspresi pengurutan kustom

Sangat mudah untuk mengurutkan koleksi jika objek memiliki data yang ingin Anda urutkan. Anda dapat menambahkan data ke objek sebelum mengurutkannya atau membuat ekspresi kustom untuk Sort-Object.

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

Dalam contoh ini saya mengambil daftar pengguna dan menggunakan beberapa cmdlet kustom untuk mendapatkan informasi tambahan hanya untuk pengurutan.

Mengurutkan daftar Hashtables

Jika Anda memiliki daftar hashtable yang ingin Anda urutkan, Anda akan menemukan bahwa Sort-Object kunci tidak diperlakukan sebagai properti. Kita bisa mendapatkan putaran dengan menggunakan ekspresi pengurutan kustom.

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

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

Splatting hashtables pada cmdlet

Ini adalah salah satu hal favorit saya tentang hashtable yang tidak ditemukan banyak orang sejak dini. Idenya adalah bahwa alih-alih menyediakan semua properti ke cmdlet pada satu baris, Anda dapat mengemasnya ke dalam hashtable terlebih dahulu. Kemudian Anda dapat memberikan hashtable ke fungsi dengan cara khusus. Berikut adalah contoh pembuatan cakupan DHCP dengan cara normal.

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"

Tanpa menggunakan splatting, semua hal tersebut perlu didefinisikan pada satu baris. Ini baik menggulir dari layar atau akan membungkus di mana pun rasanya seperti itu. Sekarang bandingkan dengan perintah yang menggunakan 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

Penggunaan @ tanda alih-alih $ adalah apa yang memanggil operasi splat.

Luangkan waktu sejenak untuk menghargai betapa mudahnya contoh itu dibaca. Mereka adalah perintah yang sama persis dengan semua nilai yang sama. Yang kedua lebih mudah dipahami dan dipertahankan ke depan.

Saya menggunakan splatting kapan saja perintah menjadi terlalu panjang. Saya mendefinisikan terlalu lama karena menyebabkan jendela saya menggulir ke kanan. Jika saya menekan tiga properti untuk fungsi, kemungkinan saya akan menulis ulang menggunakan hashtable berceceran.

Splatting untuk parameter opsional

Salah satu cara paling umum yang saya gunakan splatting adalah dengan menangani parameter opsional yang berasal dari beberapa tempat lain dalam skrip saya. Katakanlah saya memiliki fungsi yang membungkus Get-CIMInstance panggilan yang memiliki argumen opsional $Credential .

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

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

Get-CIMInstance @CIMParams

Saya mulai dengan membuat hashtable saya dengan parameter umum. Kemudian saya menambahkan $Credential jika ada. Karena saya menggunakan splatting di sini, saya hanya perlu memiliki panggilan ke Get-CIMInstance dalam kode saya sekali. Pola desain ini sangat bersih dan dapat menangani banyak parameter opsional dengan mudah.

Agar adil, Anda dapat menulis perintah Anda untuk mengizinkan $null nilai untuk parameter. Anda hanya tidak selalu memiliki kontrol atas perintah lain yang Anda panggil.

Beberapa splat

Anda dapat memercikkan beberapa hashtable ke cmdlet yang sama. Jika kita mengunjungi kembali contoh splatting asli kita:

$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

Saya akan menggunakan metode ini ketika saya memiliki sekumpulan parameter umum yang saya teruskan ke banyak perintah.

Splatting untuk kode bersih

Tidak ada yang salah dengan percikan parameter tunggal jika membuat Anda membersihkan kode.

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

Splatting executables

Splatting juga berfungsi pada beberapa executable yang menggunakan /param:value sintaks. Robocopy.exe, misalnya, memiliki beberapa parameter seperti ini.

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

Saya tidak tahu bahwa ini semua yang berguna, tapi saya menemukannya menarik.

Menambahkan hashtable

Hashtable mendukung operator penambahan untuk menggabungkan dua hashtable.

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

Ini hanya berfungsi jika kedua hashtable tidak berbagi kunci.

Hashtable berlapis

Kita dapat menggunakan hashtable sebagai nilai di dalam hashtable.

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

Saya mulai dengan hashtable dasar yang berisi dua kunci. Saya menambahkan kunci yang disebut location dengan hashtable kosong. Kemudian saya menambahkan dua item terakhir ke hashtable tersebut location . Kita bisa melakukan ini semua sebaris juga.

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

Ini menciptakan hashtable yang sama dengan yang kita lihat di atas dan dapat mengakses properti dengan cara yang sama.

$person.location.city
Austin

Ada banyak cara untuk mendekati struktur objek Anda. Berikut adalah cara kedua untuk melihat hashtable berlapis.

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

Ini mencampur konsep penggunaan hashtable sebagai kumpulan objek dan kumpulan properti. Nilainya masih mudah diakses bahkan ketika ditumpuk menggunakan pendekatan apa pun yang Anda sukai.

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

Saya cenderung menggunakan properti titik ketika saya memperlakukannya seperti properti . Itu umumnya hal-hal yang telah saya tentukan secara statis dalam kode saya dan saya tahu mereka dari atas kepala saya. Jika saya perlu menjalankan daftar atau mengakses kunci secara terprogram, saya menggunakan tanda kurung untuk memberikan nama kunci.

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

Memiliki kemampuan untuk bersarang hashtable memberi Anda banyak fleksibilitas dan opsi.

Melihat hashtable berlapis

Segera setelah Anda mulai bersarang hashtable, Anda akan membutuhkan cara mudah untuk melihatnya dari konsol. Jika saya mengambil hashtable terakhir, saya mendapatkan output yang terlihat seperti ini dan hanya masuk begitu dalam:

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

Saya pergi ke perintah untuk melihat hal-hal ini adalah ConvertTo-JSON karena sangat bersih dan saya sering menggunakan JSON pada hal-hal lain.

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

Bahkan jika Anda tidak tahu JSON, Anda harus dapat melihat apa yang Anda cari. Ada Format-Custom perintah untuk data terstruktur seperti ini tetapi saya masih lebih menyukai tampilan JSON.

Membuat objek-objek

Terkadang Anda hanya perlu memiliki objek dan menggunakan hashtable untuk menyimpan properti hanya tidak menyelesaikan pekerjaan. Umumnya Anda ingin melihat kunci sebagai nama kolom. A pscustomobject membuatnya mudah.

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

$person

name  age
----  ---
Kevin  36

Bahkan jika Anda tidak membuatnya sebagai pscustomobject awalnya, Anda selalu dapat melemparkannya nanti saat diperlukan.

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

[pscustomobject]$person

name  age
----  ---
Kevin  36

Saya sudah memiliki penulisan terperinci untuk pscustomobject yang harus Anda baca setelah yang satu ini. Ini dibangun di atas banyak hal yang dipelajari di sini.

Membaca dan menulis hashtable ke file

Menyimpan ke CSV

Berjuang dengan mendapatkan hashtable untuk menyimpan ke CSV adalah salah satu kesulitan yang saya lihat di atas. Konversikan hashtable Anda ke pscustomobject dan itu akan disimpan dengan benar ke CSV. Ini membantu jika Anda memulai dengan pscustomobject sehingga urutan kolom dipertahankan. Tetapi Anda dapat melemparkannya ke sebaris pscustomobject jika diperlukan.

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

Sekali lagi, lihat tulisan saya tentang menggunakan pscustomobject.

Menyimpan hashtable berlapis ke file

Jika saya perlu menyimpan hashtable berlapis ke file dan kemudian membacanya kembali, saya menggunakan cmdlet JSON untuk melakukannya.

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

Ada dua poin penting tentang metode ini. Pertama adalah JSON ditulis secara multibaris sehingga saya perlu menggunakan -Raw opsi untuk membacanya kembali ke dalam satu string. Yang Kedua adalah bahwa objek yang diimpor bukan lagi .[hashtable] Sekarang menjadi [pscustomobject] dan yang dapat menyebabkan masalah jika Anda tidak mengharapkannya.

Perhatikan hashtable yang sangat bersarang. Ketika Anda mengonversinya ke JSON, Anda mungkin tidak mendapatkan hasil yang Anda harapkan.

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

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

Gunakan parameter Kedalaman untuk memastikan bahwa Anda telah memperluas semua hashtable berlapis.

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

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

Jika Anda membutuhkannya untuk menjadi [hashtable] impor, maka Anda perlu menggunakan Export-CliXml perintah dan Import-CliXml .

Mengonversi JSON ke Hashtable

Jika Anda perlu mengonversi JSON ke [hashtable], ada satu cara yang saya tahu untuk melakukannya dengan JavaScriptSerializer di .NET.

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

Dimulai di PowerShell v6, dukungan JSON menggunakan newtonSoft JSON.NET dan menambahkan dukungan hashtable.

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

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

PowerShell 6.2 menambahkan parameter Kedalaman ke ConvertFrom-Json. Kedalaman default adalah 1024.

Membaca langsung dari file

Jika Anda memiliki file yang berisi hashtable menggunakan sintaks PowerShell, ada cara untuk mengimpornya secara langsung.

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

Ini mengimpor konten file ke dalam scriptblock, lalu memeriksa untuk memastikan tidak memiliki perintah PowerShell lainnya di dalamnya sebelum menjalankannya.

Pada catatan itu, tahukah Anda bahwa manifes modul (file psd1) hanya hashtable?

Kunci dapat berupa objek apa pun

Sebagian besar waktu, kunci hanyalah string. Jadi kita dapat menempatkan kutipan di sekitar apa pun dan menjadikannya kunci.

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

Anda dapat melakukan beberapa hal aneh yang mungkin tidak Anda sadari dapat Anda lakukan.

$person.'full name'

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

Hanya karena kau bisa melakukan sesuatu, itu tidak berarti bahwa kau harus. Yang terakhir hanya terlihat seperti bug yang menunggu untuk terjadi dan akan dengan mudah disalahpahami oleh siapa pun yang membaca kode Anda.

Secara teknis kunci Anda tidak harus berupa string tetapi lebih mudah dipikirkan jika Anda hanya menggunakan string. Namun, pengindeksan tidak berfungsi dengan baik dengan kunci yang kompleks.

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

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

Mengakses nilai dalam hashtable oleh kuncinya tidak selalu berfungsi. Contohnya:

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

Ketika kunci adalah array, Anda harus membungkus $key variabel dalam subekspresi sehingga dapat digunakan dengan notasi akses anggota (.). Atau, Anda dapat menggunakan notasi indeks array ([]).

Gunakan dalam variabel otomatis

$PSBoundParameters

$PSBoundParameters adalah variabel otomatis yang hanya ada di dalam konteks fungsi. Ini berisi semua parameter yang dipanggil fungsi. Ini bukan hashtable tetapi cukup dekat sehingga Anda dapat memperlakukannya seperti itu.

Itu termasuk menghapus kunci dan memercikkannya ke fungsi lain. Jika Anda menemukan diri Anda menulis fungsi proksi, lihat yang satu ini lebih dekat.

Lihat about_Automatic_Variables untuk detail selengkapnya.

PSBoundParameters gotcha

Satu hal penting yang perlu diingat adalah bahwa ini hanya mencakup nilai yang diteruskan sebagai parameter. Jika Anda juga memiliki parameter dengan nilai default tetapi tidak diteruskan oleh pemanggil, $PSBoundParameters tidak berisi nilai-nilai tersebut. Ini biasanya diabaikan.

$PSDefaultParameterValues

Variabel otomatis ini memungkinkan Anda menetapkan nilai default ke cmdlet apa pun tanpa mengubah cmdlet. Lihat contoh ini.

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

Ini menambahkan entri ke $PSDefaultParameterValues hashtable yang ditetapkan UTF8 sebagai nilai default untuk Out-File -Encoding parameter . Ini khusus sesi sehingga Anda harus menempatkannya di .$profile

Saya sering menggunakan ini untuk menetapkan nilai sebelumnya yang saya ketik cukup sering.

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

Ini juga menerima kartubebas sehingga Anda dapat mengatur nilai secara massal. Berikut adalah beberapa cara untuk menggunakannya:

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

Untuk perincian yang lebih mendalam, lihat artikel hebat ini tentang Default Otomatis oleh Michael Sorens.

Regex $Matches

Saat Anda menggunakan -match operator, variabel otomatis yang disebut $matches dibuat dengan hasil kecocokan. Jika Anda memiliki sub ekspresi dalam regex Anda, sub-kecocokan tersebut juga tercantum.

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

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

Kecocokan bernama

Ini adalah salah satu fitur favorit saya yang tidak diketahui kebanyakan orang. Jika Anda menggunakan kecocokan regex bernama, maka Anda dapat mengakses yang cocok berdasarkan nama pada kecocokan.

$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
}

Dalam contoh di atas, (?<Name>.*) adalah sub ekspresi bernama. Nilai ini kemudian ditempatkan di $Matches.Name properti .

Objek Grup -AsHashtable

Salah satu fitur yang sedikit diketahui adalah dapat mengubah beberapa himpunan Group-Object data menjadi hashtable untuk Anda.

Import-CSV $Path | Group-Object -AsHashtable -Property email

Ini akan menambahkan setiap baris ke dalam hashtable dan menggunakan properti yang ditentukan sebagai kunci untuk mengaksesnya.

Menyalin Hashtable

Satu hal penting yang perlu diketahui adalah bahwa hashtable adalah objek. Dan setiap variabel hanyalah referensi ke objek. Ini berarti bahwa dibutuhkan lebih banyak pekerjaan untuk membuat salinan hashtable yang valid.

Menetapkan jenis referensi

Ketika Anda memiliki satu hashtable dan menetapkannya ke variabel kedua, kedua variabel menunjuk ke hashtable yang sama.

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]

Ini menyoroti bahwa nilainya sama karena mengubah nilai dalam satu nilai juga akan mengubah nilai di nilai lainnya. Ini juga berlaku saat meneruskan hashtable ke fungsi lain. Jika fungsi-fungsi tersebut membuat perubahan pada hashtable tersebut, asli Anda juga diubah.

Salinan dangkal, tingkat tunggal

Jika kita memiliki hashtable sederhana seperti contoh kita di atas, kita dapat menggunakan .Clone() untuk membuat salinan dangkal.

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]

Ini akan memungkinkan kita untuk membuat beberapa perubahan dasar pada perubahan yang tidak berdampak pada yang lain.

Salinan dangkal, bersarang

Alasan mengapa itu disebut salinan dangkal adalah karena hanya menyalin properti tingkat dasar. Jika salah satu properti tersebut adalah jenis referensi (seperti hashtable lain), maka objek berlapis tersebut masih akan menunjuk satu sama lain.

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]

Jadi Anda dapat melihat bahwa meskipun saya mengkloning hashtable, referensi untuk person tidak dikloning. Kita perlu membuat salinan mendalam untuk benar-benar memiliki hashtable kedua yang tidak terkait dengan yang pertama.

Salinan mendalam

Ada beberapa cara untuk membuat salinan mendalam dari hashtable (dan menyimpannya sebagai hashtable). Berikut adalah fungsi menggunakan PowerShell untuk membuat salinan mendalam secara rekursif:

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
        }
    }
}

Ini tidak menangani jenis referensi atau array lainnya, tetapi ini adalah titik awal yang baik.

Cara lain adalah menggunakan .Net untuk mendeserialisasinya menggunakan CliXml seperti dalam fungsi ini:

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

Untuk hashtable yang sangat besar, fungsi deserialisasi lebih cepat saat diskalakan. Namun, ada beberapa hal yang perlu dipertimbangkan saat menggunakan metode ini. Karena menggunakan CliXml, itu intensif memori dan jika Anda mengkloning hashtable besar, itu mungkin menjadi masalah. Batasan lain dari CliXml adalah ada batasan kedalaman 48. Artinya, jika Anda memiliki hashtable dengan 48 lapisan hashtable berlapis, kloning akan gagal dan tidak ada hashtable yang akan menjadi output sama sekali.

Ada lagi?

Aku menutupi banyak tanah dengan cepat. Harapan saya adalah bahwa Anda pergi condong sesuatu yang baru atau memahaminya lebih baik setiap kali Anda membaca ini. Karena saya membahas spektrum lengkap fitur ini, ada aspek yang mungkin tidak berlaku untuk Anda sekarang. Itu sangat OK dan agak diharapkan tergantung pada berapa banyak Anda bekerja dengan PowerShell.