在同一对象上使用 Select-Object 和 foreach 之间的区别

10
有人能帮我理解下面两段代码之间的区别吗?为什么它们的结果不同。在每种情况下,我都选择了相同的属性(名称):
代码1:
$obj = Get-Service | Where-Object {$_.Status -eq "Running"} | foreach-object  {$_.Name} | select -first 3
foreach ( $item in $obj ) { write-output "Name is : $item" }
Output :
Name is : AeLookupSvc
Name is : Appinfo
Name is : AudioEndpointBuilder

代码2:

$obj = Get-Service | Where-Object {$_.Status -eq "Running"} | select -first 3 name
foreach ( $item in $obj ) { write-output "Name is : $item" }
Output :
Name is : @{Name=AeLookupSvc}
Name is : @{Name=Appinfo}
Name is : @{Name=AudioEndpointBuilder}
3个回答

14
Select-Object返回一个自定义对象的数组到管道中;在这种情况下,只有一个属性是字符串。
正如@walidtourni所提到的,使用expand可以解决这个问题。这是因为expand会导致输出成为属性的值,而不是具有该值属性的自定义对象。这样做的原因是expand只接受一个参数;即您不可能尝试为同一“行”返回多个值。
另一方面,foreach-object只是将东西吐出到管道中。如果您尝试包含第二个属性而没有手动将两个属性都包装到自定义对象中,则输出将创建另一行,而不是同一行上的两个属性。
为了演示,请运行以下内容:
Clear
$x = Get-Service | Where-Object {$_.Status -eq "Running"}  | select -first 3 
$x | foreach-object  {$_.Name}  #returns 3 rows, single column; string
$x | foreach-object  {$_.Name;$_.CanPauseAndContinue;} #returns 6 rows, single column; alternate string & boolean
$x | select Name #returns 3 rows, single column (string); custom object
$x | select Name, CanPauseAndContinue #returns 3 rows, two columns (string & boolean); custom property
$x | select -expand Name #returns 3 rows, single column; string; notice the lack of column header showing this is a string, not a string property of a custom object
$x | select -expand Name,CanPauseAndContinue #error; -expand can only take single valued arguments
$x | select -expand Name -expand CanPauseAndContinue #error; you can't try to define the same paramenter twice

1
非常感谢您的解释,确实帮了我很多。 - Subhayan Bhattacharya
1
@SubhayanBhattacharya 最好的方式是将John的答案标记为正确答案,以表达您的感谢之意。 - Matt

3

为了达到类似的结果,您可以将第二个示例更改为以下内容:

select -first 3 -expand name

Select-object选择属性对象


2
即使这样做可以使两段代码片段的功能相同,但这并不能解释OP所问的区别。请提供更详细的答案。 - Matt

2
这是一篇旧帖子,但我想扩展一下@JohnLBevan的答案。 Select-Object 的正常操作是创建一个 PSCustomObject,并复制原始对象的属性。
Get-Process
Get-Process | Select-Object Name,CPU

如果仅指定一个属性,则会创建一个仅有一个属性的对象(例如Get-Process | Select-Object Name),这就是你在@{Name=AeLookupSvc}等中看到的内容。请注意,这与属性的值严格不同,它具有支持获取的参数:
Get-Process | Select-Object -ExpandProperty Name

另一方面,ForEach-Object 更像是针对每个管道对象执行的循环体。这很容易用于检索要传递的属性值,甚至还带有一个方便的速记方式来实现这一点。
Get-Process | ForEach-Object Name

因此,为了真正补充其他答案,我进行了一些探索,以了解性能差异:
# Benchmark
1..100 | ForEach-Object {Measure-Command {Get-WinEvent -LogName System -ErrorAction SilentlyContinue} | Select-Object -ExpandProperty TotalMinutes} | Measure-Object -Average
# Average: 2.01325857505 Minutes

# Select-Object -ExpandProperty
1..100 | ForEach-Object {Measure-Command {Get-WinEvent -LogName System -ErrorAction SilentlyContinue | Select-Object -ExpandProperty LevelDisplayName -ErrorAction SilentlyContinue} | Select-Object -ExpandProperty TotalMinutes} | Measure-Object -Average
# Average: 2.87394915385 Minutes
# note the `Select-Object -ErrorAction SilentlyContinue` because not all pipeline records are accepted by Select-Object (strange, but I did not explore why)

# ForEach-Object
1..100 | ForEach-Object {Measure-Command {Get-WinEvent -LogName System -ErrorAction SilentlyContinue | ForEach-Object LevelDisplayName} | Select-Object -ExpandProperty TotalMinutes} | Measure-Object -Average
# Average: 3.10188571238333 Minutes

请注意,我在 PowerShell 5.1 上并行运行了这些操作(在不同的 PowerShell 窗口中)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接