Powershell 中的管道 Select-Object 示例Sort-Object 示例

问题描述

我在 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-objectsort-object 会被调用 100 次(每次调用一个对象)吗?而且,这将如何产生我在屏幕上看到的排序结果。

解决方法

Sort-Object(以及其他需要在输出任何内容之前评估所有输入对象的 cmdlet)通过一个一个地收集输入对象来工作,然后在上游之前不做任何实际工作cmdlet(在本例中为 Get-Service)已完成发送输入。

这是如何工作的?好吧,让我们尝试使用 PowerShell 函数重新创建 Sort-Object

为此,我们首先需要了解一个 cmdlet 由 3 个独立的例程组成:

  • Begin - 管道中每个 cmdlet 的 Begin 例程在发生任何其他事情之前调用一次
  • Process - 在每个 cmdlet 上调用此例程每次收到来自上游命令的输入
  • End - 一旦上游命令调用 End 并且没有更多输入项供 Process 处理

(这些是 PowerShell function 定义中使用的块标签名称 - 在二进制 cmdlet 中,您将覆盖 BeginProcessingProcessRecord、{{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