Поделиться через


Все, что вы хотели знать о хеш-таблицах

Я хочу сделать шаг назад и говорить о хэштебли. Я использую их все время сейчас. Я рассказывал кому-то о них после нашей встречи с группой пользователей вчера вечером, и я понял, что у меня была та же путаница о них, как и у него. Хеш-таблицы очень важны в PowerShell, поэтому хорошо иметь чёткое понимание их работы.

Примечание.

Оригинальная версия этой статьи появилась в блоге, написанном @KevinMarquette. Команда PowerShell благодарит Кевина за предоставление этого содержимого нам. Пожалуйста, ознакомьтесь с его блогом на 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
}

Перечислитель выдает каждую пару "ключ-значение" по очереди. Он был разработан специально для этого варианта использования. Спасибо Марк Краус за напоминание мне об этом.

Некорректное Перечисление

Одна из важных деталей заключается в том, что вы не можете изменить хэш-таблицу в процессе её перечисления. Если мы начнём с базового примера $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 ){...}

Это просто, но был источником многих ошибок для меня, потому что я упускал из виду одну важную детали в моей логике. Я начал использовать его, чтобы проверить, присутствует ли ключ. При значении $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-Object и Format-Table. Хеш-таблицы имеют специальный синтаксис, который выглядит следующим образом при полном развертывании.

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

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

В этом примере я беру список пользователей и использую настраиваемый командлет, чтобы получить дополнительную информацию исключительно для сортировки.

Сортировка списка хэш-таблиц

Если у вас есть список хэш-таблиц, которые вы хотите сортировать, обратите внимание, что Sort-Object не рассматривает ключи как свойства. Мы можем обойти это, используя пользовательское выражение сортировки.

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

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

Распаковка хеш-таблиц в командлетах

Одно из моих любимых достоинств хеш-таблиц — это то, что многие люди не узнают о них сразу. Идея заключается в том, что вместо того, чтобы указывать все свойства командлету в одной строке, можно сначала упаковать их в хэш-таблицу. Затем вы можете передать хеш-таблицу функции особым способом. Ниже приведен пример создания области 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"

Без использования расстановки , все эти вещи необходимо определить в одной строке. Он либо прокручивается за экран, либо переносится туда, где захочет. Теперь сравните это с командой, которая использует технику "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

Использование знака @ вместо $ вызывает операцию splat.

Просто уделите минуту, чтобы оценить, насколько легко этот пример читается. Они представляют собой совершенно одинаковые команды с одинаковыми значениями. Второй проще понять и поддерживать в дальнейшем.

Я использую развёртывание аргументов, когда команда становится слишком длинной. Я считаю "слишком долго" это когда окно прокручивается вправо. Если я использую три свойства для функции, вероятно, что я перезапишу её с помощью разложенной хеш-таблицы.

Сплаттинг для необязательных параметров

Один из наиболее распространенных способов, которым я использую splatting, заключается в том, чтобы иметь дело с необязательными параметрами, поступающими из другого места в моем сценарии. Предположим, у меня есть функция, которая обёртывает вызов Get-CimInstance с необязательным аргументом $Credential.

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

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

Get-CimInstance @CIMParams

Сначала создаю хэш-файл с общими параметрами. Затем я добавлю $Credential, если он существует. Так как я использую сплаттинг здесь, мне нужно только вызвать Get-CimInstance в моем коде один раз. Этот шаблон конструктора очень чист и может легко обрабатывать множество необязательных параметров.

По справедливости, вы могли бы написать команды, которые разрешают значения $null для параметров. Вы просто не всегда контролируете другие команды, которые вы вызываете.

Несколько брызг

Можно добавить несколько хэш-элементов в один и тот же командлет. Если мы вернемся к нашему исходному примеру с платтингом:

$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 для этого.

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

Убедитесь, что вы использовали параметр глубины для расширения всех вложенных хэш-таблиц.

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

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

Если нужно, чтобы при импорте было [hashtable], вам нужно использовать команды Export-CliXml и Import-CliXml.

Преобразование JSON в Hashtable

Если вам нужно преобразовать JSON в [hashtable], есть один способ, который я знаю, чтобы сделать это с JavaScriptSerializer в .NET.

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

Начиная с PowerShell версии 6 поддержка JSON использует JSON.NET NewtonSoft и добавляет хэш-поддержку.

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

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

PowerShell 6.2 добавил параметр глубины для 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 — это автоматическая переменная, которая существует только в контексте функции. Он содержит все параметры, с которыми была вызвана функция. Это не совсем хэш-код, но достаточно близко, что вы можете относиться к нему как один.

Это включает удаление ключей и их передачу в другие функции. Если вы обнаруживаете, что пишете прокси-функции, обратите внимание на эту.

Дополнительные сведения см. в about_Automatic_Variables.

PSBoundParameters ловушка

Важно помнить, что это включает только значения, передаваемые в качестве параметров. Если у вас есть параметры со значениями по умолчанию, но они не передаются вызывающим кодом, $PSBoundParameters не содержит эти значения. Обычно это не учитывается.

$PSDefaultParameterValues

Эта переменная автоматизации позволяет назначать значения по умолчанию этому командлету без изменения командлета. Ознакомьтесь с этим примером.

$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 с результатами сопоставления. Если у вас есть какие-либо подвыражения в регулярном выражении, эти подсовпадения также перечислены.

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

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

Именованные совпадения

Это одна из моих любимых функций, о которые большинство людей не знают. Если вы используете именованное совпадение регулярных выражений, вы можете получить доступ к этим совпадениям по имени в совпадениях.

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