【问题标题】:$_ variable used in function from a module is empty (PowerShell)模块函数中使用的 $_ 变量为空 (PowerShell)
【发布时间】:2011-09-09 23:58:35
【问题描述】:

这里有一个问题;)

我有这个功能:

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 复制并粘贴到命令行并运行示例 sn-p 时,一切都很好。

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


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

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

【问题讨论】:

    标签: variables powershell scope powershell-module


    【解决方案1】:

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

    【讨论】:

    • GetNewClosure() 像这样Set-DbFile -prop {...}.GetNewClosure() 没有帮助。我想这些模块背后会有一些魔力。
    • 去上班了,但有机会我会做一些测试。这些症状似乎都指向对该脚本块的评估。
    • $props = & $Properties.getnewclosure() - getnewclosure() 会根据脚本块中使用的任何变量的当前值重新评估脚本块。要为 $_ 的每个新值重新评估它,需要在流程块内调用 getnewclosure。
    • 是的,& $Properties.GetNewClosure() 给出了正确的结果。谢谢,点赞。我将它保持打开状态,因为我真的很想知道为什么模块内和模块外的功能之间存在差异。
    • 是的,我以前见过这个。没有答案。当我从一个模块中的嵌套函数转移到非嵌套函数时,我遇到了类似的问题并通过 $Script:Properties 解决了它。
    【解决方案2】:

    看起来GetNewClosure() 是一个很好的解决方法,但它改变了脚本块查看这些变量的方式。将$_ 作为参数传递给脚本块也可以。

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

    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(),此时它们将它们在调用GetNewClosure() 的范围内引用的同名变量的副本内部化(首先使用局部变量,直到全局变量)。快速演示:

    $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() 解决方法,您也必须知道脚本块将在其中执行的变量的名称才能使其工作。

    另一方面,如果你使用参数给脚本块并将$_作为参数传递给它,脚本块不需要知道变量名,它只需要知道特定类型的参数将被通过。所以你的模块会使用$props = & $Properties $_而不是$props = & $Properties.GetNewClosure(),你的脚本块看起来更像这样:

    { (param [System.IO.FileInfo]$fileinfo)
        Write-Host Creating properties for $fileinfo.FullName
        @{Name=$fileinfo.Name } # any other properties based on the file
    }
    

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

    【讨论】:

    • 我已经多次看到模块及其范围/环境的问题,以至于我认为 PowerShell 的一个功能设计得不是很好。谢谢你的解释..
    • 在跨范围使用这些自动变量时,我会远离它们。
    • 对不起,我认为这个答案有点错误。 @stej 真的应该给我积分。 :) 你说它与范围无关,实际上它与范围有关,实际上范围树是准确的。模块有自己的作用域树,因此行为。正如我在下面的回答中指出的那样,它是在 Powershell 语言规范中以明文形式编写的。
    • 我在这句话中澄清了我所说的范围是什么意思,因为我当时并不了解范围树。
    【解决方案3】:

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

    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 节):

    检索绑定的脚本块 到一个模块。任何局部变量 在调用者的上下文中 复制到模块中。

    ...因此 GetNewClosure() 是一种享受,因为它弥合了本地范围/模块的鸿沟。我希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 2013-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多