Durchlaufen eines Hashs oder Verwenden eines Arrays in PowerShell

96

Ich verwende diesen (vereinfachten) Codeabschnitt, um eine Reihe von Tabellen mit BCP aus SQL Server zu extrahieren .

$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
}

Es funktioniert gut, aber jetzt müssen einige der Tabellen über Ansichten extrahiert werden, andere nicht. Ich brauche also eine Datenstruktur wie diese:

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

Ich habe mir Hashes angesehen, aber ich finde nichts darüber, wie man einen Hash durchläuft. Was wäre hier das Richtige? Vielleicht nur ein Array verwenden, aber mit beiden durch Leerzeichen getrennten Werten?

Oder fehlt mir etwas Offensichtliches?

Sylvia
quelle

Antworten:

108

Christians Antwort funktioniert gut und zeigt, wie Sie mit der GetEnumeratorMethode jedes Hash-Tabellenelement durchlaufen können . Sie können auch mit der Schleife durchlaufenkeys Eigenschaft . Hier ist ein Beispiel, wie:

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

Ausgabe:

key = c , value = 3
key = a , value = 1
key = b , value = 2
Andy Arismendi
quelle
Wie kann ich den Hash in einer anderen Reihenfolge aufzählen? ZB wenn ich seinen Inhalt in aufsteigender Reihenfolge drucken möchte (hier a ... b ... c). Ist es überhaupt möglich?
LPrc
5
Warum $hash.Item($_)statt $hash[$_]?
Alvarez
1
@LPrc verwendet dazu die Sort-Object-Methode. Dieser Artikel macht eine ziemlich gute Erklärung dafür: technet.microsoft.com/en-us/library/ee692803.aspx
chazbot7
4
Sie könnten sogar nur verwenden $hash.$_.
TNT
1
Dieser Ansatz funktioniert nicht, wenn die Hashtabelle key = 'Keys' enthält.
Boris Nikitin
195

Kurzschrift wird für Skripte nicht bevorzugt. es ist weniger lesbar. Der Operator% {} wird als Kurzform betrachtet. So sollte es in einem Skript für Lesbarkeit und Wiederverwendbarkeit gemacht werden:

Variable Einrichtung

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

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

Option 1: GetEnumerator ()

Hinweis: persönliche Präferenz; Die Syntax ist leichter zu lesen

Die GetEnumerator () -Methode würde wie folgt ausgeführt:

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

Ausgabe:

c: 3
b: 2
a: 1

Option 2: Tasten

Die Keys-Methode würde wie folgt durchgeführt:

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

Ausgabe:

c: 3
b: 2
a: 1

Zusätzliche Information

Seien Sie vorsichtig beim Sortieren Ihrer Hashtabelle ...

Sort-Object kann es in ein Array ändern:

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

Diese und andere PowerShell-Schleifen sind in meinem Blog verfügbar.

VertigoRay
quelle
3
wie dieses mehr für die Lesbarkeit, besonders für nicht engagierte Powershell-Entwickler wie mich.
AnIBMer
1
Ich bin damit einverstanden, dass diese Methode einfacher zu lesen und daher wahrscheinlich besser für Skripte geeignet ist.
leinad13
2
Abgesehen von der Lesbarkeit gibt es einen Unterschied zwischen der Lösung von VertigoRay und der von Andy. % {} ist ein Alias ​​für ForEach-Object, der sich von der foreachAnweisung hier unterscheidet. ForEach-Objectnutzt die Pipeline, die viel schneller sein kann, wenn Sie bereits mit einer Pipeline arbeiten. Die foreachAussage nicht; Es ist eine einfache Schleife.
JamesQMurphy
4
@JamesQMurphy Ich habe die Antwort von @ andy-arismendi von oben kopiert und eingefügt. Es ist die beliebte Pipeline-Lösung für diese Frage. Ihre Aussage, die mein Interesse geweckt hat, war, dass ForEach-Object(aka :) %{}schneller ist als foreach; Ich habe gezeigt, dass das nicht schneller geht . Ich wollte zeigen, ob Ihr Punkt gültig ist oder nicht. weil ich, wie die meisten Leute, möchte, dass mein Code so schnell wie möglich ausgeführt wird. Jetzt wird Ihr Argument dahingehend geändert, dass Jobs parallel ausgeführt werden (möglicherweise mit mehreren Computern / Servern). Dies ist natürlich schneller als das Ausführen eines Jobs in Serie von einem einzelnen Thread aus. Prost!
VertigoRay
1
@JamesQMurphy Ich würde auch denken, dass Powershell dem traditionellen Vorteil folgt, den Shells Ihnen bieten, wenn Sie Datenströme zwischen mehreren Prozessen leiten: Sie erhalten natürliche Parallelität durch die einfache Tatsache, dass jede Pipeline-Stufe ein unabhängiger Prozess ist (natürlich kann ein Puffer entleeren und einlaufen am Ende geht die gesamte Pipeline so langsam wie ihr langsamster Prozess). Dies unterscheidet sich von einem Pipeline-Prozess, der selbst entscheidet, mehrere Threads zu erzeugen, was vollständig von den Implementierungsdetails des Prozesses selbst abhängt.
Johan Boulé
8

Sie können dies auch ohne Variable tun

@{
  'foo' = 222
  'bar' = 333
  'baz' = 444
  'qux' = 555
} | % getEnumerator | % {
  $_.key
  $_.value
}
Steven Penny
quelle
Dadurch erhalte ich ein "ForEach-Objekt: Parameter 'Process' kann nicht gebunden werden. Der Wert" getEnumerator "vom Typ" System.String "kann nicht in" System.Management.Automation.ScriptBlock "konvertiert werden
luis.espinal
6

Informationen zum Durchlaufen eines Hashs:

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

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

Ich bevorzuge diese Variante der Enumerator-Methode mit einer Pipeline, da Sie nicht auf die Hash-Tabelle im foreach verweisen müssen (getestet in PowerShell 5):

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

Ausgabe:

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

Dies wurde nicht absichtlich nach Wert sortiert, der Enumerator gibt die Objekte einfach in umgekehrter Reihenfolge zurück.

Da dies jedoch eine Pipeline ist, kann ich jetzt die vom Enumerator empfangenen Objekte nach Wert sortieren:

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

Ausgabe:

Key = a and Value = 3
Key = b and Value = 2
Key = c and Value = 1
Eelco L.
quelle
Der Enumerator kann die Werte in "beliebiger" Reihenfolge zurückgeben, da er einfach die zugrunde liegende Implementierung iteriert. In diesem Fall erscheint es in umgekehrter Reihenfolge. Für ein Gegenbeispiel: $x = @{a = 1; z = 2}; $x.q = 3wird hier als q, a, z "geordnet".
user2864740
4

Hier ist eine weitere schnelle Möglichkeit: Verwenden Sie einfach den Schlüssel als Index für die Hash-Tabelle, um den Wert abzurufen:

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

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

Wenn Sie PowerShell v3 verwenden, können Sie JSON anstelle einer Hashtabelle verwenden und es mit Convert-FromJson in ein Objekt konvertieren :

@'
[
    {
        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
quelle
nb der Befehl lautet ConvertFrom-Json (und umgekehrt ConvertTo-Json) - tauschen Sie einfach die Strichposition aus.
Atmarx