13

你的一个问题是在这里;)

我有这个功能:

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

3 回答 3

6

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

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

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

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

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

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

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

于 2011-06-23T10:17:19.017 回答
5

看起来和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?)

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

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

现在我理解了这种行为,但正是上述情况和其他一些实验让我明白了这一点:

  • 如果我们将脚本块更改$message为,$local:message那么所有 3 个测试都有一个空格,因为$message未在脚本块的本地范围中定义。
  • 如果我们使用$global:message,所有 3 个测试都会打印outside
  • 如果我们使用$script:message,前 2 个测试打印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 的答案。

于 2011-06-18T03:09:17.450 回答
1

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

于 2011-06-07T11:36:41.053 回答