Select-Object -First 影响管道中的先前 cmdlet

Select-Object -First affects prior cmdlet in the pipeline

PowerShell Strongly Encouraged Development Guidelines that cmdlets should Implement for the Middle of a Pipeline but I suspect that isn't doable for a parameter as -Last for the Select-Object。仅仅是因为您无法预先确定最后一个条目。换句话说:您需要等待输入流完成,直到您定义最后一个条目。
为了证明这一点,我写了一个小脚本:

$Data = 1..5 | ForEach-Object {[pscustomobject]@{Index = "$_"}}

$Data | ForEach-Object { Write-Host 'Before' $_.Index; $_ } |
Select-Object -Last 5 | ForEach-Object { Write-Host 'After' $_.Index }

并将其与 Select-Object * 进行比较:

$Data | ForEach-Object { Write-Host 'Before' $_.Index; $_ } |
Select-Object * | ForEach-Object { Write-Host 'After' $_.Index }

有结果(右:Select-Object -Last 5,左:Select-Object *):

-Last 5  *
-------  -
Before 1 Before 1
Before 2 After 1
Before 3 Before 2
Before 4 After 2
Before 5 Before 3
After 1  After 3
After 2  Before 4
After 3  After 4
After 4  Before 5
After 5  After 5

尽管没有记录,但我认为我可以从中得出结论,-Last 参数确实阻塞了管道。
这没什么大不了的,但我还针对 -First 参数对其进行了测试,并得到了一些令人不安的结果。为了更好地展示这一点,我没有选择所有对象,而是只选择了 **-First 2**:

$Data | ForEach-Object { Write-Host 'Before' $_.Index; $_ } |
Select-Object -First 2 | ForEach-Object { Write-Host 'After' $_.Index }

Before 1
After 1
Before 2
After 2

请注意,使用 -First 2 参数后,不仅以下 cmdlet 显示了两个对象,前面的 cmdlet (ForEach-Object { Write-Host 'Before' $_.Index; $_ }) 也仅显示了 2 个对象(而不是 5 个).

显然,-First 参数直接引用到先前 cmdlet 的对象中,该对象不同于例如使用 -Last 2 参数:

$Data | ForEach-Object { Write-Host 'Before' $_.Index; $_ } |
Select-Object -Last 2 | ForEach-Object { Write-Host 'After' $_.Index }

Before 1
Before 2
Before 3
Before 4
Before 5
After 4
After 5

使用 Out-Host instead of the Write-Host cmdlet 或将结果发送到变量时也会发生这种情况,例如:

$Before = ""; $After = ""
$Data | ForEach-Object { $Before += $_.Index; $_ } | Select-Object -First 2 | ForEach-Object { $After += $_.Index }
$Before
$After

这显示在 Windows Powershell (5.1.18362.628) 和 PowerShell Core (7.0.0) 上。
这是一个错误吗?

Select-Object通过作弊

影响上游命令

这听起来像是个笑话,但事实并非如此。

为了优化管道流性能,Select-Object 使用了普通用户无法使用的技巧来开发 Cmdlet - 它会抛出 StopUpstreamCommandsException.

一旦被捕获,运行时(间接)对所有前面的命令调用 StopProcessing(),但 not 将其视为终止错误事件,允许下游 cmdlet继续执行。

当您在管道早期有缓慢或计算量大的命令时,这非常有用:

# this will only take ~3 seconds to return with the StopUpstreamCommand behavior
# but would have incurred 8 extra seconds of "waiting to discard" otherwise
Measure-Command {
  1..5 |ForEach-Object { Start-Sleep -Seconds 1; $_ } |Select-Object -First 3
}