共用方式為


您想知道的有關哈希表的所有資訊

我想退一步,談論 哈希表。 我現在一直使用它們。 昨晚在我們的使用者群組會議後,我教了一個人關於它們的事情,並意識到我和他一樣對它們感到困惑。 哈希表在 PowerShell 中非常重要,因此最好有紮實的瞭解。

備註

本文的原始版本出現在@KevinMarquette撰寫的部落格上。 PowerShell 小組感謝 Kevin 與我們分享此內容。 請在 PowerShellExplained.com查看他的部落格。

哈希表作為事物的集合

我希望您先在哈希表的傳統定義中看到 哈希表 做為集合。 此定義可讓您在稍後用於更進階內容時,瞭解其運作方式。 略過這項理解通常是混淆的來源。

什麼是陣列?

在跳到 哈希表 是什麼之前,我必須先提及 陣列 。 為了進行此討論,陣列是值或物件的清單或集合。

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

將專案放入陣列之後,您可以使用 foreach 來逐一查看清單,或使用索引來存取陣列中的個別元素。

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

Write-Output $array[3]

您也可以以相同方式使用索引來更新值。

$array[2] = 13

我只是初步了解了陣列,不過這應該能幫助我在進一步學習哈希表時更好地理解它們。

什麼是哈希表?

從一般意義上說,我會先從基本技術描述哈希表開始,再轉換到 PowerShell 使用這些哈希表的其他方式。

哈希表是一種數據結構,與陣列非常類似,只是您使用索引鍵來儲存每個值(物件)。 這是基本的索引鍵/值存放區。 首先,我們會建立空的哈希表。

$ageList = @{}

請注意,用來定義哈希表的是大括號,而不是括號。 然後我們會使用類似下列的鍵值來新增專案:

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

$ageList.Add( 'Alex', 9 )

這個人的名字作為索引鍵,他的年齡是我想要儲存的值。

使用括弧進行存取

一旦您將值新增至哈希表,就可以使用相同的索引鍵來將其提取出來(而不是像數位一樣使用數值索引)。

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

當我想要凱文的年齡時,我用他的名字來存取它。 我們也可以使用此方法,將值新增或更新至哈希表。 這就像使用 Add() 上述方法一樣。

$ageList = @{}

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

$ageList['Alex'] = 9

您可以使用另一個語法來存取和更新稍後一節將討論的值。 如果您從另一種語言轉換到 PowerShell,這些範例應該符合您過去使用雜湊表的方式。

利用值來建立哈希表

到目前為止,我已經為這些範例建立了空的哈希表。 您可以在建立索引鍵和值時預先填入這些索引鍵和值。

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

做為查閱表格

這種類型的哈希表的真正價值在於您可以將其用作查閱表。 以下是簡單的範例。

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

$server = $environments[$env]

在此範例中,您會指定變數的環境 $env ,而且它會挑選正確的伺服器。 您可以對這樣的選取範圍使用switch($env){...},但使用哈希表也是一個不錯的選擇。

當您動態建置查閱表格以供稍後使用時,這會變得更好。 因此,當您需要交叉參考某些專案時,請考慮使用此方法。 我認為,如果 PowerShell 不太擅長使用 Where-Object篩選管道,我們就會看到這一點。 若您身處於效能很重要的情況下,就必須考慮這個方法。

我不會說它更快,但它確實符合規則, 如果效能很重要,測試它

多重選取

一般而言,您會將哈希表視為索引鍵/值組,您可以在其中提供一個索引鍵並取得一個值。 PowerShell 可讓您提供索引鍵陣列來取得多個值。

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

在此範例中,我使用上面的相同查閱哈希表,並提供三種不同的陣列風格來取得匹配項目。 這是 PowerShell 中大多數人不知道的隱藏寶石。

反覆運算哈希表

因為哈希表是索引鍵/值組的集合,所以您逐一查看它的方式與陣列或一般項目清單不同。

要注意的第一件事是,如果您透過管線傳送哈希表,管線會將其視為單一物件。

PS> $ageList | Measure-Object
count : 1

即使 Count 屬性會告訴您其包含的值數目。

PS> $ageList.Count
2

如果您只需要值,可以使用 Values 屬性來解決此問題。

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

列舉索引鍵並使用這些索引鍵來存取值通常更有用。

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

