考虑以下任意函数和测试用例:
Function Foo-MyBar {
Param(
[Parameter(Mandatory=$false)]
[ScriptBlock] $Filter
)
if (!$Filter) {
$Filter = { $true }
}
#$Filter = $Filter.GetNewClosure()
Get-ChildItem "$env:SYSTEMROOT" | Where-Object $Filter
}
##################################
$private:pattern = 'T*'
Get-Help Foo-MyBar -Detailed
Write-Host "`n`nUnfiltered..."
Foo-MyBar
Write-Host "`n`nTest 1:. Piped through Where-Object..."
Foo-MyBar | Where-Object { $_.Name -ilike $private:pattern }
Write-Host "`n`nTest 2:. Supplied a naiive -Filter parameter"
Foo-MyBar -Filter { $_.Name -ilike $private:pattern }
Foo-MyBar
在测试 1 中,我们通过过滤器将结果传递给管道,该Where-Object
过滤器将返回的对象与包含在私有范围变量中的模式进行比较$private:pattern
。在这种情况下,这将正确返回 C:\ 中以字母开头的所有文件/文件夹T
。
在测试 2 中,我们直接将相同的过滤脚本作为参数传递给Foo-MyBar
. 但是,Foo-MyBar
到运行过滤器时,$private:pattern
它不在范围内,因此不会返回任何项目。
我理解为什么会这样——因为传递给的 ScriptBlockFoo-MyBar
不是闭包,所以不会关闭$private:pattern
变量并且该变量会丢失。
我从评论中注意到,我之前有一个有缺陷的第三次测试,它试图通过 {...}.GetNewClosure(),但这并没有关闭私有范围的变量——感谢@PetSerAl 帮助我澄清这一点。
问题是,如何Where-Object
捕获$private:pattern
测试 1 中的值,以及我们如何在我们自己的函数/cmdlet 中实现相同的行为?
(最好不需要调用者必须知道闭包,或者知道将他们的过滤器脚本作为闭包传递。)
我注意到,如果我取消注释$Filter = $Filter.GetNewClosure()
里面的行Foo-MyBar
,那么它永远不会返回任何结果,因为$private:pattern
它丢失了。
(正如我在顶部所说,函数和参数在这里是任意的,作为我真正问题的最短形式再现!)