Цикл хешу або використання масиву в PowerShell


96

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

Я розглядав хеші, але не знаходжу нічого про те, як прокрутити хеш. Що тут правильно робити? Можливо, просто використовувати масив, але з обома значеннями, розділеними пробілом?

Або мені не вистачає чогось очевидного?

Відповіді:


108

Відповідь Крістіана працює добре і показує, як можна прокрутити кожен елемент хеш-таблиці, використовуючи 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[$_]?
Альварес

1
Для цього @LPrc використовує метод Sort-Object. Ця стаття робить дуже гарне пояснення цього: technet.microsoft.com/en-us/library/ee692803.aspx
chazbot7

4
Можна навіть просто $hash.$_.
TNT

1
Цей підхід не працює, якщо хеш-таблиця містить key = 'Keys'.
Борис Нікітін

197

Стенографія не є кращою для сценаріїв; він менш читабельний. Оператор% {} вважається скороченим. Ось як це слід робити в сценарії для читабельності та повторного використання:

Налаштування змінної

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: Ключі

Метод ключів буде виконаний, як показано:

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 дотримується традиційної переваги, яку дають вам оболонки, коли ви передаєте потоки даних між кількома процесами: ви отримуєте природний паралелізм від того простого факту, що кожна стадія конвеєра є незалежним процесом (звичайно, буфер може зливатися і кінець весь трубопровід йде так само повільно, як і найповільніший процес). Це відрізняється від процесу конвеєра, який самостійно приймає рішення про створення декількох потоків, що цілком залежить від деталей реалізації самого процесу.
Йохан Буле,

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.