以下是與 foreach(){...} 迴圈相同的範例。

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

我們會遍歷哈希表中的每個鍵,然後使用它來存取值。 這是使用哈希表做為集合時的常見模式。

GetEnumerator()

這讓我們進入 GetEnumerator() 以遍歷我們的哈希表。

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

枚舉器會依序提供每個鍵值對。 它專為此使用案例所設計。 謝謝你 馬克·克勞斯 提醒我這一點。

BadEnumeration

重要的一點是,當哈希表正在被列舉時,您無法修改它。 如果我們從基本 $environments 範例開始:

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

將每個鍵設定為相同的伺服器值的嘗試都會失敗。

$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

即使看起來應該也沒問題,這也會失敗:

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

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

這種情況的方法是先複製鍵值,再執行列舉。

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

備註

您無法複製包含單一索引鍵的雜湊表。 PowerShell 拋出錯誤。 相反地,您會將 Keys 屬性轉換成陣列,然後逐一查看陣列。

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

哈希表做為屬性的集合

到目前為止,我們在哈希表中放置的物件類型都是相同的物件類型。 我在所有這些例子中使用年齡,關鍵是人的名字。 當您的物件集合各有名稱時,這是查看它的絕佳方式。 在 PowerShell 中使用哈希表的另一個常見方式是保存屬性集合,其中索引鍵是屬性的名稱。 在下一個範例中,我將逐步了解這個想法。

屬性型存取

屬性型存取的使用會變更哈希表的動態,以及如何在PowerShell中使用它們。 以下是上述的一般範例,將索引鍵視為屬性。

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

就像上述範例一樣,如果這些索引鍵不存在於哈希表中,就會新增這些索引鍵。 根據您定義索引鍵的方式,以及您的值為何,這要麼有點奇怪,要麼完全適合。 年齡清單範例到目前為止運作良好。 我們需要一個新的範例,這樣未來的發展才能感覺合適。

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

我們可以在 $person 上新增和存取屬性,如下所示。

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

突然間,這個哈希表開始感覺並運作得像一個物件。 它仍然是專案的集合,因此上述所有範例仍適用。 我們只是從不同的角度來接近它。

檢查索引鍵和值

在大部分情況下,您只要使用類似下列內容來測試值:

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

這很簡單,但一直是我許多 Bug 的來源,因為我在邏輯中忽略了一個重要細節。 我開始使用它來測試密鑰是否存在。 當值為 $false 或零時,該語句會意外傳回 $false

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

這工作針對零值問題,但不適用於 $null 與不存在的鍵。 大部分時候,您不需要做出這種區別,但當您這樣做時,有方法。

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

我們也提供 ContainsValue(),適用於需要測試值但不知道索引鍵或不用遍歷整個集合的情況。

拿掉和清除金鑰

您可以使用 Remove() 方法移除索引鍵。

$person.Remove('age')

$null 值指派給它們後,您將獲得一個具有 $null 值的鍵。

清除哈希表的常見方法是將它初始化為空的哈希表。

$person = @{}

雖然運作正常,請嘗試改用 Clear() 方法。

$person.Clear()

這是使用 方法建立自我記錄程序代碼的其中一個實例,而且會讓程式碼的意圖非常清楚。

所有有趣的東西

已排序的哈希表

預設情況下,哈希表不是有序的(也就是說,未排序的)。 在傳統情境中,當您一律使用鍵來存取值時,順序並不重要。 您可能會發現您想要讓屬性維持在您定義它們的順序。 謝天謝地,有一種方法可以用 ordered 關鍵詞來執行此動作。

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

現在,當您列舉索引鍵和值時,它們會維持該順序。

內嵌哈希表

當您在一行上定義哈希表時,可以使用分號分隔索引鍵/值組。

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

如果您要在管道上建立它們,這會派上用場。

通用管線命令中的自定義表達式

有幾個命令列小程式支援使用哈希表來建立自定義或計算屬性。 您通常會使用 Select-ObjectFormat-Table來看到此專案。 哈希表具有特殊語法,在完全展開時看起來像這樣。

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

Cmdlet Name 會為該數據行加上標籤。 Expression是執行的腳本區塊,其中 $_ 是管道上 物件的值。 以下是作用中的文稿:

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

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

