当你编写一个没有函数且没有外部依赖其他脚本的PowerShell脚本时,PowerShell范围的概念并不是太重要。PowerShell全局变量的概念并不是核心问题。但是当你开始构建函数、模块并学会从其他脚本调用脚本时,这个主题变得更加重要。
在本文中,你将深入了解PowerShell中范围的含义、工作原理以及如何在编写代码时考虑范围。完成后,你将了解PowerShell的全局变量等内容!
范围:有点像桶
你是否曾经写过一个脚本,在其中定义了一个变量,当你检查该变量的值时,它变成了其他东西?也许你会对明明已经定义了的变量如何改变而感到困惑。一个原因可能是该变量的值在另一个范围中被覆盖了。
也许你曾想知道为什么在控制台中引用它们时,某些PowerShell变量具有值,但在你的脚本中不存在。很有可能这些变量在另一个此时不可用的“桶”中。
范围就像桶一样。范围影响PowerShell在不同区域之间隔离变量、别名、函数和PSDrives的方式。一个范围就像一个桶。这是一个将所有这些项目聚集在一起的地方。
当PowerShell启动时,它会自动为你创建这些“桶”。在那时,你已经在不自觉中使用范围了。所有范围都是由PowerShell定义的,并且在你不帮助的情况下自动创建。
范围类型
当 PowerShell 启动时,它会自动创建四个“桶”或范围,用于放置各种项目。您不能自己创建范围。您只能向这些定义如下的范围添加和删除项目。
全局范围
在 PowerShell 打开时定义的项目设置为全局范围。这些项目包括由系统创建的对象,如 PowerShell 驱动器,以及您在启动时定义的 PowerShell 配置文件 中的任何内容。
全局范围中的项目可在任何地方访问。您可以在控制台上交互地引用全局范围中的项目,在您运行的任何脚本中以及在任何函数中。PowerShell 的全局变量无处不在。因此,PowerShell 全局变量的常见用途是在脚本之间使用 PowerShell 全局变量。
总体上只有一个全局范围规则。
脚本范围
A script scope is automatically created every time a PowerShell script runs. You can have many different script scope instances. Script scopes are created when you execute a PS1 script or a module, for example.
只有在特定脚本范围实例中创建的项目才能相互引用。
私有范围
通常,定义的项目可以从其他范围访问 – 但在私有范围中的项目不是这样。私有范围中的项目包含对其他范围隐藏的对象。私有范围用于创建与其他范围中项目同名的项目,以防止重叠。
本地范围
不同于其他范围,局部范围有点不同。局部范围是指向全局、脚本或私有范围的指针。局部范围相对于代码在执行时的上下文。
如果您创建一个变量、别名、函数或 PSDrive 而没有明确分配范围(稍后我们将介绍),它将进入局部范围。
引用范围
现在您对存在的四种范围类型有了一个概念,您还应该知道有两种引用这些范围的方式。
在 PowerShell 中,有两种引用范围的方式 – 按名称或按编号。这两种方法都是引用相同的范围,只是以不同的方式。这是与范围交互的两种不同方式。
按名称命名的范围
在范围类型部分上面,您了解到了按名称引用的范围。按名称引用范围,在直观上称为命名范围。按名称引用范围的主要目的是将项目分配给范围。您将在下面学习如何做到这一点。
按编号命名的范围
除了名称外,每个范围还有一个从零开始的编号,该编号始终是局部范围。范围的编号是相对于当前局部范围动态编号的。
例如,一旦您打开 PowerShell 会话,您就是在全局范围内操作。在这一点上,全局范围是局部范围(请记住,局部范围只是一个指针)。
由于本地作用域始终是作用域零,所以在这一点上,全局作用域也是作用域零。但是,当您从同一会话中运行脚本时,将创建一个脚本作用域。在运行时,本地作用域指针随后已更改为脚本作用域。现在脚本作用域是作用域零,全局作用域是作用域一。
作用域按编号进行标记。这个过程会针对本地作用域为0的所有作用域重复进行。作用域由作用域层次结构动态编号。
作用域层次结构和继承
如前所述,当您启动 PowerShell 会话时,PowerShell 会为您在全局作用域中创建一些项目。这些项目可以是函数、变量、别名或 PSDrives。您在 PowerShell 会话中定义的任何内容也将在全局作用域中定义。
由于默认情况下您位于全局作用域,如果执行脚本或运行函数等操作创建另一个作用域,将会创建一个子作用域,其父作用域为全局作用域。作用域就像带有父级和子级的进程。
在父作用域中定义的任何内容,在本例中为全局作用域,将可以在子作用域中访问。但是这些项目只能在定义它们的作用域中进行编辑。
假设您有一个名为Test.ps1的脚本。在该脚本内部有一行,如下所示。
当您运行此脚本时,在本地范围(脚本运行时)中为$a
赋值。当运行Test.ps1时,您会发现在脚本执行后无法引用它。由于$a
是在脚本范围内赋值的,因此全局范围(在交互式控制台)无法看到它。

让我们进一步举例说明,并使Test.ps1脚本如下所示。现在,脚本尝试在相同的范围内设置变量$a
之前输出其值。
为了演示,在交互式控制台中为$a
分配一个值。这将在全局范围内分配该值。现在,当脚本运行时,它将继承父范围(全局),并应该能够看到该值。
您可以看到,当执行Test.ps1时(创建全局范围的子范围),它可以看到$a
的值。您还可以看到该变量的值在全局范围中也是可用的,因为该范围是它被设置的位置。这意味着$a
在脚本(子)和父(全局)范围中都可用。

记住这种范围继承行为。这样做将有助于您在出现故障排除场合时,例如不同范围中具有相同名称的变量冲突。
定义和访问范围中的项目
现在你知道了作用域是什么以及它们是如何工作的,那么如何访问它们呢?让我们看看如何使用 PowerShell 来设置变量作用域(并访问它们)。
获取/设置变量
在 PowerShell 中,有两个 cmdlet 允许你设置变量,它们分别是 Get-Variable
和 Set-Variable
。这些 cmdlet 允许你检索变量的值或定义一个值。
这两个 cmdlet 类似,都有一个 Name
和 Scope
参数。通过使用这些参数,PowerShell 允许你在所有作用域中设置和检索变量的值。
本地作用域
要在本地作用域中设置变量,请使用 Set-Variable
,并提供一个本地变量名称和一个值,如下所示。
本地作用域始终是默认的,因此不使用 Scope
参数将始终在本地作用域中定义变量。
要检索本地作用域变量的值,请使用 Get-Variable
并提供名称。
私有/脚本/全局作用域
当处理私有、脚本和全局变量时,你将使用相同的参数(Name
和 Value
)。唯一的区别是这一次你将使用 Scope 参数来显式定义作用域。
设置私有、脚本或全局作用域变量的方法是相同的。只需将传递给 Scope
参数的值替换为 Private
、Script
、Global
。
要检索脚本或全局作用域变量的值,请使用Get-Variable
,并提供名称和作用域。
注意:您还可以使用
Get
/Set-Variable
cmdlet,并使用作用域编号而不是名称来引用作用域。
作用域”前缀”
您还可以使用快捷方式检索和设置作用域中的变量。在引用变量时,不使用PowerShell cmdlet,而是在变量前加上作用域前缀。
本地作用域
由于本地作用域始终是默认值,所以只需定义一个变量并引用它即可设置和检索本地作用域变量
私有/脚本/全局作用域
如果您想要定义和引用脚本或全局作用域,可以在变量前加上作用域名称和一个分号。
例如,要在全局作用域中设置变量$a
,可以在a前加上$global:
。
对于脚本作用域变量也是一样。
一旦变量在所需作用域中设置好,您可以以相同的方式引用它们。同时,请注意,如果已定义的作用域是本地作用域,则可以排除作用域前缀。
脚本块中的作用域
PowerShell有一个方便的结构叫做脚本块。脚本块允许您移动代码片段并在几乎任何地方执行它们。
就像PS1脚本一样,脚本块在它们自己的脚本作用域中运行。当您执行脚本块时,实际上是在执行一个PS1脚本。
请注意下面的示例,其中一个变量在全局范围中定义,然后尝试在脚本范围中覆盖。如上所述,这样做是行不通的,因为子范围无法引用父范围。

这个例子表明,当在脚本块中更改$a
时,全局变量$a
的定义并没有改变,因为脚本块是一个脚本子范围。
点源脚本(交换本地范围)
PowerShell有一个称为点源的概念。这是一种方法,允许您执行PS1脚本,并将所有本应为脚本范围的内容带入本地范围。
通过在引用PS1脚本并执行之前加上点号(.),这样做“点源”了脚本的内容,并将所有内容带入本地范围。
为了演示,我有一个Test.ps1脚本,再次定义了一个变量,如下所示。
在PowerShell控制台中,为$a
变量设置一个值,然后像下面所示点源这个脚本。请注意,原始变量被覆盖了。PowerShell“合并”了两个本地范围。

使用AllScope属性(选项)
您已经了解了如何与特定范围中的项目进行交互,但到目前为止,每个项目仍然在单个范围中定义。但是,如果您不知道变量定义在哪个范围呢?
定义变量时,可以使用Set-Variable
命令,将变量一次性放入所有范围中。为此,请使用Option
参数的AllScope
值。
为了演示,Test.ps1脚本已经被修改,以在所有范围中设置一个变量。然后将该变量输出如下所示。
然后,您可以在下面看到,在全局范围内为$a
设置了一个值,并执行了Test.ps1脚本。然而,$a
的值并没有没有效果,它的值已被覆盖。它不仅在脚本范围内被定义(Write-Output $a
),而且还覆盖了全局范围。

AllScope
选项很方便,但要小心。该选项实质上取消了范围的概念,并将所有内容合并在一起。
函数范围
当您执行函数时,该函数内的所有代码都处于其自己的子范围中。函数范围遵循与其他范围相同的子/父行为。
为每个函数设置单独的范围是个好主意。它可以更好地控制项目,不必担心项目冲突,同时还可以获得在函数中变量的自动清理的额外好处。一旦函数完成,函数中定义的所有项目都将被清除。
要演示,直接将以下函数复制/粘贴到 PowerShell 控制台中。
粘贴后,执行该函数。注意,您无法在函数之外访问$var
变量。
保持项目私有(禁用继承)
通常,如果在父范围中定义了变量,该变量将在子范围中定义。但也许您想使用一个变量名,但该变量名已在会话中的一个范围中定义。在这种情况下,您可以选择不同的变量名,或者在私有范围中定义该变量,使其成为私有变量。
A way to use scopes to reduce conflicts is to use the private scope. Using the private scope disables inheritance on that specific variable. When multiple child scopes are created, those child scopes will not see any variables defined in a private scope.
A Test.ps1 script outputs the value of $a as shown below.
您可以看到下面我正在全局范围内定义一个私有作用域的变量,然后执行Test.ps1脚本。通常,当您在父范围中定义变量时,该变量将在子范围中可用 – 但私有作用域的变量不是这样的。
在下面的示例中,您可以看到通过执行Test.ps1脚本创建的脚本子范围无法看到在父范围中定义的私有作用域$a
变量。

与全局范围或Set-Variable
cmdlet 上的AllScope
选项不同,私有作用域变量是一种很好的分隔项的方法。
范围最佳实践
思考在全局范围定义变量或使用AllScope
选项是一种常见的做法。毕竟,所有变量都可以在任何地方使用。无需担心范围的复杂性。虽然这确实提供了对定义内容的额外访问自由,但可能很快变得难以控制和调试。
不要试图阻止使用范围,而是遵循这些建议:
- 在函数中,不要指定范围,而是使用参数将所需信息传递给函数。
- 尽量保持在局部范围内。
- 不要从脚本中定义全局变量,而是使用
Write-Output
cmdlet在需要时将所有内容输出并保存到变量中。
这里的主要观点是要接受范围并学会如何利用它们,而不是试图规避它们。