模块中函数中使用的 $_ 变量为空(PowerShell) [英] $_ variable used in function from a module is empty (PowerShell)

查看:71
本文介绍了模块中函数中使用的 $_ 变量为空(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 print outside.
  • If we use $script:message, the first 2 tests print outside and the last prints inside.

然后我也在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 get outside rather than inside.
    • 在这一点上,我很清楚会话和模块有自己的声明范围或各种上下文,至少对于脚本块是这样.脚本块在声明它们的环境中就像匿名函数一样,直到您对它们调用 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