我將它放在一個變數中,但也可以很容易地內嵌定義,並且在處理的同時,您可以將 Name 縮短為 n,以及將 Expression 縮短為 e

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

我個人不喜歡這樣讓命令變得冗長,它經常引發一些我不願深入討論的不良行為。 我更可能建立一個包含我想要的所有字段和屬性的新的哈希表或 pscustomobject,而不是在腳本中使用此方法。 但有許多程式代碼可以這麼做,所以我希望您知道它。 我稍後會談論如何建立pscustomobject

自定義排序表達式

如果物件具有您要排序的數據,則很容易排序集合。 在您排序數據之前,可以將數據加入至物件,或為 Sort-Object 建立自定義表達式。

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

在此範例中,我會取得用戶清單,並使用一些自定義 Cmdlet 來取得其他資訊,只是為了排序。

將哈希表列表排序

如果您有想要排序的哈希表清單,您會發現 Sort-Object 不會將您的索引鍵視為屬性。 我們可以使用自定義排序表示式來繞過這個問題。

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

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

在 Cmdlet 上展開哈希表

關於哈希表,我最喜愛的其中一點是,許多人在早期時未能發現的。 其概念是,您可以先將所有屬性封裝成哈希表,而不是將它們一行一行地提供給 Cmdlet。 然後,您可以用特定的方式將哈希表傳遞給函式。 以下是以正常方式建立 DHCP 範圍的範例。

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"

如果不使用 噴灑,所有這些專案都必須在單一行上定義。 它要麼從螢幕消失,要麼在任意地方換行。 現在,將它與使用展開的命令進行比較。

$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

使用 @ 符號 而不是 $ 符號,會引發 splat 作業。

只要花點時間欣賞這個範例有多容易閱讀。 這些命令與所有參數完全相同。 第二個更容易理解和維護未來的進展。

每當命令變得太長時,我都會使用噴灑。 我將「太長」定義為導致我的視窗向右捲動。 如果我為一個函式達到三個屬性的話,我可能會使用展開哈希表來重寫。

選擇性參數的展開

我使用噴洒最常見的方式之一,就是處理來自腳本中其他位置的選擇性參數。 假設我有一個函式會包裝 Get-CimInstance 具有選擇性 $Credential 自變數的呼叫。

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

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

Get-CimInstance @CIMParams

我一開始會使用一般參數來建立哈希表。 然後,我會新增 $Credential 如果它存在。 因為我在這裡使用噴灑,所以我只需要在程式碼中呼叫 Get-CimInstance 一次。 此設計模式非常簡潔,而且可以輕鬆地處理許多選擇性參數。

公平地說,您可以撰寫命令來允許 $null 用作參數的值。 您不一定能控制您呼叫的其他命令。

多個Splats

您可以將多個哈希表分割至相同的 Cmdlet。 如果我們重新流覽原始的噴灑範例:

$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

當我有一組常見的參數,而我傳遞給許多命令時,我會使用這個方法。

使用 Splatting 技術來優化代碼清晰度

如果讓程式代碼更簡潔,則噴洒單一參數沒有任何問題。

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

灑佈可執行檔

Splatting 也適用於某些使用 /param:value 語法的可執行檔。 Robocopy.exe例如,有一些類似這樣的參數。

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

我不知道這一切都是有用的, 但我發現它很有趣。

新增哈希表

哈希表支援加號運算符來結合兩個哈希表。

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

只有在兩個哈希表未共用密鑰時,才能運作。

巢狀哈希表

我們可以使用哈希表做為哈希表內的值。

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

我從包含兩個索引鍵的基本哈希表開始。 我新增了名為 location 且具有空哈希表的索引鍵。 然後,我已將最後兩個項目新增至該 location 哈希表。 我們可以全部在同一流程中完成。

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

這會建立我們上面看到的相同哈希表,而且可以以相同方式存取屬性。

$person.location.city
Austin

有許多方法可以處理對象的結構。 以下是查看巢狀哈希表的第二種方式。

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

這會混合使用哈希表做為 物件集合和屬性集合的概念。 即使這些值被嵌套在您偏好的方法中,也仍然很容易存取它們。

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

