Powershell 跨模块访问调用方范围变量
Powershell access caller scope variables across modules
我在名为 Common.psm1
的模块中声明了以下方法:
function Test-Any ([ScriptBlock]$FilterScript = $null)
{
begin {
$done = $false
}
process {
if (!$done)
{
if (!$FilterScript -or ($FilterScript | Invoke-Expression)){
$done = $true
}
}
}
end {
$done
}
}
Set-Alias any Test-Any -Scope Global
现在在另一个模块中,我有以下验证:
$id = 1
if($notifications | any { $_.Id -eq $id })
{
# do stuff here
return
}
我收到以下错误:
Invoke-Expression : The variable '$id' cannot be retrieved because it has not been set.
有趣的是,如果我将 Test-Any
定义移动到调用模块,它就像一个魅力。
如何在不将 Test-Any
复制到我的其他模块并且不更改此语法的情况下完成这项工作:
if($notifications | any { $_.Id -eq $id })
编辑 1:
关于我的代码 是否应该 似乎存在一些争论。欢迎在您自己的机器上尝试:
function Test-Any ([ScriptBlock]$FilterScript = $null)
{
begin {
$done = $false
}
process {
if (!$done)
{
if (!$FilterScript -or ($FilterScript | Invoke-Expression)){
$done = $true
}
}
}
end {
$done
}
}
Set-Alias any Test-Any -Scope Global
$id = 3
$myArray = @(
@{Id = 1},
@{Id = 2},
@{Id = 3},
@{Id = 4},
@{Id = 5},
@{Id = 6},
@{Id = 7},
@{Id = 8}
)
$myEmptyArray = @()
$myArray | any #returns true
$myArray | any {$_.Id -eq $id} #returns true
$myEmptyArray | any #returns false
$myEmptyArray | any {$_.Id -eq $id} #returns false
编辑 2:
我刚刚发现您只会遇到此问题,当 Test-Any
驻留在一个加载的模块中并且调用代码驻留在使用 Set-StrictMode -Version Latest
的第二个模块中时。如果您关闭 StrictMode,您不会收到错误,但它也不起作用。
编辑 3:
不用说,这工作得很好:
$sb = [Scriptblock]::Create("{ `$_.Id -eq $id }")
if($notifications | any $sb)
但严重偏离了我试图获得的简单性和直观性
Invoke-Expression
(which, when possible, ) 隐式地 重新创建 从 调用者的 范围传递的脚本块,通过它的 字符串representation,在 module 的上下文中,它使脚本块代码中对调用者状态的任何引用无效(因为模块通常看不到外部调用者的状态, global 范围除外).
解决方案是按原样执行脚本块,但提供传递给模块函数的管道输入:
# Note: New-Module creates a *dynamic* (in-memory only) module,
# but the behavior applies equally to regular, persisted modules.
$null = New-Module {
function Test-Any ([ScriptBlock] $FilterScript)
{
begin {
$done = $false
}
process {
if (!$done)
{
# Note the use of $_ | ... to provide pipeline input
# and the use of ForEach-Object to evaluate the script block.
if (!$FilterScript -or ($_ | ForEach-Object $FilterScript)) {
$done = $true
}
}
}
end {
$done
}
}
}
# Sample call. Should yield $true
$id = 1
@{ Id = 2 }, @{ Id = 1 } | Test-Any { $_.Id -eq $id }
注:this answer uses a similar approach, but tries to optimize processing by stopping further pipeline processing - which, however, comes at the expense of incurring an on-demand compilation penalty the first time the function is called in the session, because - as of PowerShell 7.2 - you cannot (directly) stop a pipeline on demand from user code - see GitHub issue #3821中的Test-Any
函数。
我在名为 Common.psm1
的模块中声明了以下方法:
function Test-Any ([ScriptBlock]$FilterScript = $null)
{
begin {
$done = $false
}
process {
if (!$done)
{
if (!$FilterScript -or ($FilterScript | Invoke-Expression)){
$done = $true
}
}
}
end {
$done
}
}
Set-Alias any Test-Any -Scope Global
现在在另一个模块中,我有以下验证:
$id = 1
if($notifications | any { $_.Id -eq $id })
{
# do stuff here
return
}
我收到以下错误:
Invoke-Expression : The variable '$id' cannot be retrieved because it has not been set.
有趣的是,如果我将 Test-Any
定义移动到调用模块,它就像一个魅力。
如何在不将 Test-Any
复制到我的其他模块并且不更改此语法的情况下完成这项工作:
if($notifications | any { $_.Id -eq $id })
编辑 1: 关于我的代码 是否应该 似乎存在一些争论。欢迎在您自己的机器上尝试:
function Test-Any ([ScriptBlock]$FilterScript = $null)
{
begin {
$done = $false
}
process {
if (!$done)
{
if (!$FilterScript -or ($FilterScript | Invoke-Expression)){
$done = $true
}
}
}
end {
$done
}
}
Set-Alias any Test-Any -Scope Global
$id = 3
$myArray = @(
@{Id = 1},
@{Id = 2},
@{Id = 3},
@{Id = 4},
@{Id = 5},
@{Id = 6},
@{Id = 7},
@{Id = 8}
)
$myEmptyArray = @()
$myArray | any #returns true
$myArray | any {$_.Id -eq $id} #returns true
$myEmptyArray | any #returns false
$myEmptyArray | any {$_.Id -eq $id} #returns false
编辑 2:
我刚刚发现您只会遇到此问题,当 Test-Any
驻留在一个加载的模块中并且调用代码驻留在使用 Set-StrictMode -Version Latest
的第二个模块中时。如果您关闭 StrictMode,您不会收到错误,但它也不起作用。
编辑 3: 不用说,这工作得很好:
$sb = [Scriptblock]::Create("{ `$_.Id -eq $id }")
if($notifications | any $sb)
但严重偏离了我试图获得的简单性和直观性
Invoke-Expression
(which, when possible,
解决方案是按原样执行脚本块,但提供传递给模块函数的管道输入:
# Note: New-Module creates a *dynamic* (in-memory only) module,
# but the behavior applies equally to regular, persisted modules.
$null = New-Module {
function Test-Any ([ScriptBlock] $FilterScript)
{
begin {
$done = $false
}
process {
if (!$done)
{
# Note the use of $_ | ... to provide pipeline input
# and the use of ForEach-Object to evaluate the script block.
if (!$FilterScript -or ($_ | ForEach-Object $FilterScript)) {
$done = $true
}
}
}
end {
$done
}
}
}
# Sample call. Should yield $true
$id = 1
@{ Id = 2 }, @{ Id = 1 } | Test-Any { $_.Id -eq $id }
注:this answer uses a similar approach, but tries to optimize processing by stopping further pipeline processing - which, however, comes at the expense of incurring an on-demand compilation penalty the first time the function is called in the session, because - as of PowerShell 7.2 - you cannot (directly) stop a pipeline on demand from user code - see GitHub issue #3821中的Test-Any
函数。