模块中函数中使用的 $_ 变量为空(PowerShell) [英] $_ variable used in function from a module is empty (PowerShell)
问题描述
这里有一个问题;)
我有这个功能:
function Set-DbFile {
param(
[Parameter(ValueFromPipeline=$true)]
[System.IO.FileInfo[]]
$InputObject,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[scriptblock]
$Properties
)
process {
$InputObject | % {
Write-Host `nInside. Storing $_.Name
$props = & $Properties
Write-Host ' properties for the file are: ' -nonew
write-Host ($props.GetEnumerator()| %{"{0}-{1}" -f $_.key,$_.Value})
}
}
}
查看 $Properties
.应该对每个文件进行评估,然后进一步处理文件和属性.
Look at the $Properties
. It should be evaluated for each file and then the file and the properties should be processed further.
示例如何使用它可能是:
Example how to use it might be:
Get-ChildItem c:\windows |
? { !$_.PsIsContainer } |
Set-DbFile -prop {
Write-Host Creating properties for $_.FullName
@{Name=$_.Name } # any other properties based on the file
}
当我复制 &将函数 Set-dbFile
粘贴到命令行并运行示例代码段,一切正常.
When I copy & paste function Set-dbFile
to command line and run the example snippet, everything is fine.
但是,当我将函数存储在模块中,导入它并运行示例时,$_
变量为空.有人知道为什么吗?以及如何解决?(也欢迎其他解决方案)
However, when I store the function in a module, import it and run the example, the $_
variable is empty. Does anybody know why? And how to solve it? (other solutions are welcome as well)
在脚本中定义/在命令行中输入的函数的结果:
Results for function defined in a script/typed in commandline:
Inside. Storing adsvw.ini
Creating properties for C:\windows\adsvw.ini
properties for the file are: Name-adsvw.ini
Inside. Storing ARJ.PIF
Creating properties for C:\windows\ARJ.PIF
properties for the file are: Name-ARJ.PIF
....
模块中定义的函数的结果:
Results for function defined in module:
Inside. Storing adsvw.ini
Creating properties for
properties for the file are: Name-
Inside. Storing ARJ.PIF
Creating properties for
properties for the file are: Name-
....
推荐答案
看起来 GetNewClosure()
与任何解决方案一样好,但它改变了脚本块查看这些变量的方式.将 $_
作为参数传递给脚本块也有效.
Looks like GetNewClosure()
is as good a work around as any, but it changes the way the script block sees those variables. Passing $_
to the scriptblock as an argument works, too.
它与正常范围问题(例如,全局 vs 本地)无关,但一开始看起来是这样.这是我非常简化的复制和一些解释如下:
It has nothing to do with normal scope issues (e.g., global vs local), but it appears like that at first. Here's my very simplified reproduction and some explanation following:
script.ps1
用于普通点源:
function test-script([scriptblock]$myscript){
$message = "inside"
&{write-host "`$message from $message"}
&$myscript
}
Module\MyTest\MyTest.psm1
用于导入:
function test-module([scriptblock]$myscript){
$message = "inside"
&{write-host "`$message from $message"}
&$myscript
}
function test-module-with-closure([scriptblock]$myscript){
$message = "inside"
&{write-host "`$message from $message"}
&$myscript.getnewclosure()
}
调用和输出:
» . .\script.ps1
» import-module mytest
» $message = "outside"
» $block = {write-host "`$message from $message (inside?)"}
» test-script $block
$message from inside
$message from inside (inside?)
» test-module $block
$message from inside
$message from outside (inside?)
» test-module-with-closure $block
$message from inside
$message from inside (inside?)
于是我开始四处寻找,因为这激起了我的好奇心,我发现了一些有趣的东西.
So I started hunting around since this piqued my curiosity, and I found a few interesting things.
这个问答,其中还包含指向 此错误报告 与我遇到的其他一些博客文章几乎完全相同.但是虽然它被报告为错误,但我不同意.
This Q&A, which also features a link to this bug report is pretty much the exact same topic, as are some other blog articles I ran across. But while it was reported as a bug, I disagree.
about_Scopes 页面有这样的说法(w:
The about_Scopes page has this to say (w:
...
Restricting Without Scope
A few Windows PowerShell concepts are similar to scope or interact with
scope. These concepts may be confused with scope or the behavior of scope.
Sessions, modules, and nested prompts are self-contained environments,
but they are not child scopes of the global scope in the session.
...
Modules:
...
The privacy of a module behaves like a scope, but adding a module
to a session does not change the scope. And, the module does not have
its own scope, although the scripts in the module, like all Windows
PowerShell scripts, do have their own scope.
现在我理解了这种行为,但正是上述行为和其他一些实验让我明白了这一点:
Now I understand the behavior, but it was the above and a few more experiments that led me to it:
- 如果我们将脚本块中的
$message
更改为$local:message
那么所有 3 个测试都有一个空格,因为$message
是未在脚本块的本地范围内定义. - 如果我们使用
$global:message
,所有 3 个测试都会打印outside
. - 如果我们使用
$script:message
,前两个测试打印outside
,最后一个打印inside
.
- If we change
$message
in the scriptblock to$local:message
then all 3 tests have a blank space, because$message
is not defined in the scriptblock's local scope. - If we use
$global:message
, all 3 tests printoutside
. - If we use
$script:message
, the first 2 tests printoutside
and the last printsinside
.
然后我也在about_Scopes
中读到了这个:
Then I also read this in about_Scopes
:
Numbered Scopes:
You can refer to scopes by name or by a number that
describes the relative position of one scope to another.
Scope 0 represents the current, or local, scope. Scope 1
indicates the immediate parent scope. Scope 2 indicates the
parent of the parent scope, and so on. Numbered scopes
are useful if you have created many recursive
scopes.
- 如果我们使用
$((get-variable -name message -scope 1).value)
来尝试从直接父作用域获取值,会发生什么?我们仍然得到outside
而不是inside
. - If we use
$((get-variable -name message -scope 1).value)
in order to attempt getting the value from the immediate parent scope, what happens? We still getoutside
rather thaninside
.
在这一点上,我很清楚会话和模块有自己的声明范围或各种上下文,至少对于脚本块是这样.脚本块在声明它们的环境中就像匿名函数一样,直到您对它们调用 GetNewClosure()
,此时它们会内部化它们在作用域中引用的同名变量的副本其中 GetNewClosure()
被调用(首先使用本地变量,直到全局变量).快速演示:
At this point it was clear enough to me that sessions and modules have their own declaration scope or context of sorts, at least for script blocks. The script blocks act like anonymous functions in the environment in which they're declared until you call GetNewClosure()
on them, at which point they internalize copies of the variables they reference of the same name in the scope where GetNewClosure()
was called (using locals first, up to globals). A quick demonstration:
$message = 'first message'
$sb = {write-host $message}
&$sb
#output: first message
$message = 'second message'
&$sb
#output: second message
$sb = $sb.getnewclosure()
$message = 'third message'
&$sb
#output: second message
我希望这会有所帮助.
附录:关于设计.
JasonMArcher 的评论让我想到了将脚本块传递到模块中的设计问题.在您问题的代码中,即使您使用 GetNewClosure()
解决方法,您也必须知道将在其中执行脚本块的变量的名称才能使其工作.
JasonMArcher's comment made me think about a design issue with the scriptblock being passed into the module. In the code of your question, even if you use the GetNewClosure()
workaround, you have to know the name of the variable(s) where the scriptblock will be executed in order for it to work.
另一方面,如果您使用参数给脚本块并将 $_
作为参数传递给它,则脚本块不需要知道变量名,它只需要知道一个将传递特定类型的参数.所以你的模块将使用 $props = &$Properties $_
而不是 $props = &$Properties.GetNewClosure()
,你的脚本块看起来更像这样:
On the other hand, if you used parameters to the scriptblock and passed $_
to it as an argument, the scriptblock does not need to know the variable name, it only needs to know that an argument of a particular type will be passed. So your module would use $props = & $Properties $_
instead of $props = & $Properties.GetNewClosure()
, and your scriptblock would look more like this:
{ (param [System.IO.FileInfo]$fileinfo)
Write-Host Creating properties for $fileinfo.FullName
@{Name=$fileinfo.Name } # any other properties based on the file
}
请参阅 CosmosKey 的回答以获得进一步说明.
这篇关于模块中函数中使用的 $_ 变量为空(PowerShell)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!