Select-Object -First 影响管道中的先前 cmdlet [英] Select-Object -First affects prior cmdlet in the pipeline

查看:94
本文介绍了Select-Object -First 影响管道中的先前 cmdlet的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

PowerShell 强烈鼓励开发指南 cmdlet 应该管道中间的实现 但我怀疑这对于作为 -最后 选择对象.仅仅是因为您无法预先确定最后一个条目.换句话说:您需要等待输入流完成,直到您定义最后一个条目.
为了证明这一点,我写了一个小脚本:

$Data = 1..5 |ForEach-Object {[pscustomobject]@{Index = "$_"}}$数据 |ForEach-Object { Write-Host 'Before' $_.Index;$_ } |选择对象 - 最后 5 |ForEach-Object { Write-Host 'After' $_.Index }

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

$Data |ForEach-Object { Write-Host 'Before' $_.Index;$_ } |选择对象 * |ForEach-Object { Write-Host 'After' $_.Index }

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

-最后 5 *------- -前 1 前 1前 2 后 1前 3 前 2前 4 后 2前 5 前 3后 1 后 3后 2 前 43 后 4 后后 4 前 55 后 5 后

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

$Data |ForEach-Object { Write-Host 'Before' $_.Index;$_ } |选择对象 -First 2 |ForEach-Object { Write-Host 'After' $_.Index }前 11 后前22后

请注意,使用 -First 2 参数后,不仅下面的 cmdlet 显示两个对象,而且前面的 cmdlet 只显示两个对象.

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

$Data |ForEach-Object { Write-Host 'Before' $_.Index;$_ } |选择对象 - 最后 2 |ForEach-Object { Write-Host 'After' $_.Index }前 1前2前 3前 45 之前4 后5 后

使用 Out-Host 而不是 Write-Host cmdlet 或将结果发送到变量,例如:

$Before = "";$After = ""$数据 |ForEach-Object { $Before += $_.Index;$_ } |选择对象 -First 2 |ForEach-Object { $After += $_.Index }$之前$之后

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

解决方案

Select-Object 通过欺骗

影响上游命令

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

为了优化流水线流性能,Select-Object 使用了一个普通用户无法使用的技巧来开发 Cmdlet - 它会抛出一个 StopProcessing() 在所有前面的命令上,但将其视为终止错误事件,从而允许下游 cmdlet 继续执行.>

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

# 这将只需要约 3 秒即可返回 StopUpstreamCommand 行为# 但否则会产生 8 秒的等待丢弃"额外时间测量命令{1..5 |ForEach-Object { 开始睡眠 - 秒 1;$_ } |选择对象 - 前 3}

The 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. Simply because you can't determine the last entry upfront. In other words: you will need to wait for the input stream to finish until you define the last entry.
To prove this, I wrote a little script:

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

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

and compared this to Select-Object *:

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

With results (right: Select-Object -Last 5, left: 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

Despite this isn't documented I think that I can conclude from this that the -Last parameter indeed chokes the pipeline.
This is not a big deal, but I also tested it against the -First parameter and got some disturbing results. To better show this, I am not selecting all the objects but just the **-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

Note that with the -First 2 parameter not only the following cmdlet shows two objects but also the preceding cmdlet shows only two objects.

Apparently, the -First parameter references directly into the object of the prior cmdlet which is different then e.g. using the -Last 2 parameter:

$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

This also happens when using the Out-Host instead of the Write-Host cmdlet or sending the results to a variable, like:

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

This shows on both Windows Powershell (5.1.18362.628) and PowerShell Core (7.0.0).
Is this a bug?

解决方案

Select-Object affects the upstream commands by cheating

That might sound like a joke, but it's not.

To optimize pipeline streaming performance, Select-Object uses a trick not available to a regular user developing a Cmdlet - it throws a StopUpstreamCommandsException.

Once caught, the runtime (indirectly) calls StopProcessing() on all the preceding commands, but does not treat it as a terminating error event, allowing the downstream cmdlets to continue executing.

This is extremely useful when you have slow or computationally heavy command early in a pipeline:

# 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
}

这篇关于Select-Object -First 影响管道中的先前 cmdlet的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