简体   繁体   English

Select PowerShell 中数组的所有对象的一个属性的值

[英]Select the values of one property on all objects of an array in PowerShell

Let's say we have an array of objects $objects.假设我们有一个对象数组 $objects。 Let's say these objects have a "Name" property.假设这些对象具有“名称”属性。

This is what I want to do这就是我想做的

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

This works, but can it be done in a better way?这行得通,但是可以以更好的方式完成吗?

If I do something like:如果我这样做:

 $results = objects | select Name

$results is an array of objects having a Name property. $results是一个具有 Name 属性的对象数组。 I want $results to contain an array of Names.我希望 $results 包含一个名称数组。

Is there a better way?有没有更好的办法?

I think you might be able to use the ExpandProperty parameter of Select-Object .我认为您可以使用Select-ObjectExpandProperty参数。

For example, to get the list of the current directory and just have the Name property displayed, one would do the following:例如,要获取当前目录的列表并只显示 Name 属性,可以执行以下操作:

ls | select -Property Name

This is still returning DirectoryInfo or FileInfo objects.这仍然返回 DirectoryInfo 或 FileInfo 对象。 You can always inspect the type coming through the pipeline by piping to Get-Member (alias gm ).您始终可以通过管道传递到Get-Member (别名gm )来检查通过管道的类型。

ls | select -Property Name | gm

So, to expand the object to be that of the type of property you're looking at, you can do the following:因此,要将对象扩展为您正在查看的属性类型,您可以执行以下操作:

ls | select -ExpandProperty Name

In your case, you can just do the following to have a variable be an array of strings, where the strings are the Name property:在您的情况下,您只需执行以下操作即可使变量成为字符串数组,其中字符串是 Name 属性:

$objects = ls | select -ExpandProperty Name

As an even easier solution, you could just use:作为更简单的解决方案,您可以使用:

$results = $objects.Name

Which should fill $results with an array of all the 'Name' property values of the elements in $objects .这应该用$objects元素的所有 'Name' 属性值的数组填充$results

To complement the preexisting, helpful answers with guidance of when to use which approach and a performance comparison .通过指导何时使用哪种方法性能比较来补充已有的、有用的答案。

  • Outside of a pipeline [1] , use (PSv3+):管道[1] 之外,使用 (PSv3+):

     $objects . $objects  Name姓名
    as demonstrated in rageandqq's answer , which is both syntactically simpler and much faster .rageandqq 的回答所示,它在语法上更简单,速度也更快

    • Accessing a property at the collection level to get its members' values as an array is called member enumeration and is a PSv3+ feature.集合级别访问属性以获取其成员值作为数组称为成员枚举,并且是 PSv3+ 功能。

    • Alternatively, in PSv2 , use the foreach statement , whose output you can also assign directly to a variable:或者,在PSv2 中,使用foreach语句,您也可以将其输出直接分配给变量:

       $results = foreach ($obj in $objects) { $obj.Name } $results = foreach ($obj in $objects) { $obj.Name }

    • If collecting all output from a (pipeline) command in memory first is feasible, you can also combine pipelines with member enumeration ;如果首先在内存中收集(管道)命令的所有输出是可行的,您还可以管道与成员枚举结合起来 eg:例如:

       (Get-ChildItem -File | Where-Object Length -lt 1gb).Name
    • Tradeoffs :权衡

      • Both the input collection and output array must fit into memory as a whole .输入集合和输出数组都必须作为一个整体放入内存
      • If the input collection is itself the result of a command (pipeline) (eg, (Get-ChildItem).Name ), that command must first run to completion before the resulting array's elements can be accessed.如果输入集合本身是命令(管道)(例如(Get-ChildItem).Name )的结果,则该命令必须首先运行完成,然后才能访问结果数组的元素。
  • In a pipeline , in case you must pass the results to another command, notably if the original input doesn't fit into memory as a whole, use:管道中,如果您必须将结果传递给另一个命令,特别是如果原始输入整体不适合内存,请使用:

     $objects | $对象 | Select-Object -ExpandProperty Name 选择对象-ExpandProperty 名称

    • The need for -ExpandProperty is explained in Scott Saad's answer (you need it to get only the property value ). Scott Saad 的回答中解释了-ExpandProperty的需要(您需要它来仅获取属性)。
    • You get the usual pipeline benefits of the pipeline's streaming behavior, ie one-by-one object processing, which typically produces output right away and keeps memory use constant (unless you ultimately collect the results in memory anyway).您可以从管道的流媒体行为中获得通常的管道优势,即一对一对象处理,这通常会立即产生输出并保持内存使用不变(除非您最终将结果收集到内存中)。
    • Tradeoff :权衡
      • Use of the pipeline is comparatively slow .使用管道比较

