您想要知道哈希表的一切

我想退一步,談論 哈希表。 我現在一直使用它們。 我昨晚在使用者群組會議后教別人,我意識到我和他們一樣困惑。 哈希表在 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 中使用哈希表的另一個常見方式是保存屬性集合,其中索引鍵是屬性的名稱。 在下一個範例中,我將逐步了解這個想法。

屬性型存取

屬性型存取的使用會變更哈希表的動態,以及如何在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; }

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

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

有幾個 Cmdlet 支援使用哈希表來建立自定義或導出屬性。 您通常會使用 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

我將它放在變數中,但很容易被內嵌定義,而且您可以在處理時縮短 namenexpressione

$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

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

清除清除程序代碼

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

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

要記住的一個重要事項是,這隻包含傳入做為參數的值。 如果您也有具有預設值但不會由呼叫端傳入的參數, $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

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

Regex $Matches

當您使用 -match 運算符時,會以相符的結果建立名為 $matches 的自動變數。 如果您的 regex 中有任何子運算式,也會列出這些子相符專案。

$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的量而定,這是預期的結果。