乱序:
- 是否有其他不涉及在脚本块中包含显式参数声明的解决方法?
ForEach-Object 在调用者的本地范围内执行,这可以帮助您解决问题 - 除了您不必,因为它还会自动将输入绑定到 $_/$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}
理想情况下,该解决方案将涉及显式指定运行脚本块的环境的能力。比如:
Invoke-ScriptBlock -Variables @{"_" = $Value; "A" = $Value2} -InputObject $ScriptBlock
使用ScriptBlock.InvokeWithContext() 执行脚本块:
$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)
- 为什么模块文件中的脚本块不能访问在调用它们的函数中定义的变量?
- 如果可以,为什么
$_ 自动变量不能?
因为加载的模块维护状态,而 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
这种行为,虽然可能令人惊讶,但基本上用于巩固模块作者和用户之间的隐含契约 - 模块作者不需要过多担心“外面”有什么(调用者会话状态),并且用户可以期望“在那里”(加载的模块的状态)发生的任何事情都能正常工作,而不必担心他们分配给本地范围内的变量的内容。
模块作用域行为不佳documented in the help files,但在 Bruce Payette 的“PowerShell In Action”的第 8 章中有深入的解释 (ISBN:9781633430297)