For small input collections (arrays), you probably won't notice the difference , and, especially on the command line, sometimes being able to type the command easily is more important.对于小型输入集合(数组),您可能不会注意到其中的区别,而且,尤其是在命令行上,有时能够轻松键入命令更为重要。


Here is an easy-to-type alternative , which, however is the slowest approach ;这是一个易于输入的替代方法,但它是最慢的方法 it uses simplified ForEach-Object syntax called an operation statement (again, PSv3+): ;它使用简化的ForEach-Object语法,称为操作语句(同样是 PSv3+): eg, the following PSv3+ solution is easy to append to an existing command:例如,以下 PSv3+ 解决方案很容易附加到现有命令:

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

Note : Use of the pipeline is not the primary reason this approach is slow, it is the inefficient implementation of the ForEach-Object (and Where-Object ) cmdlets , up to at least PowerShell 7.2.注意:管道的使用不是这种方法缓慢的主要原因,它是ForEach-Object (和Where-Object )cmdlet低效实现,至少在 PowerShell 7.2 中。 This excellent blog post explains the problem;这篇优秀的博客文章解释了这个问题; it led to feature request GitHub issue #10982 ;它导致功能请求GitHub 问题 #10982 the following workaround greatly speeds up the operation (only somewhat slower than a foreach statement, and still faster than .ForEach() ):以下解决方法大大加快了操作速度(仅比foreach语句慢一点,但仍然比.ForEach()快):

# Speed-optimized version of the above.
# (Use `&` instead of `.` to run in a child scope)
$objects | . { process { $_.Name } }

The PSv4+ .ForEach() array method , more comprehensively discussed in this article , is yet another, well-performing alternative , but note that it requires collecting all input in memory first , just like member enumeration: PSv4+ .ForEach .ForEach()数组方法本文中进行了更全面的讨论, 另一种性能良好的替代方法,但请注意,它需要先收集内存中的所有输入,就像成员枚举一样:

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

# By script block (more flexibility; like ForEach-Object)
$objects.ForEach({ $_.Name })
  • This approach is similar to member enumeration , with the same tradeoffs, except that pipeline logic is not applied;这种方法类似于成员枚举,具有相同的权衡,只是应用管道逻辑; it is marginally slower than member enumeration , though still noticeably faster than the pipeline .成员枚举稍微慢一点,但仍然明显比管道

  • For extracting a single property value by name ( string argument), this solution is on par with member enumeration (though the latter is syntactically simpler).对于按名称字符串参数)提取单个属性值,此解决方案与成员枚举相当(尽管后者在语法上更简单)。

  • The script-block variant ( { ... } ) allows arbitrary transformations ;脚本块变体 ( { ... } ) 允许任意转换 it is a faster - all-in-memory-at-once - alternative to the pipeline-based ForEach-Object cmdlet ( % ) .它是基于管道的ForEach-Object cmdlet ( % ) 的一种更快的全内存一次性替代方案

Note: The .ForEach() array method, like its .Where() sibling (the in-memory equivalent of Where-Object ), always returns a collection (an instance of [System.Collections.ObjectModel.Collection[psobject]] ), even if only one output object is produced.注意: .ForEach()数组方法,就像它的.Where()兄弟(内存中等价于Where-Object )一样,总是返回一个集合[System.Collections.ObjectModel.Collection[psobject]]一个实例) ,即使只产生一个输出对象。
By contrast, member enumeration, Select-Object , ForEach-Object and Where-Object return a single output object as-is, without wrapping it in a collection (array).相比之下,成员枚举、 Select-ObjectForEach-ObjectWhere-Object原样返回单个输出对象,而不将其包装在集合(数组)中。


Comparing the performance of the various approaches比较各种方法的性能

Here are sample timings for the various approaches, based on an input collection of 10,000 objects , averaged across 10 runs;以下是各种方法的示例计时,基于10,000对象的输入集合,10 次运行的平均值; the absolute numbers aren't important and vary based on many factors, but it should give you a sense of relative performance (the timings come from a single-core Windows 10 VM:绝对数字并不重要,并且会因许多因素而异,但它应该能让您了解相对性能(计时来自单核 Windows 10 VM:

