访问 PowerShell 中的父 scriptblock 作用域

access parent scriptblock scope in powershell

提问人:Andrea Bardelli 提问时间:7/26/2023 最后编辑:Andrea Bardelli 更新时间:7/26/2023 访问量:54

问:

作为学习其基础知识的一部分,我正在 pws 中实现三元运算符 cmdlet。 我让它采用脚本块来模拟条件评估三元运算符通常具有的条件评估。 在大多数情况下,它工作正常。

function valOf($var){
    if($var -is [scriptblock]){
        return & $var   }
    else {
        return $var    }
}

function ternary([bool]$condition, $t, $f){
    if($condition){
        return valOf($t)    }
    else{
        return valOf($f)    }
    #return @($t,$f)[!$condition]
}

当我开始嵌套脚本块时,我遇到了麻烦:

$i=56;
&{
   $i=0
   ternary($true) {$script:i+=2} {write-host "onFalse"}
   $i #wanted:2 #reality: 58
   <# without '$script: $i' is indeed 0, but cannot be edited #>
}      
$i #wanted:56 #reality:58

如何访问中间范围?

浏览文档和论坛 这似乎是一个很常见的问题,但主题并不明确 x.x
也许是一个从 copyOnWrite 行为中选择退出的 invokeCommand..?

PowerShell 范围 脚本块

评论

3赞 Santiago Squarzon 7/26/2023
内在的就是外在的$iGet-Variable i -Scope 0Get-Variable i -Scope 1

答:

2赞 Santiago Squarzon 7/26/2023 #1

假设您的函数是高级函数,例如(注意装饰):ternary[cmdletbinding()]

function ternary {
    [CmdletBinding()]
    param(
        [bool] $condition,
        [scriptblock] $ifTrue,
        [scriptblock] $ifFalse
    )

    if ($condition) {
        return & $ifTrue
    }

    & $ifFalse
}

然后,可以利用$PSCmdlet获取和更新内部作用域中的值:$i

$i = 56

& {
    $i = 0
    ternary $true { $PSCmdlet.SessionState.PSVariable.Get('i').Value += 2 } { Write-Host 'onFalse' }
    $i # 2
}

$i # 56

如果 不高级,则可以将 Get-Variable 定位范围用于此答案中的函数:ternary2

ternary $true { (Get-Variable i -Scope 2).Value += 2 } { Write-Host 'onFalse' }

对于问题中的函数,您需要使用作用域,因为脚本块是通过添加到作用域来传递和执行的。3valOf+1

就个人而言,我会将您的功能更改为:

function ternary([bool] $condition, $t, $f) {
    if ($condition) {
        if ($t -is [scriptblock]) {
            return & $t
        }

        return $t
    }

    if ($f -is [scriptblock]) {
        return & $f
    }

    $f
}

然后,您可以毫无问题地使用。-Scope 2

在这两种情况下,无论是高级还是非高级,使用$ExecutionContext也应该有效:

ternary $true { $ExecutionContext.SessionState.PSVariable.Get('i').Value += 2 } { Write-Host 'onFalse' }

评论

0赞 Andrea Bardelli 7/26/2023
我尝试使用 -scope 0、1 和 2,它们都给了我错误“找不到名称为'i'的变量”
1赞 Santiago Squarzon 7/26/2023
@AndreaBardelli呵呵,用你的函数,那么作用域是,因为正在传递对象,然后执行脚本块,将 +1 添加到作用域。相反,你应该做的是检查脚本块本身是否和是脚本块,或者至少在其中执行它们3ternaryvalOf$f$tternary
0赞 Santiago Squarzon 7/26/2023
@AndreaBardelli我更新以澄清这一点。希望为什么需要您的函数范围是有道理的3
0赞 Andrea Bardelli 7/26/2023
首先是答案。最重要的是不必使用冗长的语法,因为三元运算符的主要目的是简洁。但对于所有其他目的来说,这很好
2赞 mklement0 7/26/2023 #2

Santiago 的有用答案的替代方案,即无需在传递给三元函数的脚本块参数中使用特殊语法:

您可以将动态模块点源相结合:

注意:为简洁起见:

  • 下面的函数不处理参数不是脚本块的情况,但这很容易添加。ternary

  • 省略该属性;严格来说,你并不需要一个高级函数来使解决方案工作,尽管这当然是可取的,并且添加类似的东西会隐式地使你的函数成为一个高级函数。[CmdletBinding()][Parameter(Mandatory)]

# Create (and implicitly import) a dynamic module that
# hosts the ternary function.
$null = New-Module {
  # Define the ternary function.
  function ternary {
    param(
      [bool] $Condition,
      [scriptblock] $trueBlock,
      [scriptblock] $falseBlock
    )
    # Dot-source the appropriate script block,
    # which runs directly in the *caller's* scope,
    # given that's where it was created and given that the
    # module's code runs in a separate scope domain ("session state")
    if ($Condition) { . $trueBlock } else { . $falseBlock }
  }
}

$i=56;
& {
   $i=0
   # Now you can use $i as-is
   # in order to refer to the current scope's $i.
   ternary $true { $i+=2 } {write-host "onFalse"}
   $i # -> 2 
}
$i # -> 56

注意:

  • 虽然上面使用了动态模块,但该技术同样适用于持久化模块(*.psm1)

  • 此答案提供了 PowerShell 中作用域的全面概述,包括模块的单独作用域(树),不幸的是,在官方文档中称为会话状态

评论

0赞 Andrea Bardelli 7/26/2023
这看起来就像我一直在寻找的一样^^它也可以与普通的 .psm1 模块一起使用吗?
0赞 mklement0 7/26/2023
@AndreaBardelli,是的,它应该;请参阅我的更新,其中还提供了有关 PowerShell 中(模块)范围的详细信息的链接。