Все, что вы хотели знать о хеш-таблицах
Я хочу сделать шаг назад и поговорить о хеш-таблицах. Теперь я использую их постоянно. Я рассказывал о них кому-то вчера вечером после встречи нашей группы пользователей и понял, что у меня такое же замешательство по поводу них, как и у него. Хэш-таблицы очень важны в PowerShell, поэтому полезно иметь четкое представление о них.
Примечание
Оригинальная версия этой статьи появилась в блоге @KevinMarquette. Команда PowerShell благодарит Кевина за то, что он поделился с нами этим контентом. Пожалуйста, посетите его блог на PowerShellExplained.com.
Хэш-таблица как коллекция вещей
Я хочу, чтобы вы сначала увидели Хеш-таблицу как коллекцию в традиционном определении хеш-таблицы. Это определение дает вам фундаментальное понимание того, как они работают, когда позже привыкнут к более сложным вещам. Игнорирование этого понимания часто является источником путаницы.
Что такое массив?
Прежде чем я перейду к тому, что такое Hashtable, мне нужно сначала упомянуть массивы. Для целей данного обсуждения массив — это список или коллекция значений или объектов.
$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 ){...}
Это просто, но стало для меня источником многих ошибок, потому что я упустил одну важную деталь в своей логике. Я начал использовать его, чтобы проверить наличие ключа. Если значение было $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 }
}
имя
— это то, как командлет пометит этот столбец. выражение
— это блок сценария, который выполняется, где $_
— это значение объекта в канале. Вот этот скрипт в действии:
$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"
Без использования сплаттинга все эти вещи необходимо определить в одной строке. Он либо прокручивается за пределы экрана, либо переносится туда, где ему хочется. Теперь сравните это с командой, использующей сплаттинг.
$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
для параметров. Вы просто не всегда можете контролировать другие команды, которые вызываете.
Несколько знаков
Вы можете объединить несколько хеш-таблиц в один и тот же командлет. Если мы вернемся к нашему исходному примеру со знаками:
$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
Я склонен использовать свойство точки, когда рассматриваю его как свойство. Обычно это вещи, которые я определил статически в своем коде и знаю их наизусть. Если мне нужно пройти по списку или программно получить доступ к ключам, я использую скобки, чтобы указать имя ключа.
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
, чтобы прочитать его обратно в одну строку. Во-вторых, импортированный объект больше не является [хэш-таблицей]
. Теперь это [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-CliXml
и Import-CliXml
.
Преобразование JSON в хэш-таблицу
Если вам нужно преобразовать JSON в [hashtable]
, я знаю один способ сделать это с помощью JavaScriptSerializer в .NET.
[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 — это автоматическая переменная, которая существует только внутри контекста функции. Он содержит все параметры, с которыми была вызвана функция. Это не совсем хеш-таблица, но достаточно близкая к ней, чтобы вы могли относиться к ней как к таковой.
Это включает в себя удаление клавиш и распределение их по другим функциям. Если вы пишете прокси-функции, обратите на это внимание.
Дополнительные сведения см. в разделе 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
Более подробную информацию можно найти в замечательной статье Майкла Соренса «Автоматические настройки по умолчанию».
Регулярное выражение $Соответствует
При использовании оператора -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
}
В приведенном выше примере (?
является именованным подвыражением. Затем это значение помещается в свойство $Matches.Name
.
Групповой объект-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.