7

使用以下代码,$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 中看到了相同的结果。

4

1 回答 1

9

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

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

考虑在管道期间,每个函数或 cmdlet 都可以使用BeginProcessEnd块。

所有Begin块首先运行,然后Process为每个管道对象调用一次,然后是所有Ends.

变量赋值必须发生在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

对比

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
}

对比

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

根据您链接的文档,您可以使用该-Wait参数来关闭此优化:

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

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

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

-OutVariable是一些 cmdlet 实现的东西,并且很可能必须在End该特定 cmdlet 的块中实现,因此预测它将提供什么高度依赖于理解管道的执行流程。

因此,要回答您评论中的问题,在我看来,它的实施是正确的。我误解了你的说法吗?

于 2016-02-26T17:33:33.267 回答