Цикл по хешу или использование массива в PowerShell
Я использую этот (упрощенный) кусок кода для извлечения набора таблиц из SQL Server с помощью BCP.
$OutputDirectory = "c:\junk\"
$ServerOption = "-SServerName"
$TargetDatabase = "Content.dbo."
$ExtractTables = @(
"Page"
, "ChecklistItemCategory"
, "ChecklistItem"
)
for ($i=0; $i -le $ExtractTables.Length – 1; $i++) {
$InputFullTableName = "$TargetDatabase$($ExtractTables[$i])"
$OutputFullFileName = "$OutputDirectory$($ExtractTables[$i])"
bcp $InputFullTableName out $OutputFullFileName -T -c $ServerOption
}
Это прекрасно работает, но теперь некоторые таблицы нужно извлекать с помощью представлений, а некоторые нет. Поэтому мне нужна структура данных примерно так:
"Page" "vExtractPage"
, "ChecklistItemCategory" "ChecklistItemCategory"
, "ChecklistItem" "vExtractChecklistItem"
Я смотрел на хэши, но я не нашел ничего о том, как перебрать хэш. Что было бы правильно сделать здесь? Возможно, просто использовать массив, но с двумя значениями, разделенными пробелом?
Или я что-то упускаю очевидное?
Ответы
Ответ 1
Христианский ответ работает хорошо и показывает, как вы можете прокручивать каждый элемент хэш-таблицы с помощью метода GetEnumerator
. Вы также можете выполнить цикл, используя свойство keys
. Вот пример, как:
$hash = @{
a = 1
b = 2
c = 3
}
$hash.Keys | % { "key = $_ , value = " + $hash.Item($_) }
Вывод:
key = c , value = 3
key = a , value = 1
key = b , value = 2
Ответ 2
Сокращение не является предпочтительным для сценариев; это менее читабельно. Оператор% {} считается сокращением. Вот как это должно быть сделано в скрипте для удобства чтения и повторного использования:
Настройка переменных
PS> $hash = @{
a = 1
b = 2
c = 3
}
PS> $hash
Name Value
---- -----
c 3
b 2
a 1
Вариант 1: GetEnumerator()
Примечание: личные предпочтения; синтаксис легче читать
Метод GetEnumerator() будет выполнен, как показано ниже:
foreach ($h in $hash.GetEnumerator()) {
Write-Host "$($h.Name): $($h.Value)"
}
Выход:
c: 3
b: 2
a: 1
Вариант 2: ключи
Метод Keys будет выполнен так, как показано:
foreach ($h in $hash.Keys) {
Write-Host "${h}: $($hash.Item($h))"
}
Выход:
c: 3
b: 2
a: 1
Дополнительная информация
Будьте осторожны при сортировке хеш-таблицы...
Sort-Object может изменить его на массив:
PS> $hash.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Hashtable System.Object
PS> $hash = $hash.GetEnumerator() | Sort-Object Name
PS> $hash.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Этот и другие циклы PowerShell доступны в моем блоге.
Ответ 3
О циклическом прохождении хеша:
$Q = @{"ONE"="1";"TWO"="2";"THREE"="3"}
$Q.GETENUMERATOR() | % { $_.VALUE }
1
3
2
$Q.GETENUMERATOR() | % { $_.key }
ONE
THREE
TWO
Ответ 4
Вот еще один быстрый способ, просто используя ключ в качестве индекса в хеш-таблице, чтобы получить значение:
$hash = @{
'a' = 1;
'b' = 2;
'c' = 3
};
foreach($key in $hash.keys) {
Write-Host ("Key = " + $key + " and Value = " + $hash[$key]);
}
Ответ 5
Вы также можете сделать это без переменной
@{
'foo' = 222
'bar' = 333
'baz' = 444
'qux' = 555
} | % getEnumerator | % {
$_.key
$_.value
}
Ответ 6
Я предпочитаю этот вариант для метода перечислителя с конвейером, потому что вам не нужно ссылаться на хеш-таблицу в foreach (протестировано в PowerShell 5):
$hash = @{
'a' = 3
'b' = 2
'c' = 1
}
$hash.getEnumerator() | foreach {
Write-Host ("Key = " + $_.key + " and Value = " + $_.value);
}
Выход:
Key = c and Value = 1
Key = b and Value = 2
Key = a and Value = 3
Теперь, это не было преднамеренно отсортировано по значению, перечислитель просто возвращает объекты в обратном порядке.
Но так как это конвейер, я теперь могу отсортировать объекты, полученные от перечислителя, по значению:
$hash.getEnumerator() | sort-object -Property value -Desc | foreach {
Write-Host ("Key = " + $_.key + " and Value = " + $_.value);
}
Выход:
Key = a and Value = 3
Key = b and Value = 2
Key = c and Value = 1
Ответ 7
Если вы используете PowerShell v3, вы можете использовать JSON вместо хеш-таблицы и преобразовать его в объект с Convert-FromJson:
@'
[
{
FileName = "Page";
ObjectName = "vExtractPage";
},
{
ObjectName = "ChecklistItemCategory";
},
{
ObjectName = "ChecklistItem";
},
]
'@ |
Convert-FromJson |
ForEach-Object {
$InputFullTableName = '{0}{1}' -f $TargetDatabase,$_.ObjectName
# In strict mode, you can't reference a property that doesn't exist,
#so check if it has an explicit filename firest.
$outputFileName = $_.ObjectName
if( $_ | Get-Member FileName )
{
$outputFileName = $_.FileName
}
$OutputFullFileName = Join-Path $OutputDirectory $outputFileName
bcp $InputFullTableName out $OutputFullFileName -T -c $ServerOption
}