Select single property values ​​for all array objects in PowerShell

Say we have an array of $ objects. Let them say that these objects have the "Name" property.

This is what I want to do.

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

It works, but is it better to do it?

If I do something like:

  $results = objects | select Name 

$results is an array of objects that have the Name property. I want $ results to contain an array of names.

Is there a better way?

+110
arrays powershell member-enumeration
Mar 03 2018-11-11T00:
source share
3 answers

I think you could use the ExpandProperty Select-Object parameter.

For example, to get a list of the current directory and simply display the Name property, do the following:

 ls | select -Property Name 

This still returns DirectoryInfo or FileInfo objects. You can always check the type passing through the pipeline by connecting to the Get-Member (alias gm ).

 ls | select -Property Name | gm 

Thus, to expand the object to the type you are looking for, you can do the following:

 ls | select -ExpandProperty Name 

In your case, you can simply do the following so that the variable is an array of strings, where the strings are a Name property:

 $objects = ls | select -ExpandProperty Name 
+190
Mar 03 '11 at 5:15
source share

As an even simpler solution, you can simply use:

 $results = $objects.Name 

Which should fill $results array of all the values ​​of the "Name" property of the elements in $objects .

+61
Jul 24 '14 at 15:58
source share

In addition to the existing ones, useful answers indicating when to use which approach and performance comparison .

  • Outside the pipeline use:

      $ objects .  Name 
    (PSv3 +) as shown in rageandqq's answer , which is syntactically simpler and much faster .
    • Access to a property at the collection level to obtain the values ​​of its members in an array is called an enumeration of members and is a function of PSv3 + .
    • Alternatively, in PSv2, use the foreach , the output of which you can also assign directly to a variable:
        $ results = foreach ($ obj in $ objects) {$ obj.Name} 
    • Trade-offs :
      • Both the input collection and the output array must fit into memory as a whole.
      • If the input collection itself is the result of a command (pipeline) (for example, (Get-ChildItem).Name ), this command must first be run before completion before array elements are available.
  • In the pipeline where the result should be processed further or the results do not fit into memory in general, use:

      $ objects |  Select-Object -ExpandProperty Name 
    • The need for -ExpandProperty is explained in Scott Saad's answer .
    • You get the usual benefits of pipelining one at a time, which usually produces output right away and maintains constant memory usage (if you still don't collect the results in memory).
    • Exchange :
      • The use of the pipeline is relatively slow.

For small input collections (arrays), you probably won't notice the difference , and especially on the command line, sometimes the ability to type a command is easily more important.




Here is an easy-to-enter alternative , which, however, is the slowest approach ; it uses the simplified ForEach-Object syntax called the operation operator (again, PSv3 +) :; For example, the following PSv3 + solution is easy to add to an existing command:

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



For the sake of completeness: a little-known method for collecting PSv4 + .ForEach() is another alternative :

 # By property name (string): $objects.ForEach('Name') # By script block (much slower): $objects.ForEach({ $_.Name }) 
  • This approach is similar to listing members with the same trade-offs, except that pipelining logic is not applied; it's a little slower , although still noticeably faster than the conveyor.

  • To extract a single property value by name (string argument), this solution is on par with the enumeration of members (although the latter is syntactically simpler).

  • The script block variant, although much slower, allows arbitrary transformations; it’s a faster “all in one memory” alternative to the pipeline-based ForEach-Object cmdlet .




Comparing the effectiveness of different approaches

Here are sample time intervals for various approaches based on an input collection of 100,000 objects averaged over 100 runs; absolute numbers are not important and vary depending on many factors, but they should give you an idea of ​​relative effectiveness:

 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 
  • A solution using a member / property-based data collection method is 10+ faster than the fastest pipeline-based solution.

  • The foreach solution is about 2.5 slower, but still about 4-5 times faster than the fastest pipelined solution.

  • Using a script block with a solution to the collection method ( .ForEach({... } ) significantly slows down the process, so it almost corresponds to the fastest pipeline-based solution ( Select-Object -ExpandProperty ).

  • Curiously, % Number ( ForEach-Object Number ) works worse, although % Number is the conceptual equivalent of % { $_.Number } ).




Source code for tests :

Note. To run these tests, load the Time-Command function from this list .

 $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 
+21
Feb 20 '18 at
source share



All Articles