當我將它視為屬性時,我通常會使用 dot 屬性。 這些通常是我在程式碼中以靜態方式定義的東西,我心裡都很清楚。 如果我需要遍歷清單,或以程式方式存取鍵,我會使用括號來提供鍵名。

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

擁有巢狀哈希表的能力可以讓您擁有高度的彈性和多樣的選項。

查看巢狀哈希表

當您開始使用嵌套方式操作雜湊表時,您將需要一個簡單的方法從控制台查看這些雜湊表。 如果我使用最後一個哈希表,我會收到如下所示的輸出,深度有限:

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

我查看這些內容時的首選命令是 ConvertTo-Json,因為它很直觀,而且我經常在其他地方使用 JSON。

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

即使您不知道 JSON,您也應該能夠查看您要尋找的內容。 結構化數據有如下 Format-Custom 的命令,但我仍然喜歡 JSON 檢視。

建立物件

有時候,您就是需要一個物件,而僅僅依靠使用哈希表來保存屬性可能無法達到預期效果。 最常見的是您會希望將鍵值視為欄位名稱。 pscustomobject 讓這變得很容易。

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

$person

name  age
----  ---
Kevin  36

即使您一開始未建立它 pscustomobject ,您稍後仍可視需要加以轉換。

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

[pscustomobject]$person

name  age
----  ---
Kevin  36

我已經為 pscustomobject 詳細寫了,您應該在此之後閱讀。 它依據在此處學到的許多內容發展而來。

讀取和寫入哈希表至檔案

儲存至 CSV

努力取得哈希表以儲存至 CSV 是上述問題之一。 將哈希表轉換成 pscustomobject ,並將正確儲存至 CSV。 如果您從pscustomobject開始,這樣可以保留欄位順序,這會很有幫助。 但如有需要,您可以將它 pscustomobject 轉換成內聯。

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

同樣地,請參閱使用 pscustomobject 撰寫的 。

將巢狀哈希表儲存至檔案

如果您需要將巢狀哈希表儲存至檔案,然後再重新讀取它,我就會使用 JSON Cmdlet 來執行此動作。

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

這個方法有兩個重要要點。 首先,JSON 會寫出多行,因此我需要使用 -Raw 選項將它讀回單一字串。 第二個是匯入的物件不再是 [hashtable]。 現在是[pscustomobject],如果您沒有預期,它可能會導致問題。

注意深層巢狀哈希表。 當您將它轉換成 JSON 時,可能無法取得預期的結果。

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

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

使用 Depth 參數,以確保您已展開所有巢狀哈希表。

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

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

如果您需要在匯入時是 [hashtable] ,則必須使用 Export-CliXmlImport-CliXml 命令。

將 JSON 轉換為哈希表

如果您需要將 JSON 轉換成[hashtable],我知道有一種方式可透過 .NET 中的 JavaScriptSerializer 來執行此動作。

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

從 PowerShell v6 開始,JSON 支援會使用 NewtonSoft JSON.NET,並新增哈希表支援。

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

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

PowerShell 6.2 已將 Depth 參數新增至 ConvertFrom-Json。 預設 深度 為 1024。

直接從檔案讀取

如果您有使用PowerShell語法包含哈希表的檔案,則可以直接匯入它。

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

它會將檔案的內容匯入到scriptblock,然後檢查以確保其中沒有任何其他 PowerShell 命令,才執行它。

說到這裡,您知道模組指令清單(.psd1 檔案)只是雜湊表嗎?

索引鍵可以是任何物件

大部分時候,鍵只是字串。 因此,我們可以把引號放在任何東西周圍,並讓它成為關鍵。

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

你可以做一些您可能沒有意識到你可以做的奇怪的事情。

$person.'full name'

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

只是因為你可以做點什麼,這並不表示你應該這樣做。 最後一個看起來就像一個隨時會發生的錯誤,容易讓閱讀你程式碼的人誤解。

從技術上講,您的密鑰不一定是字串,但如果您只使用字串,則更容易思考。 不過,索引對複雜的鍵值效果不好。

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

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

借助其鍵值存取哈希表中的值並不總是有效。 例如:

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

當索引鍵是陣列時,您必須將變數包裝 $key 在子運算式中,以便與成員存取 (.) 表示法搭配使用。 或者,您可以使用陣列索引 ([]) 表示法。

在自動變數中使用