Important重要的

  • The relative performance varies based on whether the input objects are instances of regular .NET Types (eg, as output by Get-ChildItem ) or [pscustomobject] instances (eg, as output by Convert-FromCsv ).相对性能取决于输入对象是常规 .NET 类型的实例(例如,作为Get-ChildItem输出)还是[pscustomobject]实例(例如,作为Convert-FromCsv输出)。
    The reason is that [pscustomobject] properties are dynamically managed by PowerShell, and it can access them more quickly than the regular properties of a (statically defined) regular .NET type.原因是[pscustomobject]属性由 PowerShell 动态管理,它可以比(静态定义的)常规 .NET 类型的常规属性更快地访问它们。 Both scenarios are covered below.下面介绍了这两种情况。

  • The tests use already-in-memory-in-full collections as input, so as to focus on the pure property extraction performance.测试使用内存中已满的集合作为输入,以专注于纯属性提取性能。 With a streaming cmdlet / function call as the input, performance differences will generally be much less pronounced, as the time spent inside that call may account for the majority of the time spent.使用流式 cmdlet/函数调用作为输入,性能差异通常不会那么明显,因为在该调用中花费的时间可能占花费的大部分时间。

  • For brevity, alias % is used for the ForEach-Object cmdlet.为简洁起见,别名%用于ForEach-Object cmdlet。

General conclusions , applicable to both regular .NET type and [pscustomobject] input:一般结论,适用于常规 .NET 类型和[pscustomobject]输入:

  • The member-enumeration ( $collection.Name ) and foreach ($obj in $collection) solutions are by far the fastest , by a factor of 10 or more faster than the fastest pipeline-based solution.成员枚举 ( $collection.Name ) 和foreach ($obj in $collection)解决方案是迄今为止最快的,比最快的基于管道的解决方案快 10 倍或更多。

  • Surprisingly, % Name performs much worse than % { $_.Name } - see this GitHub issue .令人惊讶的是, % Name性能比% { $_.Name }差得多 - 请参阅此 GitHub 问题

  • PowerShell Core consistently outperforms Windows Powershell here. PowerShell Core 在这方面始终优于 Windows Powershell。

Timings with regular .NET types :使用常规 .NET 类型的时间

  • PowerShell Core v7.0.0-preview.3 PowerShell 核心 v7.0.0-preview.3
Factor Command                                       Secs (10-run avg.)
------ -------                                       ------------------
1.00   $objects.Name                                 0.005
1.06   foreach($o in $objects) { $o.Name }           0.005
6.25   $objects.ForEach('Name')                      0.028
10.22  $objects.ForEach({ $_.Name })                 0.046
17.52  $objects | % { $_.Name }                      0.079
30.97  $objects | Select-Object -ExpandProperty Name 0.140
32.76  $objects | % Name                             0.148
  • Windows PowerShell v5.1.18362.145 Windows PowerShell v5.1.18362.145
Factor Command                                       Secs (10-run avg.)
------ -------                                       ------------------
1.00   $objects.Name                                 0.012
1.32   foreach($o in $objects) { $o.Name }           0.015
9.07   $objects.ForEach({ $_.Name })                 0.105
10.30  $objects.ForEach('Name')                      0.119
12.70  $objects | % { $_.Name }                      0.147
27.04  $objects | % Name                             0.312
29.70  $objects | Select-Object -ExpandProperty Name 0.343

Conclusions:结论:

  • In PowerShell Core , .ForEach('Name') clearly outperforms .ForEach({ $_.Name }) .在 PowerShell Core 中.ForEach('Name')明显优于.ForEach({ $_.Name }) In Windows PowerShell, curiously, the latter is faster, albeit only marginally so.奇怪的是,在 Windows PowerShell 中,后者更快,尽管只是稍微快一点。

Timings with [pscustomobject] instances : [pscustomobject]实例的时间

  • PowerShell Core v7.0.0-preview.3 PowerShell 核心 v7.0.0-preview.3
