Faire une boucle via un hachage ou utiliser un tableau dans PowerShell

96

J'utilise ce morceau de code (simplifié) pour extraire un ensemble de tables de SQL Server avec 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
}

Cela fonctionne très bien, mais maintenant certaines tables doivent être extraites via des vues, et d'autres non. J'ai donc besoin d'une structure de données comme celle-ci:

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

Je regardais les hachages, mais je ne trouve rien sur la façon de parcourir un hachage. Quelle serait la bonne chose à faire ici? Peut-être simplement utiliser un tableau, mais avec les deux valeurs, séparées par un espace?

Ou est-ce que je rate quelque chose d'évident?

Sylvia
la source

Réponses:

108

La réponse de Christian fonctionne bien et montre comment vous pouvez parcourir chaque élément de table de hachage en utilisant la GetEnumeratorméthode. Vous pouvez également parcourir en boucle en utilisant la keyspropriété. Voici un exemple comment:

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

Production:

key = c , value = 3
key = a , value = 1
key = b , value = 2
Andy Arismendi
la source
Comment puis-je énumérer le hachage dans un autre ordre? Par exemple, lorsque je souhaite imprimer son contenu par ordre croissant (ici a ... b ... c). Est-ce même possible?
LPrc
5
Pourquoi $hash.Item($_)au lieu de $hash[$_]?
alvarez
1
@LPrc utilise la méthode Sort-Object pour ce faire. Cet article en fait une assez bonne explication: technet.microsoft.com/en-us/library/ee692803.aspx
chazbot7
4
Vous pouvez même utiliser juste $hash.$_.
TNT
1
Cette approche ne fonctionne pas si la table de hachage contient key = 'Keys'.
Boris Nikitin
197

La sténographie n'est pas préférée pour les scripts; il est moins lisible. L'opérateur% {} est considéré comme un raccourci. Voici comment procéder dans un script pour plus de lisibilité et de réutilisation:

Configuration variable

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

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

Option 1: GetEnumerator ()

Remarque: préférence personnelle; la syntaxe est plus facile à lire

La méthode GetEnumerator () serait effectuée comme indiqué:

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

Production:

c: 3
b: 2
a: 1

Option 2: Clés

La méthode Keys serait effectuée comme indiqué:

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

Production:

c: 3
b: 2
a: 1

Information additionnelle

Soyez prudent en triant votre hashtable ...

Sort-Object peut le changer en tableau:

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

Ceci et d'autres boucles PowerShell sont disponibles sur mon blog.

VertigoRay
la source
3
comme celui-ci plus pour la lisibilité, en particulier pour les développeurs PowerShell non dédiés comme moi.
anIBMer
1
Je suis d'accord que cette méthode est plus facile à lire et donc probablement meilleure pour les scripts.
leinad13
2
Mis à part la lisibilité, il existe une différence entre la solution de VertigoRay et celle d'Andy. % {} est un alias pour ForEach-Object, qui est différent de l' foreachinstruction ici. ForEach-Objectutilise le pipeline, qui peut être beaucoup plus rapide si vous travaillez déjà avec un pipeline. La foreachdéclaration ne le fait pas; c'est une simple boucle.
JamesQMurphy
4
@JamesQMurphy J'ai copié et collé la réponse fournie par @ andy-arismendi d'en haut; c'est la solution de pipeline populaire pour cette question. Votre déclaration, qui a culminé mon intérêt, était que ForEach-Object(aka:) %{}est plus rapide que foreach; J'ai montré que ce n'était pas plus rapide . Je voulais démontrer si votre argument était valable ou non; parce que, comme la plupart des gens, j'aime que mon code s'exécute aussi vite que possible. Maintenant, votre argument est de changer pour exécuter des travaux en parallèle (en utilisant potentiellement plusieurs ordinateurs / serveurs) ... ce qui est bien sûr plus rapide que d'exécuter un travail en série à partir d'un seul thread. À votre santé!
VertigoRay
1
@JamesQMurphy Je pense également que powershell suit l'avantage traditionnel que les shells vous donnent lorsque vous dirigez des flux de données entre plusieurs processus: vous obtenez un parallélisme naturel du simple fait que chaque étape du pipeline est un processus indépendant (bien sûr, un tampon peut drainer et la fin, l'ensemble du pipeline va aussi lentement que son processus le plus lent). C'est différent d'avoir un processus de pipeline qui décide seul de générer plusieurs threads, ce qui dépend entièrement des détails d'implémentation du processus lui-même.
Johan Boulé
8

Vous pouvez également le faire sans variable

@{
  'foo' = 222
  'bar' = 333
  'baz' = 444
  'qux' = 555
} | % getEnumerator | % {
  $_.key
  $_.value
}
Steven Penny
la source
Cela me donne un "ForEach-Object: Impossible de lier le paramètre 'Process'. Impossible de convertir la valeur" getEnumerator "de type" System.String "en type" System.Management.Automation.ScriptBlock "
luis.espinal
6

À propos de la boucle sur un hachage:

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

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

Voici un autre moyen rapide, en utilisant simplement la clé comme index dans la table de hachage pour obtenir la valeur:

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

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

Je préfère cette variante à la méthode de l'énumérateur avec un pipeline, car vous n'avez pas à vous référer à la table de hachage dans le foreach (testé dans PowerShell 5):

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

Production:

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

Maintenant, cela n'a pas été délibérément trié sur la valeur, l'énumérateur renvoie simplement les objets dans l'ordre inverse.

Mais comme il s'agit d'un pipeline, je peux maintenant trier les objets reçus de l'énumérateur sur la valeur:

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

Production:

Key = a and Value = 3
Key = b and Value = 2
Key = c and Value = 1
Eelco L.
la source
L'énumérateur peut renvoyer les valeurs dans "n'importe quel" ordre, car il itère simplement l'implémentation sous-jacente. Dans ce cas, cela semble être l'ordre inverse. Pour un contre-exemple: $x = @{a = 1; z = 2}; $x.q = 3est "ordonné" comme q, a, z ici.
user2864740
0

Si vous utilisez PowerShell v3, vous pouvez utiliser JSON au lieu d'une table de hachage et le convertir en objet avec 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
la source
nb la commande est ConvertFrom-Json (et l'inverse, ConvertTo-Json) - il suffit de changer le placement du tiret.
atmarx