Powershell 中的管道

Pipeline in Powershell

我在 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(以及其他需要在输出任何内容之前评估 all 输入对象的 cmdlet)通过一个接一个地收集输入对象,然后不执行任何操作来工作实际工作,直到上游 cmdlet(Get-Service 在本例中)完成发送输入。

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

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

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

(这些是 PowerShell function 定义中使用的块标签名称 - 在二进制 cmdlet 中,您将覆盖 cmdlet 的 BeginProcessing, ProcessRecord, EndProcessing 方法的实现)

因此,要“收集”每个输入项,我们需要在命令的 Process 块中添加一些逻辑,然后我们可以将对所有项进行操作的代码放在 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 调用我们的新命令,我们将看到输入是如何一一收集的:

PS ~> 10..1 |Sort-ObjectCustom -Verbose
VERBOSE: Begin was called
VERBOSE: Process was called [InputObject: 10]
VERBOSE: Process was called [InputObject: 9]
VERBOSE: Process was called [InputObject: 8]
VERBOSE: Process was called [InputObject: 7]
VERBOSE: Process was called [InputObject: 6]
VERBOSE: Process was called [InputObject: 5]
VERBOSE: Process was called [InputObject: 4]
VERBOSE: Process was called [InputObject: 3]
VERBOSE: Process was called [InputObject: 2]
VERBOSE: Process was called [InputObject: 1]
VERBOSE: End was called
1
2
3
4
5
6
7
8
9
10

有关如何为二进制 cmdlet 实施管道输入处理例程的详细信息,请参阅 "How to Override Input Processing"

有关如何在函数中利用相同管道语义的更多信息,请参阅 about_Functions_Advanced_Methods and related help topics

为了补充 Mathias, you can actually visualize the order of the process from an existing cmdlet using the Write-Host 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":"Two"}
in: {"Id":"3","Name":"Three"}
in: {"Id":"1","Name":"One"}
out: {"Id":"1","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 cmdlets Write Single Records to the Pipeline where it is possible (one of the advantages of this encouraged guideline is that it reduces memory consumption). As implied by your question, Sort-Object can't do this because the last record might possibly come before the first record. But there are also exceptions where it would be technically possible to write single records according the encouraged guideline, but it is not. See e.g.: #11221 Select-Object -Unique is unnecessary slow and exhaustive