[英]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-Object
的ExpandProperty
参数。
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 :权衡:
(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 名称
-ExpandProperty
is explained in Scott Saad's answer (you need it to get only the property value ). Scott Saad 的回答中解释了-ExpandProperty
的需要(您需要它来仅获取属性值)。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-Object
、 ForEach-Object
和Where-Object
原样返回单个输出对象,而不将其包装在集合(数组)中。
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 类型的时间:
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
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:结论:
.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]
实例的时间:
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
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.