Bagikan melalui


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.

Nota

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 objek

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 membahas mengenai Hashtable, saya perlu menyebutkan terlebih dahulu array. 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 baru memahami sedikit tentang array, tetapi hal ini seharusnya membuatnya lebih jelas saat saya beralih 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 tabel hash kosong.

$ageList = @{}

Perhatikan bahwa kurung kurawal, bukan kurung biasa, 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 mengetahui usia Kevin, aku menggunakan namanya. Kita dapat menggunakan pendekatan ini untuk menambahkan atau memperbarui nilai ke dalam hashtable juga. Ini sama seperti menggunakan metode 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 akan cocok dengan cara Anda mungkin telah menggunakan hashtables sebelumnya.

Membuat tabel hash dengan nilai

Sejauh ini saya sudah membuat tabel hash 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 adalah 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 tabel hash adalah opsi 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 lebih sering jika PowerShell tidak begitu baik dalam pemfilteran melalui pipeline 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 tabel hash pencarian yang sama seperti yang disebutkan sebelumnya dan menyediakan tiga gaya array berbeda untuk mendapatkan kecocokan. Ini adalah permata tersembunyi di PowerShell yang tidak diketahui kebanyakan orang.

Iterasi tabel hash

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 menjelajahi setiap kunci dalam hashtable dan kemudian menggunakan kunci tersebut untuk mengakses nilai. Ini adalah pola umum saat bekerja dengan hashtables sebagai kumpulan.

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

Mencoba mengatur setiap kunci agar memiliki nilai server yang sama ternyata 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 biasa kami dari atas yang 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-kunci Anda dan apa nilai-nilainya, ini bisa jadi agak aneh atau cocok sekali. 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'

Secara tiba-tiba, hashtable ini mulai terasa dan bertindak seperti sebuah 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 mengatasi masalah tersebut untuk nilai nol, tetapi tidak untuk $null dibandingkan kunci yang tidak ada. Sebagian besar waktu Anda tidak perlu membuat perbedaan itu tetapi ada metode 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 membersihkan kunci

Anda dapat menghapus kunci dengan metode .Remove()

$person.Remove('age')

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

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

$person = @{}

Meskipun itu berhasil, cobalah untuk menggunakan metode Clear() sebagai gantinya.

$person.Clear()

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

Semua hal yang menyenangkan

Hashtable yang diurutkan

Menurut default, hashtables tidak 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, mereka tetap dalam urutan yang sama.

Hashtable tertanam

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 jalur pemrosesan 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 bagaimana cmdlet akan menamakan kolom tersebut. Expression adalah blok skrip yang dijalankan di mana $_ adalah nilai objek pada pipa. Berikut adalah skrip dalam aksi:

$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 secara langsung dan Anda dapat mempersingkat Name ke n dan Expression ke e sambil melakukannya.

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

Saya pribadi tidak suka seberapa panjang itu membuat perintah dan sering kali mendorong beberapa perilaku buruk yang tidak akan saya bahas. 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 akan berbicara tentang membuat pscustomobject di kemudian waktu.

Ekspresi pengurutan khusus

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 tabel hash

Jika Anda memiliki daftar hashtable yang ingin Anda urutkan, Anda akan menemukan bahwa Sort-Object tidak memperlakukan kunci sebagai properti. Kita bisa mengatasi masalah tersebut 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 banyak orang temukan dari awal. 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 tabel hash ke fungsi dengan cara yang 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. Entah menggulir keluar dari layar atau membungkus di mana saja sesuai keinginannya. 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 simbol @ alih-alih $ menyebabkan operasi splat.

Luangkan waktu sejenak untuk menghargai betapa mudahnya contoh itu dibaca. Perintah-perintah tersebut adalah 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 panjang sebagai menyebabkan jendela saya bergeser ke kanan. Jika saya menekan tiga properti untuk fungsi, kemungkinan saya akan menulis ulang menggunakan hashtable berceceran.

Splatting untuk parameter opsional

Salah satu cara yang paling umum saya gunakan untuk splatting adalah menangani parameter opsional yang berasal dari bagian 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 memanggil Get-CimInstance dalam kode saya sekali. Pola desain ini sangat bersih dan dapat menangani banyak parameter opsional dengan mudah.

Supaya adil, Anda dapat menulis perintah Anda untuk mengizinkan nilai $null 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 meninjau ulang 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 menggunakan parameter tunggal jika itu membuat kode Anda lebih bersih.

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

Penyebaran berkas eksekusi

Splatting juga berfungsi pada beberapa file yang dapat dieksekusi yang menggunakan sintaks /param:value. 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 tabel hash

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 elemen dalam sebuah hashtable.

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

Saya mulai dengan sebuah tabel-hash dasar yang berisi dua kunci. Saya menambahkan kunci dengan nama location dengan hashtable kosong. Kemudian saya menambahkan dua item terakhir ke hashtable tersebut location . Kita bisa melakukan ini semua sekaligus juga.

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

