PowerShell範圍:了解變量範圍

當您撰寫一個沒有函數和不依賴其他腳本的PowerShell腳本時,PowerShell範圍的概念並不是很重要。PowerShell全域變數的概念也不是首要關注的。但是當您開始建立函數、模組並學習如何從其他腳本調用腳本時,這個主題變得更加重要。

在本文中,您將深入瞭解PowerShell中的範圍是什麼,它們是如何工作的,以及您如何根據範圍撰寫代碼。到最後,您將了解PowerShell的全域變數,以及更多相關知識!

範圍:有點像桶

您是否曾經撰寫過一個腳本,在其中定義了一個變數,但當您檢查該變數的值時,它卻是其他的值?您可能會對變數在明確定義的情況下發生變化感到困惑。其中一個原因可能是該變數的值在另一個範圍中被覆蓋了。

也許您曾經想知道為什麼在控制台中引用某些PowerShell變數時它們有值,但在您的腳本中並不存在。很有可能這些變數存在於另一個在該時刻不可用的“桶”中。

範圍就像桶一樣。範圍影響PowerShell在不同區域之間隔離變數、別名、函數和PSDrive的方式。範圍就像是一個桶,它是一個將所有這些項目收集在一起的地方。

當PowerShell啟動時,它會自動為您創建這些“桶”。在那時,您已經在使用範圍,卻沒有意識到。所有範圍都由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 會在全域範圍中為您創建一些項目。這些項目可以是函數、變量、別名或 PSDrive。您在 PowerShell 會話中定義的任何內容也將在全域範圍中定義。

由於默認情況下您位於全域範圍中,如果執行腳本或運行函數等創建另一個範圍的操作,將創建一個子範圍,其父範圍為全域範圍。範圍就像具有父範圍和子範圍的進程。

在父範圍(全域範圍)中定義的任何內容,將在子範圍中可訪問。但是,這些項目只能在它們被定義的範圍中進行編輯。

假設您有一個名為 Test.ps1 的腳本。該腳本內部只有一行,如下所示。

$a = 'Hello world!'

當你執行此腳本時,$a 在本地範圍內被賦予一個值(腳本在運行時)。當運行 Test.ps1 時,你可以看到在腳本執行後無法引用它。由於 $a 是在腳本範圍內賦值的,全域範圍(在互動式控制台中)無法看到它。

Global scope cannot see the variable

讓我們進一步舉一個例子,使 Test.ps1 腳本如下所示。現在,腳本在同一範圍內在設置 $a 值之前嘗試輸出它。

Write-Output $a
$a = 'Hello world!'

為了示範,請在互動式控制台中給 $a 賦值。這會在全域範圍內賦值。現在,當腳本運行時,它將繼承父範圍(全域範圍),並應該能夠看到該值。

如下所示,當執行 Test.ps1(創建全域範圍的子範圍)時,它可以看到 $a 的值。同時,你也可以看到該變數的值在全域範圍中也是可用的,因為它是在該範圍中設置的。這意味著 $a 在腳本(子範圍)和父範圍(全域範圍)中都可用。

Variable is available in script and global scopes

記住這種範圍繼承行為。這樣做將有助於您在出現變數衝突的疑難解答場合,例如在不同範圍中具有相同名稱的變數。

定義和訪問範圍中的項目

現在您已經知道作用域是什麼以及它們是如何工作的,那麼如何訪問它們呢?讓我們看看如何使用 PowerShell 來設置變量作用域(並訪問它們)。

Get/Set-Variable

在 PowerShell 中,有兩個 cmdlet 允許您設置變量,分別是 Get-VariableSet-Variable。這些 cmdlet 允許您檢索變量的值或定義一個值。

這兩個 cmdlet 都具有相似的 NameScope 參數。使用這些參數,PowerShell 允許您在所有作用域中設置和檢索變量值。

本地作用域

要在本地作用域中設置變量,使用 Set-Variable 並提供本地變量名稱和值,如下所示。

PS> Set-Variable -Name a -Value 'foo'

本地作用域始終是默認作用域,因此不使用 Scope 參數將始終在本地作用域中定義變量。

要檢索本地作用域變量的值,使用 Get-Variable 並提供變量名稱。

PS> Get-Variable -Name a

Name         Value
----         -----
a            foo

私有/腳本/全局作用域

在處理私有、腳本和全局變量時,您將使用相同的參數(NameValue)。唯一的區別是這次您將使用 Scope 參數來明確定義作用域。

設置私有、腳本或全局作用域變量的方法是相同的。只需將傳遞給 Scope 參數的值替換為 PrivateScriptGlobal

PS> Set-Variable -Name a -Value 'foo' -Scope <Private|Script|Global>

要检索脚本或全局范围变量的值,请使用Get-Variable命令并提供名称和范围。

PS> Get-Variable -Name a -Scope <Script|Global>

Name         Value
----         -----
a            foo

注意:您还可以使用Get/Set-Variable命令通过范围编号而不是名称来引用范围。

范围前缀

