当“Select-Object -First”出现时,如何解释不工作“Tee-Object”

时间:2016-02-26 17:20:31

标签: powershell powershell-v3.0 pipeline powershell-v5.0

使用以下代码,$t等于@(1,2)

$t = "before"
1..2 | Tee-Object -Variable t

那么为什么下一个代码段的$t等于"before"而不是@(1) *?

$t = "before"
1..2 | Tee-Object -Variable t | Select-Object -First 1

我在Powershell版本5和版本3中看到相同的结果。

1 个答案:

答案 0 :(得分:9)

这与管道的工作方式有关。如果您使用其他cmdlet,例如Write-Verbose,它将按预期工作:

$t = "before"
1..2 | Tee-Object -Variable t | Write-Verbose -Verbose

请注意,在管道中,每个函数或cmdlet都可以使用BeginProcessEnd块。

首先运行所有Begin块,然后为每个管道对象调用Process一次,然后调用所有End s。

变量赋值必须在End块中进行。

从PowerShell v3开始,cmdlet可以中断管道。

这对于像Select-Object -First 1这样的东西很有用,因为这意味着整个管道可以在第一个对象之后停止,而不是为每个项目执行一次,即使其余部分将被丢弃。

但这也意味着End块永远不会运行。

如果您在v2中启动PowerShell:powershell.exe -Version 2.0

然后运行你的第二个例子,它将按预期工作,因为管道在该版本中不能过早停止。

这是一个示范:

function F1 {
[CmdletBinding()]
param(
    [Parameter(ValueFromPipeline)]
    $o
)

    Begin {
        Write-Verbose "Begin" -Verbose
    }

    Process {
        Write-Verbose $o -Verbose
        $o
    }

    End {
        Write-Verbose "End" -Verbose
    }
}

然后叫它:

1..2 | F1

VS

1..2 | F1 | Select-Object -First 1

您还可以使用ForEach-Object来证明这一点:

1..2 | ForEach-Object -Begin {
    Write-Verbose "Begin" -Verbose
} -Process {
    Write-Verbose $_ -Verbose
    $_
} -End {
    Write-Verbose "End" -Verbose
}

VS

1..2 | ForEach-Object -Begin {
    Write-Verbose "Begin" -Verbose
} -Process {
    Write-Verbose $_ -Verbose
    $_
} -End {
    Write-Verbose "End" -Verbose
} | Select-Object -First 1

根据the documentation you linked,您可以使用-Wait参数关闭此优化:

$t = "before"
1..2 | Tee-Object -Variable t | Select-Object -First 1 -Wait

这将填充$t,但可能不会填充您想要的值。它将包含@(1,2),大概是因为-OutVariable放在Tee-Object而不是Select-Object

请记住"结果"管道是从执行中返回的(=的左侧),这在所有情况下都是正确的。

-OutVariable是由某些cmdlet实现的,并且很可能必须在该特定cmdlet的End块中实现,因此预测它将给出的内容高度依赖于理解执行管道的流动。

因此,为了回答您的评论中的问题,我觉得可以正确实施。我误解了你的断言吗?