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

ste*_*tej 13 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})
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

看看$Properties.应对每个文件进行评估,然后进一步处理文件和属性.

示例如何使用它可能是:

Get-ChildItem c:\windows |
    ? { !$_.PsIsContainer } |
    Set-DbFile -prop { 
        Write-Host Creating properties for $_.FullName
        @{Name=$_.Name } # any other properties based on the file
    }
Run Code Online (Sandbox Code Playgroud)

当我将函数复制并粘贴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
....
Run Code Online (Sandbox Code Playgroud)

模块中定义的函数结果:

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- 
....
Run Code Online (Sandbox Code Playgroud)

Cos*_*Key 6

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

function F1{
    $test="Hello"
    F2
}
function F2{
    $test
}
Run Code Online (Sandbox Code Playgroud)

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

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

引用Powershell语言规范(第4.3.7节)中的GetNewClosure()方法的文本:

检索绑定到模块的脚本块.调用者上下文中的任何局部变量都将复制到模块中.

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


Joe*_*ant 5

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

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

script.ps1 对于正常的点源:

function test-script([scriptblock]$myscript){
    $message = "inside"
    &{write-host "`$message from $message"}    
    &$myscript
}
Run Code Online (Sandbox Code Playgroud)

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()
}
Run Code Online (Sandbox Code Playgroud)

来电和输出:

» . .\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?)
Run Code Online (Sandbox Code Playgroud)

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

这个Q&A,也提供了这个错误报告的链接几乎是完全相同的主题,我遇到的其他博客文章也是如此.但是虽然它被报道为一个错误,但我不同意.

参阅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. 
Run Code Online (Sandbox Code Playgroud)

现在我理解了这种行为,但是上面还有一些实验让我了解了它:

  • 如果我们更改$message了scriptblock,$local:message那么所有3个测试都有一个空格,因为$message没有在scriptblock的本地范围中定义.
  • 如果我们使用$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.
Run Code Online (Sandbox Code Playgroud)
  • 如果我们使用$((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
Run Code Online (Sandbox Code Playgroud)

我希望这有帮助.

附录:关于设计.

JasonMArcher的评论让我想到了将scriptblock传递给模块的设计问题.在您的问题的代码中,即使您使用GetNewClosure()变通方法,您也必须知道将执行scriptblock的变量的名称才能使其工作.

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

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

有关进一步说明,请参阅CosmosKey的答案.