$PSBoundParameters

$PSBoundParameters 是僅存在於函式內容內的自動變數。 它包含使用呼叫函式的所有參數。 這不是哈希表,但足夠接近,您可以將其視為哈希表。

這包括移除索引鍵,並將它展開至其他函式。 如果您發現自己撰寫 Proxy 函式,請仔細看看這一個函式。

如需詳細資訊 ,請參閱about_Automatic_Variables

PSBoundParameters 意想不到的問題

要記住的一個重要事項是,這隻包含傳入做為參數的值。 如果您也有具有預設值但不會由呼叫端傳入的參數, $PSBoundParameters 則不包含這些值。 這通常被忽略。

$PSDefaultParameterValues

這個自動變數可讓您將預設值指派給任何 Cmdlet,而不需要變更 Cmdlet。 看看這個範例。

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

這會將項目新增至 $PSDefaultParameterValues 哈希表,並將 UTF8 設為 Out-File -Encoding 參數的預設值。 這是特定於會話的,因此您應該將它放在您的$PROFILE中。

我經常使用此項目來預先指派我經常輸入的值。

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

這也接受通配符,因此您可以大量設定值。 以下是一些您可以使用的方式:

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

如需更深入的細目,請參閱邁克爾·索倫斯關於自動預設值的這篇絕佳文章。

正則表達式 $Matches

當您使用 -match 運算符時,會以相符的結果建立名為 $Matches 的自動變數。 如果您的正則表示式中有任何子運算式,也會列出這些子匹配項。

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

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

指名比賽

這是我最愛的功能之一,大多數人不知道。 如果您使用具名的 regex 比對方式,則可以在比對結果中依名稱存取該比對項目。

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

在上述範例中, (?<Name>.*) 是具名子表達式。 這個值接著會放在 屬性中 $Matches.Name

Group-Object -AsHashtable

其中一個鮮為人知的功能 Group-Object 是,它可以將某些數據集轉換成哈希表。

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

這會將每個數據列加入哈希表中,並使用指定的屬性做為索引鍵來存取它。

複製哈希表

要了解的一個重要事情是哈希表是一個物件。 而且每個變數只是對象的參考。 這表示建立哈希表的有效複本需要更多工作。

指派參考類型

當您有一個哈希表並將它指派給第二個變數時,這兩個變數都會指向相同的哈希表。

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]

這強調它們相同,因為改變其中一個中的值也會改變另一個中的值。 這也適用於將哈希表傳遞至其他函式時。 如果這些函式對該哈希表進行變更,您的原始函式也會改變。

淺層複製,單一層級

如果我們有類似上述範例的簡單哈希表,我們可以使用 Clone() 來建立淺層複本。

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]

這將使我們能對其中一個進行一些基本變更,而不會影響另一個。

淺層拷貝,巢狀

之所以稱為淺層複本,是因為它只會複製基底層級屬性。 如果其中一個屬性是參考型別(例如另一個哈希表),這些巢狀物件仍會彼此指向。

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]

因此,即使我複製了哈希表,但對 person 的引用並未被複製。 我們需要進行深層複製,才能真正擁有第二個哈希表,而該哈希表並未連結到第一個哈希表。

深度複製

有幾種方式可以製作哈希表的深層複本(並將它保留為哈希表)。 以下是使用 PowerShell 以遞歸方式建立深層複製的函式:

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

它無法處理任何其他參考型別或陣列,然而它是一個很好的起點。

另一種方式是使用 .NET 和 CliXml 進行反序列化,如下函式所示:

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

對於極大型雜湊表,反序列化函式在處理大量資料時會更快。然而,使用此方法時需要考慮一些事項。 因為它使用 CliXml,所以記憶體密集,而且如果您正在複製龐大的哈希表,這可能是個問題。 CliXml 的另一個限制是深度限制為 48。 也就是說,如果您有具有 48 層巢狀哈希表的哈希表,複製將會失敗,而且完全不會輸出任何哈希表。

別的東西?

我快速地完成了很多工作。 我希望你每次閱讀這篇文章時,都能學到新知識或對它有更深入的理解。 由於我涵蓋了這項功能的完整範圍,其中有些方面目前對您可能不適用。 這完全沒問題,根據您使用PowerShell的量而定,這是預期的結果。