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?

   

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