Perulangan melalui hash, atau menggunakan array di PowerShell

96

Saya menggunakan potongan kode (yang disederhanakan) ini untuk mengekstrak satu set tabel dari SQL Server dengan 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
}

Ini berfungsi dengan baik, tetapi sekarang beberapa tabel perlu diekstraksi melalui tampilan, dan beberapa tidak. Jadi saya membutuhkan struktur data seperti ini:

"Page"                      "vExtractPage"
, "ChecklistItemCategory"   "ChecklistItemCategory"
, "ChecklistItem"           "vExtractChecklistItem"

Saya melihat hash, tetapi saya tidak menemukan apa pun tentang cara mengulang melalui hash. Apa hal benar yang harus dilakukan di sini? Mungkin hanya menggunakan larik, tetapi dengan kedua nilai, dipisahkan oleh spasi?

Atau apakah saya melewatkan sesuatu yang jelas?

Sylvia
sumber

Jawaban:

108

Jawaban Christian bekerja dengan baik dan menunjukkan bagaimana Anda dapat mengulang setiap item tabel hash menggunakan GetEnumeratormetode tersebut. Anda juga dapat melakukan perulangan menggunakan keysproperti. Berikut ini contohnya caranya:

$hash = @{
    a = 1
    b = 2
    c = 3
}
$hash.Keys | % { "key = $_ , value = " + $hash.Item($_) }

Keluaran:

key = c , value = 3
key = a , value = 1
key = b , value = 2
Andy Arismendi
sumber
Bagaimana cara menghitung hash dalam urutan lain? Misal ketika saya ingin mencetak isinya dalam urutan menaik (disini a ... b ... c). Apakah itu mungkin?
LPrc
5
Mengapa, $hash.Item($_)bukan $hash[$_]?
alvarez
1
@LPrc menggunakan metode Sort-Object untuk melakukan itu. Artikel ini tidak penjelasan yang baik cantik itu: technet.microsoft.com/en-us/library/ee692803.aspx
chazbot7
4
Anda bahkan bisa menggunakan saja $hash.$_.
TNT
1
Pendekatan ini tidak berfungsi jika hashtable berisi key = 'Keys'.
Boris Nikitin
197

Singkatan tidak disukai untuk skrip; itu kurang terbaca. Operator% {} dianggap singkatan. Inilah cara melakukannya dalam skrip agar terbaca dan dapat digunakan kembali:

Variabel Setup

PS> $hash = @{
    a = 1
    b = 2
    c = 3
}
PS> $hash

Name                           Value
----                           -----
c                              3
b                              2
a                              1

Opsi 1: GetEnumerator ()

Catatan: preferensi pribadi; sintaks lebih mudah dibaca

Metode GetEnumerator () akan dilakukan seperti yang ditunjukkan:

foreach ($h in $hash.GetEnumerator()) {
    Write-Host "$($h.Name): $($h.Value)"
}

Keluaran:

c: 3
b: 2
a: 1

Opsi 2: Kunci

Metode kunci akan dilakukan seperti yang ditunjukkan:

foreach ($h in $hash.Keys) {
    Write-Host "${h}: $($hash.Item($h))"
}

Keluaran:

c: 3
b: 2
a: 1

Informasi tambahan

Hati-hati saat menyortir hashtable Anda ...

Sort-Object dapat mengubahnya menjadi array:

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

Ini dan perulangan PowerShell lainnya tersedia di blog saya.

