Zapętlanie się przez skrót lub używanie tablicy w programie PowerShell

98

Używam tego (uproszczonego) fragmentu kodu do wyodrębnienia zestawu tabel z SQL Server z 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
}

Działa świetnie, ale teraz niektóre tabele muszą zostać wyodrębnione za pomocą widoków, a niektóre nie. Więc potrzebuję struktury danych podobnej do tej:

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

Patrzyłem na skróty, ale nie znajduję nic o tym, jak zapętlić hash. Co należałoby tutaj zrobić? Może po prostu użyj tablicy, ale z obiema wartościami oddzielonymi spacją?

A może brakuje mi czegoś oczywistego?

Sylvia
źródło

Odpowiedzi:

109

Odpowiedź Christiana działa dobrze i pokazuje, w jaki sposób można przejść przez każdy element tablicy z haszowaniem za pomocą tej GetEnumeratormetody. Możesz również zapętlić, używając keyswłaściwości. Oto przykład, jak:

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

Wynik:

key = c , value = 3
key = a , value = 1
key = b , value = 2
Andy Arismendi
źródło
Jak mogę wyliczyć hash w innej kolejności? Np. Gdy chcę wydrukować jego zawartość w porządku rosnącym (tutaj a ... b ... c). Czy to w ogóle możliwe?
LPrc
5
Dlaczego $hash.Item($_)zamiast $hash[$_]?
alvarez
1
@LPrc użyj do tego metody Sort-Object. Ten artykuł dobrze to wyjaśnia: technet.microsoft.com/en-us/library/ee692803.aspx
chazbot7
4
Możesz nawet użyć just $hash.$_.
TNT
1
To podejście nie działa, jeśli tablica hashy zawiera key = 'Keys'.
Boris Nikitin
200

Stenografia nie jest preferowana w przypadku skryptów; jest mniej czytelny. Operator% {} jest uważany za skrótowy. Oto, jak należy to zrobić w skrypcie, aby zapewnić czytelność i możliwość ponownego użycia:

Zmienna konfiguracja

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

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

Opcja 1: GetEnumerator ()

Uwaga: osobiste preferencje; składnia jest łatwiejsza do odczytania

Metoda GetEnumerator () zostanie wykonana w następujący sposób:

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

Wynik:

c: 3
b: 2
a: 1

Opcja 2: klucze

Metoda Keys zostanie wykonana w następujący sposób:

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

Wynik:

c: 3
b: 2
a: 1

Dodatkowe informacje

Uważaj, sortując swój hashtable ...

Sort-Object może zmienić to na tablicę:

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

To i inne pętle PowerShell są dostępne na moim blogu.

VertigoRay
źródło
3
polub ten jeszcze dla czytelności, szczególnie dla nieprofesjonalnych programistów PowerShell, takich jak ja.
anIBMer
1
Zgadzam się, że ta metoda jest łatwiejsza do odczytania i prawdopodobnie lepsza do pisania skryptów.
leinad13
2
Pomijając czytelność, jest jedna różnica między rozwiązaniem VertigoRay a rozwiązaniem Andy'ego. % {} to alias dla ForEach-Object, który różni się od foreachinstrukcji tutaj. ForEach-Objectwykorzystuje potok, który może być znacznie szybszy, jeśli już pracujesz z potokiem. foreachOświadczenie nie robi; to prosta pętla.
JamesQMurphy,
4
@JamesQMurphy Skopiowałem i wkleiłem odpowiedź podaną przez @ andy-arismendi z góry; jest to popularne rozwiązanie dla tego pytania. Twoje oświadczenie, które wzbudziło moje zainteresowanie, brzmiało ForEach-Object(aka:) %{}jest szybsze niż foreach; Pokazałem, że nie jest szybciej . Chciałem pokazać, czy twój punkt jest ważny, czy nie; ponieważ, jak większość ludzi, lubię, aby mój kod działał jak najszybciej. Teraz twoja argumentacja zmienia się na uruchamianie zadań równolegle (potencjalnie przy użyciu wielu komputerów / serwerów) ... co oczywiście jest szybsze niż uruchamianie zadań szeregowo z jednego wątku. Twoje zdrowie!
VertigoRay
1
@JamesQMurphy Myślę również, że PowerShell podąża za tradycyjną zaletą, jaką dają powłoki, gdy potokujesz strumienie danych między kilkoma procesami: otrzymujesz naturalny paralelizm z prostego faktu, że każdy etap potoku jest procesem niezależnym (oczywiście bufor może opróżnić się i koniec cały rurociąg przebiega równie wolno, jak jego najwolniejszy proces). Różni się to od procesu potokowego, który sam decyduje o utworzeniu wielu wątków, co jest całkowicie zależne od szczegółów implementacji samego procesu.
Johan Boulé
8

Możesz to również zrobić bez zmiennej

@{
  'foo' = 222
  'bar' = 333
  'baz' = 444
  'qux' = 555
} | % getEnumerator | % {
  $_.key
  $_.value
}
Steven Penny
źródło
W ten sposób otrzymuję informację „ForEach-Object: Cannot bind parameter„ Process ”. Nie można przekonwertować wartości„ getEnumerator ”typu„ System.String ”na typ„ System.Management.Automation.ScriptBlock ”
luis.espinal
6

O pętli przez hash:

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

$Q.GETENUMERATOR() | % { $_.key }
ONE
THREE
TWO
CB.
źródło
5

Oto inny szybki sposób, po prostu używając klucza jako indeksu do tabeli skrótów, aby uzyskać wartość:

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

foreach($key in $hash.keys) {
    Write-Host ("Key = " + $key + " and Value = " + $hash[$key]);
}
user1161625
źródło
5

Preferuję ten wariant metody wyliczającej z potokiem, ponieważ nie musisz odwoływać się do tabeli skrótów w foreach (testowane w PowerShell 5):

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

Wynik:

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

Teraz nie zostało to celowo posortowane według wartości, moduł wyliczający po prostu zwraca obiekty w odwrotnej kolejności.

Ale ponieważ jest to potok, mogę teraz sortować obiekty otrzymane z modułu wyliczającego według wartości:

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

Wynik:

Key = a and Value = 3
Key = b and Value = 2
Key = c and Value = 1
Eelco L.
źródło
Moduł wyliczający może zwracać wartości w „dowolnej” kolejności, ponieważ po prostu iteruje podstawową implementację. W tym przypadku zdarza się pojawiać be odwrotnej kolejności. Dla kontrprzykładu: $x = @{a = 1; z = 2}; $x.q = 3jest „uporządkowany” jako q, a, z tutaj.
user2864740
0

Jeśli używasz PowerShell v3, możesz użyć JSON zamiast tablicy haszującej i przekonwertować ją na obiekt z tablicy 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
źródło
nb polecenie to ConvertFrom-Json (i odwrotnie, ConvertTo-Json) - po prostu zamień położenie myślnika.
atmarx