在PowerShell中选择数组的所有对象上一个属性的值


133

假设我们有一个对象数组$ objects。假设这些对象具有“名称”属性。

这就是我想做的

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

这行得通,但是可以用更好的方法做到吗?

如果我做类似的事情:

 $results = objects | select Name

$results是具有Name属性的对象数组。我希望$ results包含一个Names数组。

有没有更好的办法?


4
为了完整起见,您还可以从原始代码中删除“ + =”,以便foreach仅选择Name :$results = @($objects | %{ $_.Name })。尽管我认为Scott的答案通常更好,但有时在命令行中键入更为方便。
XLII皇帝

1
@EmperorXLII:好点,在PSv3 +中,您甚至可以简化为:$objects | % Name
mklement0

Answers:


211

我认为您也许可以使用的ExpandProperty参数Select-Object

例如,要获取当前目录的列表并仅显示Name属性,可以执行以下操作:

ls | select -Property Name

这仍然返回DirectoryInfo或FileInfo对象。您始终可以通过管道传递到Get-Member(别名gm)来检查通过管道的类型。

ls | select -Property Name | gm

因此,要将对象扩展为要查看的属性类型,可以执行以下操作:

ls | select -ExpandProperty Name

就您而言,您可以执行以下操作以使变量为字符串数组,其中字符串为Name属性:

$objects = ls | select -ExpandProperty Name

73

作为一个更简单的解决方案,您可以使用:

$results = $objects.Name

其中应填充$results元素中所有“名称”属性值的数组$objects


请注意,这不适用于Exchange Management Shell。当使用Exchange我们需要使用$objects | select -Property Propname, OtherPropname
Bassie

2
@Bassie:在集合级别访问属性以获取其成员值作为数组称为成员枚举,并且是PSv3 +的功能;大概您的Exchange命令行管理程序是PSv2。
mklement0 '18

32

为了补充先前存在的有用答案,并提供有关何时使用哪种方法以及性能比较的指南

  • 管道外部,使用(PSv3 +):

    $ objects 名称
    rageandqq的答案所示,它在语法上更加简单而且更快

    • 集合级别访问属性以获取其成员值作为数组称为成员枚举,这是PSv3 +的功能。
    • 或者,在PSv2中,使用foreach 语句,您也可以将其输出直接分配给变量:
      $ results = foreach($ objects中的$ obj){$ obj.Name}
    • 权衡
      • 无论是输入收集和输出数组 必须适合存储器作为一个整体
      • 如果输入集合本身是命令(管道)(例如(Get-ChildItem).Name)的结果,则该命令必须首先运行完成,然后才能访问所得数组的元素。
  • 必须对结果进行进一步处理或结果无法整体存储管道中,请使用:

    $ objects | 选择对象-扩展属性名称

    • Scott Saad的答案-ExpandProperty解释了对的需求。
    • 您将获得通常的一对一处理流水线优势,该处理通常立即产生输出并保持内存使用不变(除非您最终还是将结果收集到内存中)。
    • 权衡
      • 管道的使用相对较慢

对于较小的输入集合(数组),您可能不会注意到其中的区别,尤其是在命令行上,有时能够轻松键入命令更为重要。


这是一种易于键入的替代方法,但是这是最慢的方法。它使用称为操作语句的简化ForEach-Object语法(再次是PSv3 +):例如,以下PSv3 +解决方案很容易附加到现有命令中:

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

为了完整起见:本文中讨论的鲜为人知的PSv4 + .ForEach() 阵列方法另一种替代方法

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

# By script block (more flexibility; like ForEach-Object)
$objects.ForEach({ $_.Name })
  • 这种方法与成员枚举类似,但有相同的取舍,只是应用管道逻辑。它或多或少地更慢,但仍明显比管道更快。

  • 为了通过名称字符串参数)提取单个属性值,此解决方案与成员枚举相当(尽管后者在语法上更简单)。

  • 脚本块变体,允许任意变换 ; 它是基于管道的ForEach-Object cmdlet%)的一次更快的全内存替代方法


比较各种方法的性能

这是基于输入的对象集合的各种方法的采样时间,这些10,000对象是 10次​​运行的平均值。绝对数字并不重要,并且会因许多因素而有所不同,但这绝对会让您有相对的性能(计时来自单核Windows 10 VM:

重要

  • 相对性能取决于输入对象是常规.NET类型的实例(例如,由输出Get-ChildItem)还是[pscustomobject]实例(例如,由输出Convert-FromCsv)。
    原因是[pscustomobject]属性由PowerShell动态管理,并且与(静态定义的)常规.NET类型的常规属性相比,它可以更快地访问它们。两种情况都在下面介绍。

  • 这些测试使用内存已满的集合作为输入,从而专注于纯属性提取性能。使用流cmdlet /函数调用作为输入时,性能差异通常不会那么明显,因为在该调用中花费的时间可能占花费的大部分时间。

  • 为简便起见,别名%用于ForEach-Objectcmdlet。

一般结论,适用于常规.NET类型和[pscustomobject]输入:

  • 到目前为止,成员枚举($collection.Name)和foreach ($obj in $collection)解决方案是最快的,比最快的基于管道的解决方案快十倍甚至更多。

  • 令人惊讶的是,% Name它的性能要差得多% { $_.Name }-请参阅此GitHub问题

  • 在这里,PowerShell Core始终优于Windows Powershell。

计时与普通.NET类型

  • PowerShell Core 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
Comparing property-value extraction methods with 10000 input objects, averaged over 10 runs...

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

结论:

  • 在PowerShell Core中,其.ForEach('Name')性能明显优于.ForEach({ $_.Name })。奇怪的是,在Windows PowerShell中,后者速度更快,尽管速度虽然很小。

[pscustomobject]实例的时间

  • PowerShell Core 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
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

结论:

  • 请注意,[pscustomobject]输入.ForEach('Name')远远优于基于脚本块的变体.ForEach({ $_.Name })

  • 同样,在Windows PowerShell中,[pscustomobject]输入Select-Object -ExpandProperty Name实际上使基于管道的速度更快.ForEach({ $_.Name }),但实际上在PowerShell Core中仍然慢了约50%。

  • 简而言之:奇怪的例外是% Name,使用[pscustomobject]基于字符串的引用属性的方法要优于基于脚本块的方法。


测试的源代码

注意:

  • Time-Command这个要点下载功能来运行这些测试。

  • 设置$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 home dir. tree
  #       may be less than $count
  $objects = Get-ChildItem -Recurse $HOME | 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

注意,成员枚举仅在集合本身不具有相同名称的成员时才有效。因此,如果您有一个FileInfo对象数组,则无法使用来获得文件长度数组

 $files.length # evaluates to array length

在您说“显然很好”之前,请考虑一下。如果您有一个具有Capacity属性的对象数组,则

 $objarr.capacity

可以正常工作,除非 $ objarr实际上不是[Array],而是例如[ArrayList]。因此,在使用成员枚举之前,您可能必须先查看包含您的集合的黑匣子。

(主持人注意:这应该是对rageandqq的回答的评论,但我还没有足够的声誉。)


这是一个好点;这个GitHub功能请求要求为成员枚举使用单独的语法。名称冲突的解决方法是使用.ForEach()数组方法,如下所示:$files.ForEach('Length')
mklement0
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.