【问题标题】:Variable scoping in PowerShellPowerShell 中的变量范围
【发布时间】:2012-03-08 16:40:10
【问题描述】:

关于 PowerShell 的一个可悲之处在于函数和脚本块是动态范围的。

但让我感到惊讶的另一件事是,变量在内部范围内表现为写入时复制。

$array=@("g")
function foo()
{
    $array += "h"
    Write-Host $array
}

& {
    $array +="s"
    Write-Host $array
}
foo

Write-Host $array

输出是:

g s
g h
g

这使得动态作用域的痛苦减轻了一点。但是如何避免写时复制呢?

【问题讨论】:

    标签: powershell scope


    【解决方案1】:

    PowerShell 作用域文章 (about_Scopes) 很好,但过于冗长,所以这是我的 article 的引文:

    通常,PowerShell 作用域类似于 .NET 作用域。它们是:

    • 全球是公开的
    • 脚本是内部的
    • 私人是私人的
    • 本地是当前堆栈级别
    • 编号范围从 0..N 开始,其中每个步骤都达到堆栈级别(0 是本地)

    这是一个简单的例子,它描述了作用域的用法和作用:

    $test = 'Global Scope'
    Function Foo {
        $test = 'Function Scope'
        Write-Host $Global:test                                  # Global Scope
        Write-Host $Local:test                                   # Function Scope
        Write-Host $test                                         # Function Scope
        Write-Host (Get-Variable -Name test -ValueOnly -Scope 0) # Function Scope
        Write-Host (Get-Variable -Name test -ValueOnly -Scope 1) # Global Scope
    }
    Foo
    

    如您所见,您只能在命名范围内使用类似 $Global:test 的语法,$0:test 将始终为 $null。

    【讨论】:

      【解决方案2】:

      您可以使用 范围修饰符*-Variable cmdlet。

      范围修饰符是:

      • global 用于在最外层范围内访问/修改(例如交互式 shell)
      • script 用于在运行脚本(.ps1 文件)范围内访问/修改。如果不运行脚本,则以global 运行。

      (对于*-Variable cmdlet 的-Scope 参数,请参阅帮助。)

      例如。在您的第二个示例中,直接修改全局$array

      & {
        $global:array +="s"
        Write-Host $array
      }
      

      有关更多详细信息,请参阅帮助主题 about_scopes

      【讨论】:

      • 感谢您的信息。我对 about_scopes 主题的阅读更少。我在本文档中没有看到的一件事是变量是动态范围的。 :(
      • 这让我在对我的脚本进行错误检查时感到困惑;我有一个简单的计数器,每次脚本遇到错误时它都会递增,它决定了在电子邮件中发送的信息。无论我设置了多少错误条件,最后的错误计数始终为零。现在我明白了为什么会这样以及如何解决它。
      • 我认为这种方法只回答一两个非常有限的情况下的问题。不是一般的。
      【解决方案3】:

      不仅仅是变量。当这说“项目”时,它表示变量、函数、别名和 psdrive。所有这些都有范围。

      详细描述 Windows PowerShell 保护对变量、别名、函数和 Windows PowerShell 驱动器 (PSDrives),通过限制它们可以读取的位置和 改变了。通过强制执行一些简单的范围规则,Windows PowerShell 有助于确保您不会无意中更改应 不会被改变。 以下是范围的基本规则: - 您包含在范围中的项目在它所在的范围内可见 被创建并在任何子范围内,除非您明确地创建它 私人的。您可以放置​​变量、别名、函数或 Windows PowerShell 在一个或多个范围内驱动。 - 您在范围内创建的项目只能在 创建它的范围,除非您明确指定 范围不同。

      您看到的写入时复制问题是由于 Powershell 处理数组的方式造成的。添加到该数组实际上会破坏原始数组并创建一个新数组。由于它是在该范围内创建的,因此当函数或脚本块退出并且该范围被释放时,它就会被销毁。

      您可以在更新变量时显式定义其范围,或者您可以使用 [ref] 对象进行更新,或者编写脚本以便更新对象的属性或对象的哈希表键,或者父范围内的哈希表。这不会在本地范围内创建新对象,它会修改父范围内的对象。

      【讨论】:

      • 确定函数和别名是一种变量。但我对此并不担心。特别是在某些语言中,函数的作用域与变量不同(例如常见的 lisp),但仍然是变量。
      • 如果有一天您编写了一个在子范围内创建别名或函数的 Powershell 脚本,这可能会引起一些问题。
      • 但是您没有回答问题。 @Richard 得到了正确答案
      • 但这使 powershell 成为一种更糟糕的语言。如果范围语义根据变量的类型而改变,那是可怕的设计选择,IMO。此外,它还可以在您面前恢复动态范围。 :(
      • 嗯,就是这样。我只是试图解释发生了什么。写时复制是野兽的本性。明确定义变量的范围并不能避免写时复制,它只是指定副本将被写入的位置。
      【解决方案4】:

      虽然其他帖子提供了许多有用的信息,但它们似乎只是为了让您免于 RTFM。
      未提及的答案是我认为最有用的答案!

      ([ref]$var).value = 'x'
      

      这会修改 $var 的值,无论它碰巧在什么范围内。你不需要知道它的范围;只是它确实已经存在。要使用 OP 的示例:

      $array=@("g")
      function foo()
      {
          ([ref]$array).Value += "h"
          Write-Host $array
      }
      & {
          ([ref]$array).Value +="s"
          Write-Host $array
      }
      foo
      Write-Host $array
      

      生产:

      g s
      g s h
      g s h
      

      说明:
      ([ref]$var) 为您获取指向变量的指针。由于这是一个读取操作,它解析为实际创建该名称的最新范围。如果变量不存在,它也会解释错误,因为 [ref] 不能创建任何东西,它只能返回对已经存在的东西的引用。

      .value 然后将您带到保存变量定义的属性;然后你可以设置。

      您可能很想做这样的事情,因为它有时看起来很有效。

      ([ref]$var) = "New Value"
      

      不要!!!!
      它看起来像它工作的实例是一种错觉,因为 PowerShell 正在做一些它只在一些非常狭窄的情况下(例如在命令行上)做的事情。你不能指望它。实际上它在 OP 示例中不起作用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-06-20
        • 2015-05-02
        • 2015-07-02
        • 2014-09-24
        • 1970-01-01
        • 1970-01-01
        • 2019-06-15
        • 1970-01-01
        相关资源
        最近更新 更多