如何在调用者的上下文中调用脚本块?

How can I invoke a scriptblock in the caller's context?

考虑以下调用站点:

$modifiedLocal = 'original local value'
'input object' | SomeScriptblockInvoker {
    $modifiedLocal = 'modified local value'
    [pscustomobject] @{
        Local = $local
        DollarBar = $_
    }
}
$modifiedLocal

我想实现 SomeScriptblockInvoker 这样

  1. 在模块中定义,
  2. 脚本块在调用者的上下文中被调用。

函数在调用点的输出如下:

Local DollarBar   
----- ---------   
local input object
modified local value

PowerShell 似乎能够做到这一点。例如,将 SomeScriptblockInvoker 替换为 ForEach-Object 会产生所需的输出。

我已经接近使用以下定义:

New-Module m {
    function SomeScriptblockInvoker {
        param
        (
            [Parameter(Position = 1)]
            [scriptblock]
            $Scriptblock,

            [Parameter(ValueFromPipeline)]
            $InputObject
        )
        process
        {
            $InputObject | . $Scriptblock
        }
    }
} |
    Import-Module

使用该定义的调用站点的输出如下:

Local DollarBar
----- ---------
local          
modified local value

请注意 DollarBar 是空的,而它应该是 input object

(gist of Pester tests to check for correct behavior)

一般来说,你不能。脚本块的调用者无法控制与该脚本块关联的 SessionState,并且 SessionState 决定(部分)执行脚本块的上下文(有关详细信息,请参阅范围部分)。根据脚本块的定义位置,其 SessionState 可能与调用者的上下文匹配,但也可能不匹配。

范围

关于执行脚本块的上下文,有两个相关的考虑因素:

  1. 与脚本块关联的 SessionState。
  2. 调用方法是否将范围添加到 SessionState 的范围堆栈。

Here is a good explanation of how this works.

$_ 自动变量

$_ contains the current object in the pipeline。提供给 % 的脚本块与提供给 .& 的脚本块的解释不同:

  • 'input_object' | % {$_} - $_ 的值为 'input_object' 因为脚本块绑定到 %-Process 参数。该脚本块对管道中的每个对象执行一次。
  • 'input_object' | . {process{$_}}'input_object' | & {process{$_}} - $_ 的值为 'input_object' 因为脚本块中的 $_process{} 块内对管道中的每个对象执行一次。

请注意,在使用 . 调用脚本块时,OP $_ 为空。这是因为脚本块不包含 process{} 块。脚本块中的每个语句都是脚本块的 end{} 块的隐含部分。当 end{} 块为 运行 时,管道中不再有任何对象并且 $_ 为空。

. 对比 & 对比 %

.&% 均使用脚本块的 SessionState 调用脚本块,但根据以下 table:

+---+-----------------+-----------+-------------------+----------------------+
|   |      Name       |    Kind   |  interprets {} as |  adds scope to stack |
+---+-----------------+-----------+-------------------+----------------------+
| % |  ForEach-Object |  Command  |  Process block    |  No                  |
| . |  dot-source     |  Operator |  scriptblock      |  No                  |
| & |  call           |  Operator |  scriptblock      |  Yes                 |
+---+-----------------+-----------+-------------------+----------------------+
  • %命令还有对应Begin{}End{}块的其他参数。
  • 对于脚本块进行的变量分配对与脚本块关联的 SessionState 产生副作用,请使用不向堆栈添加作用域的调用方法。否则,变量赋值只会影响新创建的范围,并在脚本块执行完成后消失。

最可行的选择

通过 the tests in OP 最可行的两个选项如下。请注意,两者都不是严格在调用者的上下文中调用脚本块,而是在使用与脚本块关联的 SessionState 的上下文中调用。

选项 1

更改调用站点,使脚本块包含 process{}:

$modifiedLocal = 'original local value'
'input object' | SomeScriptblockInvoker {
    process {
        $modifiedLocal = 'modified local value'
        [pscustomobject] @{
            Local = $local
            DollarBar = $_
        }
    }
}
$modifiedLocal

并在 OP 中使用 SomeScriptblockInvoker 调用脚本块。

选项 2

使用 % 作为 调用脚本块。