Цикл через хеш или использование массива в PowerShell


98

Я использую этот (упрощенный) фрагмент кода для извлечения набора таблиц из 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"

Я смотрел хеши, но не нашел ничего о том, как их перебирать. Что было бы правильным здесь сделать? Возможно, просто использовать массив, но с обоими значениями, разделенными пробелом?

Или я упускаю что-то очевидное?

Ответы:


109

Ответ Кристиана работает хорошо и показывает, как с помощью этого 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

Как я могу пронумеровать хеш в другом порядке? Например, когда я хочу распечатать его содержимое в порядке возрастания (здесь a ... b ... c). Это вообще возможно?
LPrc

5
Почему $hash.Item($_)вместо $hash[$_]?
alvarez

1
@LPrc использует для этого метод Sort-Object. Эта статья дает довольно хорошее объяснение этому: technet.microsoft.com/en-us/library/ee692803.aspx
chazbot7

4
Вы даже можете использовать просто $hash.$_.
TNT

1
Этот подход не работает, если хеш-таблица содержит key = 'Keys'.
Борис Никитин

200

Сокращение не рекомендуется для скриптов; он менее читабелен. Оператор% {} считается сокращением. Вот как это должно быть сделано в скрипте для удобства чтения и повторного использования:

Настройка переменных

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
как этот еще один для удобства чтения, особенно для не преданных разработчиков PowerShell, таких как я.
anIBMer

1
Я согласен, что этот метод легче читать и поэтому, вероятно, лучше для написания сценариев.
leinad13

2
Помимо удобочитаемости, есть одно различие между решением VertigoRay и решением Энди. % {} - это псевдоним ForEach-Object, который отличается от приведенного foreachздесь оператора. ForEach-Objectиспользует конвейер, что может быть намного быстрее, если вы уже работаете с конвейером. В foreachзаявлении нет; это простой цикл.
JamesQMurphy

4
@JamesQMurphy Я скопировал и вставил ответ, предоставленный @andy-arismendi сверху; это популярное конвейерное решение этого вопроса. Ваше утверждение, которое вызвало у меня наибольший интерес, заключалось в том, что ForEach-Object(он же:) %{}быстрее, чем foreach; Я показал, что не быстрее . Я хотел продемонстрировать, верна ваша точка зрения или нет; потому что мне, как и большинству людей, нравится, чтобы мой код выполнялся как можно быстрее. Теперь ваш аргумент меняется на параллельное выполнение заданий (потенциально с использованием нескольких компьютеров / серверов) ... что, конечно, быстрее, чем последовательное выполнение задания из одного потока. Ура!
VertigoRay

1
@JamesQMurphy Я бы также подумал, что powershell следует традиционному преимуществу, которое оболочки дают вам, когда вы перенаправляете потоки данных между несколькими процессами: вы получаете естественный параллелизм из того простого факта, что каждый этап конвейера является независимым процессом (конечно, буфер может истощаться и в в конце весь конвейер идет так же медленно, как и его самый медленный процесс). Это отличается от конвейерного процесса, который сам по себе решает создать несколько потоков, что полностью зависит от деталей реализации самого процесса.
Johan Boulé

8

Вы также можете сделать это без переменной

@{
  'foo' = 222
  'bar' = 333
  'baz' = 444
  'qux' = 555
} | % getEnumerator | % {
  $_.key
  $_.value
}

Это дает мне «ForEach-Object: Невозможно привязать параметр« Процесс ». Невозможно преобразовать значение« getEnumerator »типа« System.String »в тип« System.Management.Automation.ScriptBlock »
luis.espinal,

6

О цикле по хешу:

$Q = @{"ONE"="1";"TWO"="2";"THREE"="3"}
$Q.GETENUMERATOR() | % { $_.VALUE }
1
3
2

$Q.GETENUMERATOR() | % { $_.key }
ONE
THREE
TWO

5

Вот еще один быстрый способ, просто используя ключ в качестве индекса в хеш-таблице, чтобы получить значение:

$hash = @{
    'a' = 1;
    'b' = 2;
    'c' = 3
};

foreach($key in $hash.keys) {
    Write-Host ("Key = " + $key + " and Value = " + $hash[$key]);
}

5

Я предпочитаю этот вариант метода перечислителя с конвейером, потому что вам не нужно обращаться к хеш-таблице в 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

Перечислитель может возвращать значения в «любом» порядке, поскольку он просто выполняет итерацию базовой реализации. В данном случае это случается появляться Ве обратным порядком. В качестве контрпримера: $x = @{a = 1; z = 2}; $x.q = 3здесь "упорядочено" как q, a, z.
user2864740


0

Если вы используете 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
    }

nb команда - ConvertFrom-Json (и наоборот, ConvertTo-Json) - просто поменяйте местами размещение тире.
atmarx
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.