Ciclando attraverso un hash o usando un array in PowerShell


96

Sto usando questo pezzo di codice (semplificato) per estrarre un set di tabelle da SQL Server con 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
}

Funziona benissimo, ma ora alcune tabelle devono essere estratte tramite le visualizzazioni e altre no. Quindi ho bisogno di una struttura dati simile a questa:

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

Stavo guardando gli hash, ma non ho trovato nulla su come eseguire il loop attraverso un hash. Quale sarebbe la cosa giusta da fare qui? Forse usi solo un array, ma con entrambi i valori, separati da uno spazio?

O mi sto perdendo qualcosa di ovvio?

Risposte:


108

La risposta di Christian funziona bene e mostra come puoi scorrere ogni elemento della tabella hash usando il GetEnumeratormetodo. È inoltre possibile eseguire il ciclo utilizzando la keysproprietà. Ecco un esempio di come:

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

Produzione:

key = c , value = 3
key = a , value = 1
key = b , value = 2

Come posso enumerare l'hash in un altro ordine? Ad esempio, quando voglio stamparne il contenuto in ordine crescente (qui a ... b ... c). È anche possibile?
LPrc

5
Perché $hash.Item($_)invece di $hash[$_]?
alvarez

1
@LPrc usa il metodo Sort-Object per farlo. Questo articolo ne fornisce una spiegazione abbastanza buona: technet.microsoft.com/en-us/library/ee692803.aspx
chazbot7

4
Potresti anche usare solo $hash.$_.
TNT

1
Questo approccio non funziona se la tabella hash contiene key = 'Keys'.
Boris Nikitin

195

La stenografia non è preferita per gli script; è meno leggibile. L'operatore% {} è considerato abbreviazione. Ecco come dovrebbe essere fatto in uno script per leggibilità e riusabilità:

Configurazione variabile

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

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

Opzione 1: GetEnumerator ()

Nota: preferenza personale; la sintassi è più facile da leggere

Il metodo GetEnumerator () verrebbe eseguito come mostrato:

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

Produzione:

c: 3
b: 2
a: 1

Opzione 2: chiavi

Il metodo Keys verrebbe eseguito come mostrato:

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

Produzione:

c: 3
b: 2
a: 1

Informazioni aggiuntive

Fai attenzione a ordinare la tua tabella hash ...

Sort-Object può cambiarlo in un 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

Questo e altri loop di PowerShell sono disponibili sul mio blog.


3
come questo in più per la leggibilità, soprattutto per gli sviluppatori powershell non dedicati come me.
anIBMer

1
Sono d'accordo che questo metodo sia più facile da leggere e quindi probabilmente migliore per lo scripting.
leinad13

2
Leggibilità a parte, c'è una differenza tra la soluzione di VertigoRay e la soluzione di Andy. % {} è un alias per ForEach-Object, che è diverso foreachdall'istruzione qui. ForEach-Objectfa uso della pipeline, che può essere molto più veloce se stai già lavorando con una pipeline. La foreachdichiarazione non lo fa; è un semplice ciclo.
JamesQMurphy

4
@JamesQMurphy Ho copiato e incollato la risposta fornita da @ andy-arismendi dall'alto; è la popolare soluzione di pipeline per questa domanda. La tua affermazione, che ha suscitato il mio interesse, è stata che ForEach-Object(aka:) %{}è più veloce di foreach; Ho dimostrato che non è più veloce . Volevo dimostrare se il tuo punto era valido o no; perché a me, come la maggior parte delle persone, piace che il mio codice venga eseguito il più velocemente possibile. Ora, il tuo argomento sta cambiando nell'esecuzione di lavori in parallelo (potenzialmente utilizzando più computer / server) ... che ovviamente è più veloce dell'esecuzione di un lavoro in serie da un singolo thread. Saluti!
VertigoRay

1
@JamesQMurphy Penserei anche che powershell segua il vantaggio tradizionale che le shell ti danno quando installi flussi di dati tra diversi processi: ottieni un parallelismo naturale dal semplice fatto che ogni fase della pipeline è un processo indipendente (ovviamente, un buffer può svuotarsi e in alla fine l'intera pipeline va lenta quanto il suo processo più lento). È diverso dall'avere un processo di pipeline che decide da solo di generare più thread, che dipende interamente dai dettagli di implementazione del processo stesso.
Johan Boulé

8

Puoi anche farlo senza una variabile

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

Questo mi fa ottenere un "ForEach-Object: Impossibile associare il parametro" Process ". Impossibile convertire il valore" getEnumerator "di tipo" System.String "nel tipo" System.Management.Automation.ScriptBlock "
luis.espinal

6

Informazioni sul ciclo attraverso un hash:

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

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

5

Ecco un altro modo rapido, utilizzando semplicemente la chiave come indice nella tabella hash per ottenere il valore:

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

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

5

Preferisco questa variante sul metodo enumeratore con una pipeline, perché non devi fare riferimento alla tabella hash in foreach (testata in PowerShell 5):

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

Produzione:

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

Ora, questo non è stato deliberatamente ordinato in base al valore, l'enumeratore restituisce semplicemente gli oggetti in ordine inverso.

Ma poiché questa è una pipeline, ora posso ordinare gli oggetti ricevuti dall'enumeratore in base al valore:

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

Produzione:

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

L'enumeratore può restituire i valori in "qualsiasi" ordine, poiché itera semplicemente l'implementazione sottostante. In questo caso sembra essere in ordine inverso. Per un controesempio: $x = @{a = 1; z = 2}; $x.q = 3è "ordinato" come q, a, z qui.
user2864740


0

Se stai usando PowerShell v3, puoi usare JSON invece di una tabella hash e convertirlo in un oggetto con 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 il comando è ConvertFrom-Json (e viceversa, ConvertTo-Json): basta scambiare la posizione del trattino.
atmarx
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.