Factor Command                                       Secs (10-run avg.)
------ -------                                       ------------------
1.00   $objects.Name                                 0.006
1.11   foreach($o in $objects) { $o.Name }           0.007
1.52   $objects.ForEach('Name')                      0.009
6.11   $objects.ForEach({ $_.Name })                 0.038
9.47   $objects | Select-Object -ExpandProperty Name 0.058
10.29  $objects | % { $_.Name }                      0.063
29.77  $objects | % Name                             0.184
  • Windows PowerShell v5.1.18362.145 Windows PowerShell v5.1.18362.145
Factor Command                                       Secs (10-run avg.)
------ -------                                       ------------------
1.00   $objects.Name                                 0.008
1.14   foreach($o in $objects) { $o.Name }           0.009
1.76   $objects.ForEach('Name')                      0.015
10.36  $objects | Select-Object -ExpandProperty Name 0.085
11.18  $objects.ForEach({ $_.Name })                 0.092
16.79  $objects | % { $_.Name }                      0.138
61.14  $objects | % Name                             0.503

Conclusions:结论:

  • Note how with [pscustomobject] input .ForEach('Name') by far outperforms the script-block based variant, .ForEach({ $_.Name }) .请注意[pscustomobject]输入[pscustomobject] .ForEach('Name')远远优于基于脚本块的变体.ForEach({ $_.Name })

  • Similarly, [pscustomobject] input makes the pipeline-based Select-Object -ExpandProperty Name faster, in Windows PowerShell virtually on par with .ForEach({ $_.Name }) , but in PowerShell Core still about 50% slower.同样, [pscustomobject]输入使基于管道的Select-Object -ExpandProperty Name更快,在 Windows PowerShell 中几乎与.ForEach({ $_.Name })相当,但在 PowerShell Core 中仍然慢了大约 50%。

  • In short: With the odd exception of % Name , with [pscustomobject] the string-based methods of referencing the properties outperform the scriptblock-based ones.简而言之:除了% Name的奇怪例外,在[pscustomobject] ,基于字符串的引用属性的方法优于基于脚本块的方法。


Source code for the tests :测试的源代码

Note:笔记:

  • Download function Time-Command from this Gist to run these tests.这个 Gist下载函数Time-Command来运行这些测试。

    • Assuming you have looked at the linked code to ensure that it is safe (which I can personally assure you of, but you should always check), you can install it directly as follows:假设您已经查看了链接的代码以确保它是安全的(我个人可以向您保证,但您应该始终检查),您可以直接安装它,如下所示:

       irm https://gist.github.com/mklement0/9e1f13978620b09ab2d15da5535d1b27/raw/Time-Command.ps1 | iex
  • Set $useCustomObjectInput to $true to measure with [pscustomobject] instances instead.$useCustomObjectInput设置$useCustomObjectInput $true以使用[pscustomobject]实例进行测量。

$count = 1e4 # max. input object count == 10,000
$runs  = 10  # number of runs to average 

# Note: Using [pscustomobject] instances rather than instances of 
#       regular .NET types changes the performance characteristics.
# Set this to $true to test with [pscustomobject] instances below.
$useCustomObjectInput = $false

# Create sample input objects.
if ($useCustomObjectInput) {
  # Use [pscustomobject] instances.
  $objects = 1..$count | % { [pscustomobject] @{ Name = "$foobar_$_"; Other1 = 1; Other2 = 2; Other3 = 3; Other4 = 4 } }
} else {
  # Use instances of a regular .NET type.
  # Note: The actual count of files and folders in your file-system
  #       may be less than $count
  $objects = Get-ChildItem / -Recurse -ErrorAction Ignore | Select-Object -First $count
}

Write-Host "Comparing property-value extraction methods with $($objects.Count) input objects, averaged over $runs runs..."

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

# Time the approaches and sort them by execution time (fastest first):
Time-Command $approaches -Count $runs | Select Factor, Command, Secs*

[1] Technically, even a command without | [1] 从技术上讲,即使是没有|的命令, the pipeline operator , uses a pipeline behind the scenes, but for the purpose of this discussion using the pipeline refers only to commands that use | 管道操作符,在幕后使用管道,但出于本次讨论的目的,使用管道仅指使用|命令| , the pipeline operator, and therefore by definition involve multiple commands . ,管道运算符,因此根据定义涉及多个命令

Caution, member enumeration only works if the collection itself has no member of the same name.注意,成员枚举仅在集合本身没有同名成员时才有效。 So if you had an array of FileInfo objects, you couldn't get an array of file lengths by using因此,如果您有一个 FileInfo 对象数组,则无法通过使用获取文件长度数组

 $files.length # evaluates to array length

