Sélectionnez les valeurs d'une propriété sur tous les objets d'un tableau dans PowerShell

Disons que nous avons un tableau d'objets $objets. Disons que ces objets ont une propriété "Nom".

C'est ce que je veux faire

 $results = @()
 $objects | %{ $results += $_.Name }

Cela fonctionne, mais peut - il être fait d'une meilleure façon?

Si je fais quelque chose comme:

 $results = objects | select Name

$results est un tableau d'objets ayant un Nom de propriété. Je veux que $results contienne un tableau de noms.

Est-il un meilleur moyen?

92
demandé sur Peter Mortensen 2011-03-03 07:55:08

3 réponses

Je pense que vous pourriez être en mesure d'utiliser le ExpandProperty paramètre de Select-Object.

Par exemple, pour obtenir la liste du répertoire courant et simplement afficher la propriété Name, on ferait ce qui suit:

ls | select -Property Name

Cela renvoie toujours des objets DirectoryInfo ou FileInfo. Vous pouvez toujours inspecter le type traversant le pipeline en passant par Get-Member (alias gm).

ls | select -Property Name | gm

Donc, pour développer l'objet pour être celui du type de propriété que vous recherchez à, vous pouvez faire ce qui suit:

ls | select -ExpandProperty Name

Dans votre cas, vous pouvez simplement faire ce qui suit pour qu'une variable soit un tableau de chaînes, où les chaînes sont la propriété Name:

$objects = ls | select -ExpandProperty Name
161
répondu Scott Saad 2016-02-08 12:17:07

Comme une solution encore plus facile, vous pouvez simplement utiliser:

$results = $objects.Name

Qui devrait remplir $results avec un tableau de toutes les valeurs de propriété' Name ' des éléments dans $objects.

50
répondu rageandqq 2014-07-24 15:58:18

Pour compléter les réponses préexistantes et utiles avec des indications sur Quand utiliser quelle approche et une comparaison de performances .

  • en dehors de d'un pipeline, utilisez:

    $objects.Name
    (PSv3+), comme démontré dans la réponse de rageandqq, qui est à la fois syntaxiquement plus simple et beaucoup plus rapide .

    • accéder à une propriété au niveau collection pour obtenir ses membres ' valeurs en tant que matrice est appelé membre de l'énumération et est un PSv3+ fonction;
    • sinon, dans PSv2 , utilisez le foreach instruction , dont vous pouvez également attribuer la sortie directement à une variable:
      $results = foreach ($obj in $objects) { $obj.Name }
    • Compromis:
      • la collection d'entrée et le tableau de sortie doit tenir dans la mémoire dans son ensemble .
      • Si la collection d'entrées est elle-même le résultat d'une commande (pipeline) (par exemple, (Get-ChildItem).Name), Cette commande doit d'abord s'exécuter jusqu'à l'achèvement avant d'accéder aux éléments du tableau résultant.
  • Dans un pipeline lorsque le résultat doit être traité plus loin ou que les résultats ne rentrent pas dans la mémoire dans son ensemble, utilisez:

    $objects | Select-Object -ExpandProperty Name

    • la nécessité de -ExpandPropertyest expliquée dans la réponse de Scott saad.
    • vous bénéficiez des avantages habituels d'un traitement un par un, ce qui produit généralement une sortie tout de suite et maintient l'utilisation de la mémoire constante (sauf si vous collectez finalement les résultats en mémoire de toute façon).
    • Compromis:
      • L'utilisation du pipeline est comparativement lente .

Pour petites collections d'entrée (tableaux), vous ne remarquerez probablement pas la différence, et, surtout sur la ligne de commande, pouvoir parfois taper la commande facilement est plus important.


Voici une alternative facile à taper , qui est cependant la l'approche la plus lente ; elle utilise la syntaxe ForEach-Object simplifiée appelée instruction d'opération ( encore une fois, PSv3+): ; par exemple, la solution PSv3+ suivante est facile à ajouter à une commande existante:

$objects | % Name      # short for: $objects | ForEach-Object -Process { $_.Name }

, Par souci d'exhaustivité: Le peu connu PSv4+ .ForEach() la méthode de collecte de est encore un autre alternative :

# By property name (string):
$objects.ForEach('Name')

# By script block (much slower):
$objects.ForEach({ $_.Name })
  • Cette approche est similaire à l'énumération des membres , avec les mêmes compromis, sauf que la logique du pipeline est pas appliquée; elle estlégèrement plus lente , bien que toujours sensiblement plus rapide que le pipeline.

  • Pour extraire une seule valeur de propriété par name (string argument), cette solution est à égalité avec l'énumération des membres (bien que cette dernière soit syntaxique plus simple).

  • Le script-block variant , bien que beaucoup plus lent, permet des transformations arbitraires ; c'est une alternative plus rapide tout-en-mémoire à la fois au pipeline ForEach-Object cmdlet.


Comparaison des performances des différentes approches

Voici exemple timings pour les diverses approches, basées sur une collection d'entrée de 100,000 objets, moyenne à travers 100 courses; les nombres absolus ne sont pas importants et varient en fonction de nombreux facteurs, mais cela devrait vous donner une idée de la performancerelative :

Command                                         FriendlySecs (100-run avg.) Factor
-------                                         --------------------------- ------
$objects.ForEach('Number')                      0.078                       1.00
$objects.Number                                 0.079                       1.02
foreach($o in $objects) { $o.Number }           0.188                       2.42
$objects | Select-Object -ExpandProperty Number 0.881                       11.36
$objects.ForEach({ $_.Number })                 0.925                       11.93
$objects | % { $_.Number }                      1.564                       20.16
$objects | % Number                             2.974                       38.35
  • La solution de méthode de collecte basée sur l'énumération des membres / le nom de la propriété est plus rapide d'un facteur 10+ que la solution basée sur le pipeline la plus rapide.

  • Le foreach statement la solution est environ 2,5 plus lente, mais toujours environ 4-5 fois plus rapide que le pipeline le plus rapide solution.

  • L'utilisation d'un bloc de script avec la solution de méthode de collecte (.ForEach({ ... }) ralentit considérablement les choses, de sorte qu'il est pratiquement à égalité avec la solution basée sur le pipeline la plus rapide (Select-Object -ExpandProperty).

  • % Number (ForEach-Object Number), curieusement, effectue pire, même si % Number est l'équivalent conceptuel de % { $_.Number }).


Le code Source pour les tests:

Note: Télécharger fonction Time-Command de cet essentiel pour l'exécution de ces tests.

$count = 1e5 # input-object count (100,000)
$runs  = 100  # number of runs to average 

# Create sample input objects.
$objects = 1..$count | % { [pscustomobject] @{ Number = $_ } }

# An array of script blocks with the various approaches.
$approaches = { $objects | Select-Object -ExpandProperty Number },
              { $objects | % Number },
              { $objects | % { $_.Number } },
              { $objects.ForEach('Number') },
              { $objects.ForEach({ $_.Number }) },
              { $objects.Number },
              { foreach($o in $objects) { $o.Number } }

# Time the approaches and sort them by execution time (fastest first):
Time-Command $approaches -Count $runs | Select Command, FriendlySecs*, Factor
8
répondu mklement0 2018-08-24 16:03:07