gpt4 book ai didi

powershell - 为什么变量的范围会根据它是 .ps1 还是 .psm1 文件而改变,如何缓解这种情况?

转载 作者:行者123 更新时间:2023-12-02 23:17:17 46 4
gpt4 key购买 nike

我有一个执行脚本 block 的函数。为方便起见,脚本 block 不需要显式定义参数,而是可以使用 $_$A引用输入。

在代码中,这样做是这样的:

$_ = $Value
$A = $Value2
& $ScriptBlock

整个事情都包含在一个函数中。最小的例子:

function F {
param(
[ScriptBlock]$ScriptBlock,
[Object]$Value
[Object]$Value2
)

$_ = $Value
$A = $Value2
& $ScriptBlock
}

如果此函数写在 PowerShell 脚本文件 ( .ps1 ) 中,但使用 Import-Module 导入, F 的行为正如预期的那样:

PS> F -Value 7 -Value2 1 -ScriptBlock {$_ * 2 + $A}
15
PS>

但是,当函数写入 PowerShell 模块文件 ( .psm1 ) 并使用 Import-Module 导入时,行为出乎意料:

PS> F -Value 7 -Value2 1 -ScriptBlock {$_ * 2 + $A}
PS>

使用 {$_ + 1}而是给出 1 .看来 $_值为 $null反而。据推测,某些安全措施限制了 $_ 的范围。变量或以其他方式保护它。或者,可能是 $_变量由一些自动过程分配。无论如何,只要 $_变量受到影响,第一个不成功的示例将返回 1 .

理想情况下,该解决方案将涉及明确指定运行脚本 block 的环境的能力。就像是:

Invoke-ScriptBlock -Variables @{"_" = $Value; "A" = $Value2} -InputObject $ScriptBlock

总之,问题是:
  • 为什么模块文件中的脚本 block 不能访问在调用它们的函数中定义的变量?
  • 是否有一种方法可以在调用脚本 block 时显式指定脚本 block 可访问的变量?
  • 是否有其他不涉及在脚本 block 中包含显式参数声明的解决方法?
  • 最佳答案

    乱序:

    • Is there some other way of solving this that does not involve including an explicit parameter declaration in the script block?


    是的,如果您只想填充 $_ , 使用 ForEach-Object !
    ForEach-Object在调用者的本地范围内执行,这可以帮助您解决问题 - 除非您不必这样做,因为它还会自动将输入绑定(bind)到 $_/ $PSItem :

    # this will work both in module-exported commands and standalone functions
    function F {
    param(
    [ScriptBlock]$ScriptBlock,
    [Object]$Value
    )

    ForEach-Object -InputObject $Value -Process $ScriptBlock
    }

    现在 F将按预期工作:
    PS C:\> F -Value 7 -ScriptBlock {$_ * 2}

    Ideally, the solution would involve the ability to explicitly specify the environment in which a script block is run. Something like:

    Invoke-ScriptBlock -Variables @{"_" = $Value; "A" = $Value2} -InputObject $ScriptBlock


    使用 ScriptBlock.InvokeWithContext() 执行脚本 block :

    $functionsToDefine = @{
    'Do-Stuff' = {
    param($a,$b)
    Write-Host "$a - $b"
    }
    }

    $variablesToDefine = @(
    [PSVariable]::new("var1", "one")
    [PSVariable]::new("var2", "two")
    )

    $argumentList = @()

    {Do-Stuff -a $var1 -b two}.InvokeWithContext($functionsToDefine, $variablesToDefine, $argumentList)

    或者,包裹在一个函数中,如您的原始示例:
    function F
    {
    param(
    [scriptblock]$ScriptBlock
    [object]$Value
    )

    $ScriptBlock.InvokeWithContext(@{},@([PSVariable]::new('_',$Value)),@())
    }

    现在您知道如何解决您的问题,让我们回到有关模块范围的问题。

    首先,值得注意的是,您实际上可以使用模块来实现上述目标,但有点相反。

    (在下文中,我使用 New-Module 定义的内存模块,但模块范围解析行为描述与从磁盘导入脚本模块时相同)

    虽然模块范围“绕过”正常范围解析规则(请参阅下面的解释),但 PowerShell 实际上支持在特定模块范围内的反向显式执行。

    只需将模块引用作为第一个参数传递给 &调用运算符,PowerShell 会将后续参数视为要在所述模块中调用的命令:
    # Our non-module test function
    $twoPlusTwo = { return $two + $two }
    $two = 2

    & $twoPlusTwo # yields 4

    # let's try it with explicit module-scoped execution
    $myEnv = New-Module {
    $two = 2.5
    }

    & $myEnv $twoPlusTwo # Hell froze over, 2+2=5 (returns 5)

    • Why can't script blocks in module files access variables defined in functions from which they were called?
    • If they can, why can't the $_ automatic variable?


    因为加载的模块保持状态,而 PowerShell 的实现者想要 隔离模块状态来自调用者的环境。

    你问,为什么这可能有用,为什么一个会排除另一个?

    考虑以下示例,一个用于测试奇数的非模块函数:

    $two = 2
    function Test-IsOdd
    {
    param([int]$n)

    return $n % $two -ne 0
    }

    如果我们在脚本或交互式提示中运行上述语句,随后调用 Test-IsOdd应该产生预期的结果:
    PS C:\> Test-IsOdd 123
    True

    到目前为止,太好了,但依赖于非本地 $two在这种情况下,变量存在一个缺陷——如果在我们的脚本或 shell 中的某个地方,我们不小心重新分配了局部变量 $two ,我们可能会破坏 Test-IsOdd完全地:
    PS C:\> $two = 1 # oops!
    PS C:\> Test-IsOdd 123
    False

    这是意料之中的,因为默认情况下,变量范围解析只是在调用堆栈上游荡,直到它到达全局范围。

    但有时你可能需要在一个或多个函数的执行过程中保持状态,就像我们上面的例子一样。

    模块通过遵循稍微不同的范围解析规则来解决这个问题——模块导出的函数遵循我们称之为模块范围的东西(在到达全局范围之前)。

    为了说明这如何解决我们之前的问题,考虑这个相同函数的模块导出版本:

    $oddModule = New-Module {
    function Test-IsOdd
    {
    param([int]$n)

    return $n % $two -ne 0
    }

    $two = 2
    }

    现在,如果我们调用导出的新模块 Test-IsOdd ,我们可以预见地得到预期的结果,而不管调用者范围内的“污染”如何:
    PS C:\> Test-IsOdd 123
    True
    PS C:\> $two = 1
    PS C:\> Test-IsOdd 123 # still works
    True

    这种行为虽然可能令人惊讶,但基本上是 用于巩固模块作者和用户之间的隐含契约 - 模块作者不需要太担心“外面”是什么(调用者 session 状态),用户可以期望“里面”发生的任何事情(加载的模块的状态)都能正常工作,而不必担心什么它们分配给本地范围内的变量。

    模块范围行为不佳 documented in the help files , 但在 Bruce Payette 的“PowerShell In Action” (ISBN:9781633430297) 的第 8 章中进行了深入解释

    关于powershell - 为什么变量的范围会根据它是 .ps1 还是 .psm1 文件而改变,如何缓解这种情况?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61979503/

    46 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com