您还可以使用一种快捷方式在范围中检索和设置变量。在引用变量时,您可以在变量之前加上范围前缀,而不是使用PowerShell命令。

本地范围

由于本地范围始终是默认范围,只需定义变量并引用即可设置和检索本地范围变量。

PS> $a = 'foo'
PS> $a

foo

私有/脚本/全局范围

如果您想定义和引用脚本或全局范围的变量,可以在变量之前加上范围名称和分号。

例如,要在全局范围中设置变量$a,可以在其前面加上$global:

$global:a = ‘foo’

对于脚本范围变量也是如此。

$script:a = ‘foo’

一旦变量在所选范围中设置好了,您可以以相同的方式引用它们。还要注意,如果定义的范围是本地范围,则可以省略范围前缀。

PS> $global:a = 'foo'
PS> $global:a
foo
PS> $a
foo

脚本块中的范围

PowerShell有一个方便的结构称为脚本块。脚本块允许您移动代码片段并在几乎任何地方执行它们。

与PS1脚本类似,脚本块在其自己的脚本范围中运行。当您执行脚本块时,实际上是在执行一个PS1脚本。

在下面的示例中,请注意变量在全局范围内定义,然后在脚本范围内尝试覆盖的情况。如上所述,这样做是行不通的,因为子范围无法引用父范围。

A child scope cannot overwrite a parent scope

此示例显示,当在脚本块中更改$a时,全局变量定义的$a不会改变,因为脚本块是脚本子范围。

点化脚本(交换本地范围)

PowerShell有一个称为点化脚本的概念。这是一种方法,允许您执行一个PS1脚本,并将所有原本将作为脚本范围的内容引入到本地范围中。

通过在引用PS1脚本并执行之前加上一个点(.),这样可以“点化”脚本的内容,并将所有内容引入到本地范围中。

为了演示,我有一个名为Test.ps1的脚本,如下所示定义了一个变量。

$a = 'Hello world!'

在PowerShell控制台中,为$a变量设置一个值,然后像下面这样点化此脚本。注意,原始变量被覆盖了。PowerShell将这两个本地范围“合并”在一起。

Original variable overwritten

使用AllScope属性(可选)

您已经了解了如何与特定范围中的项目进行交互,但到目前为止,每个项目仍然只在一个范围中定义。但是,如果您不知道变量定义在哪个范围中呢?

當使用Set-Variable cmdlet定義變數時,可以將該變數一次性放入所有範圍中。要做到這一點,請使用Option參數的AllScope值。

為了演示,Test.ps1腳本已被修改以在所有範圍中設置一個變數。該變數隨後被輸出,如下所示。

Set-Variable -Name a -Value 'Hello world!' -Option AllScope
Write-Output $a

然後你可以在下面看到,在全局範圍中設置了$a的值並且執行了Test.ps1腳本。然而,它並沒有沒有效果,$a的值已經被覆蓋。它不僅在腳本範圍中被定義(Write-Output $a),而且還覆蓋了全局範圍。

Variable overwritten in global scope

AllScope選項很方便,但要小心使用。該選項基本上混合了所有範圍的概念。

函數範圍

當執行函數時,該函數內的所有代碼都處於其自己的子範圍中。函數範圍遵循與其他範圍相同的子父行為。

對於每個函數都有單獨的範圍是一個好主意。這允許更好地控制不用擔心項目衝突,同時還提供了在函數中自動清理變量的附加好處。一旦函數完成,函數中定義的所有項目都將被清除。

為了演示,將以下函數直接複製/粘貼到PowerShell控制台中。

Function Do-Thing {
    $var = 'bar'
}

粘貼後,執行該函數。注意,在函數外部無法訪問$var變量。

PS> Do-Thing
PS> $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.

Write-Output $a

如下所示,我正在定義一個在全局範圍內的私有範圍變量,然後執行Test.ps1腳本。通常情況下,當您在父範圍中定義一個變量時,該變量將在子範圍中可用,但對於私有範圍變量則不適用。

在下面的示例中,您可以看到執行Test.ps1腳本創建的腳本子範圍無法看到父範圍中定義的私有範圍$a變量。

Script scope cannot see the private-scoped variable

與全局範圍或Set-Variable cmdlet上的AllScope選項不同,私有範圍變量是一種很好的分隔項目的方法。

範圍最佳實踐

認為在全域範圍中定義變數或使用AllScope選項是正常的做法。畢竟,所有變數都可以在任何地方使用。不需要擔心範圍的複雜性。儘管這樣做可以提供額外的訪問自由,但很容易失控且難以疑難排解。

不要試圖防止使用範圍,請遵循以下提示:

  1. 在函數中不要指定範圍,使用參數將所需的信息傳遞給函數。
  2. 盡可能地保持在局部範圍內。
  3. 不要從腳本中定義全域變數,使用Write-Output cmdlet在需要時將所有內容輸出並保存到變數中。

重點是要接受範圍並學會運用它們來獲得好處,而不是試圖規避它們。

進一步閱讀

Source:
https://adamtheautomator.com/powershell-scopes/