Wählen Sie die Werte einer Eigenschaft für alle Objekte eines Arrays in PowerShell aus

Nehmen wir an, wir haben ein Array von Objekten $ objects. Nehmen wir an, diese Objekte haben eine Eigenschaft “Name”.

Das möchte ich tun

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

Das funktioniert, aber kann es besser gemacht werden?

Wenn ich etwas mache wie:

  $results = objects | select Name 

$results ist ein Array von Objekten mit einer Name-Eigenschaft. Ich möchte, dass $ results ein Array von Names enthält.

Gibt es einen besseren Weg?

Solutions Collecting From Web of "Wählen Sie die Werte einer Eigenschaft für alle Objekte eines Arrays in PowerShell aus"

Ich denke, dass Sie möglicherweise den ExpandProperty Parameter von Select-Object .

Um zum Beispiel die Liste des aktuellen Verzeichnisses zu erhalten und nur die Eigenschaft Name anzuzeigen, würde man Folgendes tun:

 ls | select -Property Name 

Dies gibt immer noch DirectoryInfo oder FileInfo Objekte zurück. Sie können den Typ, der durch die Pipeline kommt, immer durch Piping zu Get-Member (alias gm ) überprüfen.

 ls | select -Property Name | gm 

Um das Objekt so zu erweitern , dass es sich um den Typ der Eigenschaft handelt, die Sie gerade betrachten, können Sie Folgendes tun:

 ls | select -ExpandProperty Name 

In Ihrem Fall können Sie einfach Folgendes tun, damit eine Variable ein Array von Strings ist, wobei die Strings die Name-Eigenschaft sind:

 $objects = ls | select -ExpandProperty Name 

Als eine noch einfachere Lösung könnten Sie einfach verwenden:

 $results = $objects.Name 

Dies sollte $results mit einem Array aller “Name” -Eigenschaftswerte der Elemente in $objects füllen.

Ergänzen Sie die bereits vorhandenen, hilfreichen Antworten mit einer Anleitung, wann Sie welchen Ansatz und einen performancesvergleich verwenden sollten .

  • Außerhalb einer Pipeline verwenden Sie:

      $ Objekte .  Name 

    (PSv3 +), wie in der Antwort von rageandqq gezeigt , die sowohl syntaktisch einfacher als auch viel schneller ist .

    • Der Zugriff auf eine Eigenschaft auf der Auflistungsebene , um die Werte ihrer Elemente als Array abzurufen, wird als Mitgliederaufzählung bezeichnet und ist eine PSv3 + -function .
    • Verwenden Sie alternativ in PSv2 die foreach statement , deren Ausgabe Sie auch direkt einer Variablen zuweisen können:
        $ results = foreach ($ obj in $ objects) {$ obj.Name} 
    • Kompromisse :
      • Sowohl das Input-Collection- als auch das Output-Array müssen als Ganzes in den Speicher passen.
      • Wenn die Eingabeauflistung selbst das Ergebnis eines Befehls (Pipeline) ist (z. B. (Get-ChildItem).Name ), muss dieser Befehl zuerst (Get-ChildItem).Name ausgeführt werden, bevor auf die Elemente des resultierenden Arrays zugegriffen werden kann.
  • In einer Pipeline, in der das Ergebnis weiter verarbeitet werden muss oder die Ergebnisse nicht in den Speicher als Ganzes passen, verwenden Sie:

      $ Objekte |  Select-Object -ExpandProperty Name 

    • Die Notwendigkeit von -ExpandProperty wird in Scott Saads Antwort erläutert.
    • Sie erhalten die üblichen Pipeline-Vorteile der Einzelverarbeitung, die in der Regel sofort eine Ausgabe erzeugt und den Speicherverbrauch konstant hält (es sei denn, Sie sammeln die Ergebnisse letztendlich im Speicher).
    • Kompromiss :
      • Die Nutzung der Pipeline ist vergleichsweise langsam .

Bei kleinen Eingabesammlungen (Arrays) werden Sie wahrscheinlich den Unterschied nicht bemerken , und besonders in der Befehlszeile ist es manchmal wichtiger, den Befehl leicht eingeben zu können.


Hier ist eine leicht zu typisierende Alternative , die jedoch der langsamste Ansatz ist ; Es verwendet eine vereinfachte ForEach-Object Syntax, die als Operationsanweisung (wiederum PSv3 +) bezeichnet wird:; Die folgende PSv3 + -Lösung kann beispielsweise einfach an einen vorhandenen Befehl angehängt werden:

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

Der Vollständigkeit halber: Die wenig bekannte PSv4 + .ForEach() Erfassungsmethode ist eine weitere Alternative :

 # By property name (string): $objects.ForEach('Name') # By script block (much slower): $objects.ForEach({ $_.Name }) 
  • Dieser Ansatz ähnelt der Elementaufzählung mit den gleichen Kompromissen, außer dass die Pipeline-Logik nicht angewendet wird. es ist geringfügig langsamer , aber immer noch merklich schneller als die Pipeline.

  • Zum Extrahieren eines einzelnen Eigenschaftswerts nach Name ( Zeichenfolgenargument ) entspricht diese Lösung der Elementenumeration (obwohl letztere syntaktisch einfacher ist).

  • Die Script-Block- Variante erlaubt, obwohl viel langsamer, beliebige Transformationen ; Es ist eine schnellere – All-in-memory-at-once – Alternative zum Pipeline-basierten ForEach-Object Cmdlet .


Vergleichen der performance der verschiedenen Ansätze

Hier sind Beispielzeiten für die verschiedenen Ansätze, basierend auf einer Eingangssammlung von 100,000 Objekten , gemittelt über 100 Läufe; die absoluten Zahlen sind nicht wichtig und variieren aufgrund vieler Faktoren, aber es sollte Ihnen ein Gefühl der relativen performance geben:

 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 
  • Die Methode der Auflistungs- / Eigenschaftsname-basierten Auflistungsmethode ist um einen Faktor von 10 schneller als die schnellste pipeline-basierte Lösung.

  • Die foreach statement ist etwa 2,5 langsamer, aber immer noch 4-5 mal so schnell wie die schnellste Pipeline-Lösung.

  • Die Verwendung eines .ForEach({ ... } mit der Sammlungsmethoden-Lösung ( .ForEach({ ... } ) verlangsamt die .ForEach({ ... } dramatisch, so dass sie praktisch mit der schnellsten pipeline-basierten Lösung ( Select-Object -ExpandProperty ) Select-Object -ExpandProperty .

  • % Number ( ForEach-Object Number ) führt merkwürdigerweise am schlechtesten aus, obwohl % Number die konzeptionelle Entsprechung von % { $_.Number } .


Quellcode für die Tests :

Hinweis: Laden Sie die function Time-Command von diesem Gist herunter, um diese Tests auszuführen.

 $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