问题描述
我在 about_Pipelines 阅读了有关管道在 PowerShell 中的工作原理的文章,并了解到管道一次传送一个对象。
所以,这个
Get-Service | Format-Table -Property Name,DependentServices
与此不同
Format-Table -InputObject (Get-Service) -Property Name,DependentServices
因此,按照解释,在第一种情况下,Format-Table
一次作用于一个对象,而在第二个示例中,Format-Table
作用于一组对象。如果我错了,请纠正我。
如果是这种情况,那么我想知道 sort-object
和其他需要处理数据集合的 cmdlet 是如何处理管道字符的。
当我这样做时:
Get-Service | sort-object
如果 sort-object
一次只处理一个对象,它如何能够排序。因此,假设有 100 个服务对象要传递给 sort-object
。 sort-object
会被调用 100 次(每次调用一个对象)吗?而且,这将如何产生我在屏幕上看到的排序结果。
解决方法
Sort-Object
(以及其他需要在输出任何内容之前评估所有输入对象的 cmdlet)通过一个一个地收集输入对象来工作,然后在上游之前不做任何实际工作cmdlet(在本例中为 Get-Service
)已完成发送输入。
这是如何工作的?好吧,让我们尝试使用 PowerShell 函数重新创建 Sort-Object
。
为此,我们首先需要了解一个 cmdlet 由 3 个独立的例程组成:
-
Begin
- 管道中每个 cmdlet 的Begin
例程在发生任何其他事情之前调用一次 -
Process
- 在每个 cmdlet 上调用此例程每次收到来自上游命令的输入 -
End
- 一旦上游命令调用End
并且没有更多输入项供Process
处理
(这些是 PowerShell function 定义中使用的块标签名称 - 在二进制 cmdlet 中,您将覆盖 BeginProcessing
、ProcessRecord
、{{3} } cmdlet 的方法)
因此,要“收集”每个输入项,我们需要在命令的 Process
块中添加一些逻辑,然后我们可以将操作所有项的代码放在 {{1} } 块:
End
如果我们用 function Sort-ObjectCustom
{
param(
[Parameter(Mandatory,ValueFromPipeline)]
[object[]]$InputObject
)
begin {
# Let's use the `begin` block to create a list that'll hold all the input items
$list = [System.Collections.Generic.List[object]]::new()
Write-Verbose "Begin was called"
}
process {
# Here we simply collect all input to our list
$list.AddRange($InputObject)
Write-Verbose "Process was called [InputObject: $InputObject]"
}
end {
# The `end` block is only ever called _after_ we've collected all input
# Now we can safely sort it
$list.Sort()
Write-Verbose "End was called"
# and output the results
return $list
}
}
调用我们的新命令,我们将看到输入是如何一一收集的:
-Verbose
有关如何为二进制 cmdlet 实现管道输入处理例程的详细信息,请参阅 EndProcessing
。
有关如何在函数中利用相同管道语义的更多信息,请参阅 "How to Override Input Processing"
,为了补充来自 Mathias 的答案,您实际上可以使用 Write-Host
cmdlet 从现有 cmdlet 中可视化进程的顺序,该 cmdlet 立即将输出写入显示器(而不是管道):
$Data = ConvertFrom-Csv @'
Id,Name
4,Four
2,Two
3,Three
1,One
'@
Select-Object
示例
$Data |
Foreach-Object { Write-Host 'in:' ($_ |ConvertTo-Json -Compress); $_ } |
Select-Object * |
Foreach-Object { Write-Host 'out:' ($_ |ConvertTo-Json -Compress); $_ }
节目:
in: {"Id":"4","Name":"Four"}
out: {"Id":"4","Name":"Four"}
in: {"Id":"2","Name":"Two"}
out: {"Id":"2","Name":"Two"}
in: {"Id":"3","Name":"Three"}
out: {"Id":"3","Name":"Three"}
in: {"Id":"1","Name":"One"}
out: {"Id":"1","Name":"One"}
Id Name
-- ----
4 Four
2 Two
3 Three
1 One
Sort-Object
示例
$Data |
Foreach-Object { Write-Host 'in:' ($_ |ConvertTo-Json -Compress); $_ } |
Sort-Object * |
Foreach-Object { Write-Host 'out:' ($_ |ConvertTo-Json -Compress); $_ }
节目:
in: {"Id":"4","Name":"Four"}
in: {"Id":"2","Name":"One"}
out: {"Id":"2","Name":"Two"}
out: {"Id":"3","Name":"Three"}
out: {"Id":"4","Name":"Four"}
Id Name
-- ----
1 One
2 Two
3 Three
4 Four
一般来说,PowerShell cmdlet Write Single Records to the Pipeline 在可能的情况下(此鼓励指南的优点之一是它减少了内存消耗)。正如您的问题所暗示的那样, Sort-Object
不能这样做,因为最后一条记录可能出现在第一条记录之前。但也有例外,在技术上可以根据鼓励的准则编写单个记录,但事实并非如此。参见例如:#11221
Select-Object -Unique is unnecessary slow and exhaustive