And before you say "well obviously", consider this.在你说“很明显”之前,请考虑这一点。 If you had an array of objects with a capacity property then如果您有一组具有容量属性的对象,那么

 $objarr.capacity

would work fine UNLESS $objarr were actually not an [Array] but, for example, an [ArrayList].会正常工作,除非$objarr 实际上不是 [Array],而是例如 [ArrayList]。 So before using member enumeration you might have to look inside the black box containing your collection.因此,在使用成员枚举之前,您可能需要查看包含您的集合的黑匣子内部。

(Note to moderators: this should be a comment on rageandqq's answer but I don't yet have enough reputation.) (版主注意:这应该是对 rageandqq 回答的评论,但我还没有足够的声誉。)

I learn something new every day!我每天都学到新东西! Thank you for this.这次真是万分感谢。 I was trying to achieve the same.我试图达到同样的目标。 I was directly doing this: $ListOfGGUIDs = $objects.{Object GUID} Which basically made my variable an object again!我直接这样做了: $ListOfGGUIDs = $objects.{Object GUID}这基本上使我的变量再次成为对象! I later realized I needed to define it first as an empty array, $ListOfGGUIDs = @()后来我意识到我需要先将它定义为一个空数组, $ListOfGGUIDs = @()

Thanks guys, very useful post.谢谢各位,很有用的帖子。

I have another tricky question if anyone can help please.如果有人可以提供帮助,我还有另一个棘手的问题。

I am running a PS command to show some specific values for each environment.我正在运行一个 PS 命令来显示每个环境的一些特定值。

Eg Get-Environment |例如获取环境 | Select-Object DisplayName, Location, EnvironmentType,CreatedTime, createdBy Select-Object DisplayName、Location、EnvironmentType、CreatedTime、createdBy

The "createdBy" returns an array. “createdBy”返回一个数组。

Current Output:当前 Output:

DisplayName: Environment1 Location: unitedstates EnvironmentType: Standard CreatedTime: 2016-10-12T09:19:58.4239413Z CreatedBy: @{id=SYSTEM;显示名称:Environment1 位置:美国 EnvironmentType:标准 CreatedTime:2016-10-12T09:19:58.4239413Z CreatedBy:@{id=SYSTEM; displayName=SYSTEM;显示名称=系统; type=NotSpecified}类型=未指定}

I cannot work out if it would be possible to just display the displayName for the array which gets returned in the output for the "CreatedBy. Can anyone please advise?我无法确定是否可以只显示在 output 中为“CreatedBy”返回的数组的 displayName。有人可以建议吗?

Desired Output:所需的 Output:

DisplayName: Environment1 Location: unitedstates EnvironmentType: Standard CreatedTime: 2016-10-12T09:19:58.4239413Z CreatedBy: SYSTEM显示名称:Environment1 位置:美国 EnvironmentType:标准 CreatedTime:2016-10-12T09:19:58.4239413Z CreatedBy:SYSTEM

Any help is very much appreciated, thank you.非常感谢任何帮助,谢谢。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何按 Powershell 中的一个属性值对对象数组进行排序? - How do I sort an array of objects by one of their property values in Powershell? 检查对象数组中的所有属性值是否相同 - Check if all property values are the same in an Array of Objects 在Powershell中,如何将对象数组的一个属性连接成一个字符串? - In Powershell, how do I concatenate one property of an array of objects into a string? 将具有相同属性的对象数组转换为具有数组值的一个对象 - Convert array of objects with same property to one object with array values Javascript 如何从对象数组中获取属性的所有值? - Javascript How to get all values for a property from an Array of Objects? 对象数组的对象。 获取具有相同属性的所有值的长度 - Object of array of objects. Get the length of all values with the same property 从数组中的所有javascript对象添加特定属性的值 - Adding values of a particular property from all javascript objects in an array 根据对象的所有属性值对对象数组进行排序 - Sorting an array of objects based on all of its property values 如何通过键 select 数组中的所有对象并将它们的值相加? - How to select all objects within an array by their key and add their values together? 在对象数组中的属性中查找具有最大值的所有对象,并从同一对象返回其他属性的值 - Finding all objects with Max value in a property within an Array of Objects and return values of other property from the same object
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM