PowerShell中的变量作用域


68

关于PowerShell的一件不幸的事是函数和脚本块是动态范围的。

但是让我惊讶的是,变量在内部作用域内表现为写时复制。

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

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

Write-Host $array

输出为:

g s
g h
g

这使得动态范围界定的痛苦减轻了一些。但是,如何避免写时复制?

Answers:


75

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

范围修饰符是:

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

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

例如。在第二个示例中,直接修改global $array

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

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


谢谢(你的)信息。我少读了about_scopes主题。我在本文档中没有看到的一件事是变量是动态范围的。:(
mathk 2012年

这使我在对脚本进行错误检查时感到困惑。我有一个简单的计数器,每次脚本遇到错误时,计数器都会增加,该计数器确定通过电子邮件发送的信息。最后的错误计数始终为零,无论设置多少错误条件。现在,我既了解为什么会这样,又知道如何解决它。
KeithS

我认为这种方法只能在一个或两个非常有限的情况下回答问题。不是一般的。
bielawski

89

PowerShell作用域文章(about_Scopes)很不错,但是太冗长了,因此引用了我的文章

通常,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。


17

不只是变量。如果说“项目”,则表示变量,函数,别名和psdrives。所有这些都有范围。

详细描述  
    Windows PowerShell保护对变量,别名,函数和变量的访问
    Windows PowerShell驱动器(PSDrives)通过限制它们的读取和读取位置
    改变了。通过执行一些简单的作用域规则,Windows PowerShell
    有助于确保您不会无意中更改应
    不变。

    以下是范围的基本规则:

        -您包含在范围内的项目在该范围内可见
          是在任何子范围内创建的,除非您明确创建
          私人的。您可以放置​​变量,别名,函数或Windows
          PowerShell在一个或多个范围内驱动。

        -您在范围内创建的项目只能在
          除非您明确指定一个
          不同的范围。

您看到的写副本问题是因为Powershell处理数组的方式。添加到该数组实际上会破坏原始数组并创建一个新数组。由于它是在该作用域中创建的,因此当函数或脚本块退出并处置该作用域时,它将被销毁。

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


确定函数和别名是种类变量。但我对此并不担心。特别是在某些语言中,函数的作用域与变量不同(例如,普通的lisp),但仍然是变量。
mathk 2012年

如果某天您编写了在子作用域中创建别名或函数的Powershell脚本,则可能会有些担心。
mjolinor 2012年

但是您没有回答这个问题。@Richard得到了正确的答案
mathk 2012年

2
但这使powershell成为更糟糕的语言。如果范围语义根据可怕的设计选择的变量类型而变化,则为IMO。另外,它还带给您动态的范围。:(
mathk 2012年

1
好吧,就是这样。我只是想解释发生了什么。写时复制是野兽的本性。明确地对变量进行范围界定并不能避免写入时复制,而只是指定复制将要写入的位置。
mjolinor 2012年

1

尽管其他帖子提供了许多有用的信息,但它们似乎只是使您脱离了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示例中不起作用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.