函数中使用的$ _变量为空(PowerShell)

时间:2011-06-07 11:15:18

标签: variables powershell scope powershell-module

有一个问题在这里;)

我有这个功能:

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。应对每个文件进行评估,然后进一步处理文件和属性。

如何使用它的示例可能是:

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粘贴到命令行并运行示例代码段,一切都很好。

但是,当我将函数存储在模块中,导入它并运行示例时,$_变量为空。有人知道为什么吗?以及如何解决? (欢迎其他解决方案)


脚本中定义的函数/在命令行中输入的结果:

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
....

模块中定义的函数的结果:

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- 
....

3 个答案:

答案 0 :(得分:6)

这里的问题是范围层次结构。如果您定义两个函数,如...

function F1{
    $test="Hello"
    F2
}
function F2{
    $test
}

然后F2将继承F1的变量范围,因为它是从F1的范围调用的。如果在模块中定义函数F2并导出函数,则$ test变量不可用,因为模块具有自己的作用域树。请参阅Powershell Language Specification(第3.5.6节):

在您的情况下,当前节点变量在本地范围内定义,因此它不会存在于模块范围内,因为它位于具有不同范围根的不同树中(除了全局变量)。

引用Powershell Language Specification中的GetNewClosure()方法的文本(第4.3.7节):

  

检索绑定的脚本块   到一个module.Any局部变量   是在来电者意志的背景下   被复制到模块中。

...因此GetNewClosure()起作用,因为它桥接了本地范围/模块鸿沟。我希望这会有所帮助。

答案 1 :(得分:5)

看起来GetNewClosure()和任何一个一样好,但它会改变脚本块看到这些变量的方式。将$_作为参数传递给scriptblock也是有效的。

它与正常范围问题无关(例如,全局与本地),但最初看起来似乎是这样。这是我非常简化的复制和一些解释:

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?)

所以我开始狩猎,因为这激起了我的好奇心,我发现了一些有趣的东西。

This Q&A,其中还包含指向this bug report的链接,与我遇到的其他一些博客文章完全相同。但是虽然它被报道为一个错误,但我不同意。

about_Scopes页面就是这样说的(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. 

现在我理解了这种行为,但正是上面的内容以及更多的实验让我了解了这一行为:

  • 如果我们将脚本块中的$message更改为$local:message,则所有3个测试都有一个空格,因为脚本块的本地范围中未定义$message
  • 如果我们使用$global:message,则所有3个测试都会打印outside
  • 如果我们使用$script:message,则前两个测试会打印outside,最后一个打印inside

然后我也在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

在这一点上,我很清楚会话和模块有自己的声明范围或各种上下文,至少对于脚本块。脚本块在它们被声明的环境中就像匿名函数一样,直到你在它们上面调用GetNewClosure(),此时它们将{{1}范围内的同名变量的副本内部化。被调用(首先使用本地,最多使用全局)。快速演示:

GetNewClosure()

我希望这会有所帮助。

附录:关于设计。

JasonMArcher的评论让我想到了将scriptblock传递给模块的设计问题。在您的问题的代码中,即使您使用$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 解决方法,您也必须知道将执行scriptblock的变量的名称,以便它可以工作。

另一方面,如果你使用了scriptblock的参数并将GetNewClosure()作为参数传递给它,那么scriptblock不需要知道变量名,它只需要知道一个特定的参数。类型将被传递。因此,您的模块将使用$_而不是$props = & $Properties $_,并且您的scriptblock看起来更像是这样:

$props = & $Properties.GetNewClosure()

请参阅CosmosKey的答案以获得进一步说明。

答案 2 :(得分:1)

我相信你需要在运行它之前在该脚本块上调用getnewclosure()。从脚本文件或模块调用,在编译时评估脚本块。当您从控制台工作时,没有“编译时间”。它在运行时进行了评估,因此它的行为与模块中的行为不同。