VertigoRay
sumber
3
suka yang satu ini lebih untuk keterbacaan, terutama untuk tidak mendedikasikan pengembang PowerShell seperti saya.
anIBMer
1
Saya setuju metode ini lebih mudah dibaca dan karenanya mungkin lebih baik untuk pembuatan skrip.
leinad13
2
Selain keterbacaan, ada satu perbedaan antara solusi VertigoRay dan solusi Andy. % {} adalah alias untuk ForEach-Object, yang berbeda dengan foreachpernyataan di sini. ForEach-Objectmemanfaatkan pipa, yang bisa jauh lebih cepat jika Anda sudah bekerja dengan pipa. The foreachpernyataan tidak; itu lingkaran sederhana.
JamesQMurphy
4
@JamesQMurphy Saya salin dan tempel jawaban yang diberikan oleh @ andy-arismendi dari atas; Ini adalah solusi saluran pipa yang populer untuk pertanyaan ini. Pernyataan Anda, yang menarik minat saya, adalah bahwa ForEach-Object(alias:) %{}lebih cepat dari foreach; Saya menunjukkan bahwa tidak lebih cepat . Saya ingin menunjukkan apakah maksud Anda valid atau tidak; karena saya, seperti kebanyakan orang, suka kode saya berjalan secepat mungkin. Sekarang, argumen Anda berubah menjadi menjalankan pekerjaan secara paralel (berpotensi menggunakan banyak komputer / server) ... yang tentu saja lebih cepat daripada menjalankan pekerjaan secara seri dari satu thread. Bersulang!
VertigoRay
1
@JamesQMurphy Saya juga akan berpikir PowerShell mengikuti keuntungan tradisional yang diberikan shell ketika Anda menyalurkan aliran data antara beberapa proses: Anda mendapatkan paralelisme alami dari fakta sederhana bahwa setiap tahap pipeline adalah proses independen (tentu saja, buffer dapat terkuras dan masuk akhirnya seluruh pipeline berjalan selambat prosesnya yang paling lambat). Itu berbeda dengan memiliki proses pipeline yang memutuskan sendiri untuk menelurkan banyak utas, yang sepenuhnya bergantung pada detail implementasi dari proses itu sendiri.
Johan Boulé
8

Anda juga dapat melakukan ini tanpa variabel

@{
  'foo' = 222
  'bar' = 333
  'baz' = 444
  'qux' = 555
} | % getEnumerator | % {
  $_.key
  $_.value
}
Steven Penny
sumber
Ini memberi saya "ForEach-Object: Tidak dapat mengikat parameter 'Proses'. Tidak dapat mengubah nilai" getEnumerator "dari jenis" System.String "menjadi" System.Management.Automation.ScriptBlock "
luis.espinal
6

Tentang perulangan melalui hash:

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

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

Berikut cara cepat lainnya, cukup gunakan kunci sebagai indeks ke dalam tabel hash untuk mendapatkan nilainya:

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

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

Saya lebih suka varian ini pada metode enumerator dengan pipa, karena Anda tidak perlu merujuk ke tabel hash di bagian depan (diuji di PowerShell 5):

$hash = @{
    'a' = 3
    'b' = 2
    'c' = 1
}
$hash.getEnumerator() | foreach {
    Write-Host ("Key = " + $_.key + " and Value = " + $_.value);
}

Keluaran:

Key = c and Value = 1
Key = b and Value = 2
Key = a and Value = 3

Sekarang, ini belum secara sengaja diurutkan berdasarkan nilai, enumerator hanya mengembalikan objek dalam urutan terbalik.

Tetapi karena ini adalah pipeline, saya sekarang dapat mengurutkan objek yang diterima dari enumerator berdasarkan nilai:

$hash.getEnumerator() | sort-object -Property value -Desc | foreach {
  Write-Host ("Key = " + $_.key + " and Value = " + $_.value);
}

Keluaran:

Key = a and Value = 3
Key = b and Value = 2
Key = c and Value = 1
Eelco L.
sumber
Pencacah dapat mengembalikan nilai dalam urutan "apa saja" , karena enumerator hanya mengulang implementasi yang mendasarinya. Dalam hal ini kebetulan muncul urutan terbalik. Untuk counter-example: $x = @{a = 1; z = 2}; $x.q = 3"diurutkan" sebagai q, a, z di sini.
pengguna2864740
0

Jika Anda menggunakan PowerShell v3, Anda dapat menggunakan JSON sebagai ganti hashtable, dan mengonversinya menjadi objek dengan 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
    }
Aaron Jensen
sumber
nb perintahnya adalah ConvertFrom-Json (dan sebaliknya, ConvertTo-Json) - tukar saja penempatan tanda hubung.
atmarx