Ini membuat hashtable yang sama dengan yang telah kita lihat di atas dan memungkinkan kita mengakses sifat-sifatnya dengan cara yang sama.

$person.location.city
Austin

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

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

Ini memadukan konsep penggunaan hashtable sebagai kumpulan objek dan kumpulan sifat. 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 notasi titik ketika saya menganggapnya sebagai sebuah properti. Itu umumnya hal-hal yang sudah saya tetapkan secara statis dalam kode saya dan saya mengetahuinya dengan ingatan. 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 bisa dieksplorasi hingga tingkat tertentu.

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

Perintah andalan saya untuk melihat hal-hal ini adalah ConvertTo-Json karena sangat bersih dan saya sering menggunakan JSON untuk 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

Terkadang Anda hanya perlu memiliki sebuah objek, dan menggunakan hashtable untuk menyimpan properti tidak cukup efektif. Paling umum, Anda ingin melihat kunci sebagai nama kolom. Sebuah pscustomobject membuat segalanya menjadi 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 memanfaatkan banyak hal yang telah dipelajari di sini.

Membaca dan menulis tabel hash ke dalam berkas

Menyimpan ke CSV

Mengatasi kesulitan dalam membuat hashtable yang dapat disimpan ke CSV adalah salah satu hal yang saya sebutkan sebelumnya. 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 mengubahnya menjadi sebaris pscustomobject jika diperlukan.

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

Sekali lagi, lihat tulisan saya tentang menggunakan pscustomobject.

Menyimpan tabel hash bersarang ke dalam 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. Kedua, objek yang diimpor bukan lagi [hashtable]. Sekarang menjadi [pscustomobject] dan hal tersebut 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 membuka semua tabel hash bertingkat.

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

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

Jika Anda membutuhkannya menjadi [hashtable] pada saat impor, maka Anda perlu menggunakan perintah Export-CliXml 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 pada 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.

Ngomong-ngomong, tahukah Anda bahwa manifes modul (*.psd1* file) hanyalah sebuah 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.

Meskipun secara teknis kunci Anda tidak harus berupa string, dalam konteks ini, lebih mudah dipahami 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 dengan kuncinya tidak selalu berfungsi. Contohnya:

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

Ketika kunci berupa array, Anda harus mengapit variabel $key dalam subekspresi agar 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.

Kejutan PSBoundParameters

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 yang sering saya ketik.

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

Ini juga menerima wildcard sehingga Anda dapat mengatur nilai dalam jumlah besar. Berikut adalah beberapa cara untuk menggunakannya:

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

Untuk perincian yang lebih mendalam, lihat artikel menarik ini tentang Pengaturan Bawaan Otomatis oleh Michael Sorens.

Regex $Matches

Saat Anda menggunakan operator -match, variabel otomatis yang bernama $Matches dibuat dengan hasil dari kecocokan tersebut. 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]

Pencocokan Bernama

Ini adalah salah satu fitur favorit saya yang tidak diketahui kebanyakan orang. Jika Anda menggunakan pola regex yang diberi nama, maka Anda dapat mengakses hasil pencocokan itu berdasarkan nama dari hasil pencocokan.

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

Group-Object -AsHashtable

Salah satu fitur Group-Object yang jarang diketahui adalah kemampuannya untuk mengubah beberapa himpunan 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 Tabel Hash

Satu hal penting yang perlu diketahui adalah bahwa tabel hash adalah objek. Dan setiap variabel hanyalah referensi ke objek. Ini berarti bahwa dibutuhkan lebih banyak upaya untuk membuat salinan tabel hash 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 keduanya sama karena mengubah nilai dalam satu juga akan mengubah nilai dalam yang lain. Ini juga berlaku saat meneruskan hashtable ke fungsi lain. Jika fungsi-fungsi tersebut membuat perubahan pada hashtable, data asli Anda juga ikut berubah.

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 membuat beberapa perubahan dasar pada satu tanpa berdampak pada yang lain.

Salinan dangkal, bertingkat

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 lengkap

Ada beberapa cara untuk membuat salinan lengkap 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 ketika berkembang. Namun, ada beberapa hal yang perlu dipertimbangkan sewaktu menggunakan metode ini. Karena menggunakan CliXml, penggunaan memorinya sangat tinggi dan jika Anda mengkloning hashtable besar, itu bisa menjadi kendala. Batasan lain dari CliXml adalah ada batasan kedalaman 48. Artinya, jika Anda memiliki hashtable dengan 48 lapisan bertingkat, kloning akan gagal dan tidak ada keluaran hashtable sama sekali.

Ada lagi?

Aku mencapai banyak hal dengan cepat. Harapan saya adalah Anda mendapatkan sesuatu yang baru atau lebih memahaminya setiap kali Anda membacanya. 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.