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?
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
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
.
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
-ExpandProperty
est 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 .
- la nécessité de
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