【问题标题】:PowerShell: Invoking a script block that contains underscore variablePowerShell:调用包含下划线变量的脚本块
【发布时间】:2016-04-11 02:02:50
【问题描述】:

我通常执行以下操作来调用包含$_ 的脚本块:

$scriptBlock = { $_ <# do something with $_ here #> }
$theArg | ForEach-Object $scriptBlock

实际上,我正在创建一个管道,它将赋予 $_ 其值(在 Foreach-Object 函数调用中)。

但是,在查看LINQ 模块的源代码时,它定义并使用以下函数来调用委托:

# It is actually surprisingly difficult to write a function (in a module)
# that uses $_ in scriptblocks that it takes as parameters. This is a strange
# issue with scoping that seems to only matter when the function is a part
# of a module which has an isolated scope.
# 
# In the case of this code:
# 1..10 | Add-Ten { $_ + 10 }
#
# ... the function Add-Ten must jump through hoops in order to invoke the
# supplied scriptblock in such a way that $_ represents the current item
# in the pipeline.
#
# Which brings me to Invoke-ScriptBlock.
# This function takes a ScriptBlock as a parameter, and an object that will
# be supplied to the $_ variable. Since the $_ may already be defined in
# this scope, we need to store the old value, and restore it when we are done.
# Unfortunately this can only be done (to my knowledge) by hitting the
# internal api's with reflection. Not only is this an issue for performance,
# it is also fragile. Fortunately this appears to still work in PowerShell
# version 2 through 3 beta.
function Invoke-ScriptBlock {
[CmdletBinding()]

    param (
        [Parameter(Position=1,Mandatory=$true)]
        [ScriptBlock]$ScriptBlock,

        [Parameter(ValueFromPipeline=$true)]
        [Object]$InputObject
    )

    begin {
            # equivalent to calling $ScriptBlock.SessionState property:
            $SessionStateProperty = [ScriptBlock].GetProperty('SessionState',([System.Reflection.BindingFlags]'NonPublic,Instance'))
            $SessionState = $SessionStateProperty.GetValue($ScriptBlock, $null)
        }
    }
    process {
        $NewUnderBar = $InputObject
        $OldUnderBar = $SessionState.PSVariable.GetValue('_')
        try {
            $SessionState.PSVariable.Set('_', $NewUnderBar)
            $SessionState.InvokeCommand.InvokeScript($SessionState, $ScriptBlock, @())
        }
        finally {
            $SessionState.PSVariable.Set('_', $OldUnderBar)
        }
    }
}

这让我觉得有点低级。有推荐的、安全的方法吗?

【问题讨论】:

  • 另一种方法是动态添加一个参数块:$ScriptBlock = [scriptblock]::Create("param(`$_)$ScriptBlock")
  • Mathias 的评论应该是一个答案。这正是我的处理方式。
  • 这与$ScriptBlock.InvokeWithContext($null, (New-Object PSVariable '_',$value), $null) 有何不同——只是问问。

标签: powershell


【解决方案1】:

您可以使用与号调用脚本块。无需使用 Foreach-Object。

$scriptblock = {## whatever}
& $scriptblock

@(1,2,3) | % { & {write-host $_}}

传递参数:

$scriptblock = {write-host $args[0]}
& $scriptblock 'test'

$scriptBlock = {param($NamedParam) write-host $NamedParam}
& $scriptBlock -NamedParam 'test'

如果您要在 Invoke-Command 中使用它,您也可以使用 $using 构造。

$test = 'test'
$scriptblock = {write-host $using:test}

【讨论】:

  • 请再次阅读问题。我已经知道了。我说的是包含$_ 变量的特殊脚本块。
  • 我确实阅读了您的问题并对其进行了测试。不管包含管道变量的脚本块是什么,它的工作原理都是一样的。我已经更新了答案。
  • 如果您必须在模块之间跳转,它将无法正常工作。 link
  • 在我的电脑上,&amp; { $_ } 10 只返回 &amp; { param($x) $x } 10 正确返回值 10。我在询问是否建议调用管道变量 ($_) 脚本块。跨度>
  • 我明白了。您是在询问将参数传递到脚本块中吗?那是完全不同的故事。无需基于您对术语的误解而对我的回答投反对票。我已经更新了我的答案。
猜你喜欢
  • 2017-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-19
相关资源
最近更新 更多