區域變數、全域變數是各種程式語言都有的基本概念,PowerShell 也不例外。寫程式因觀念不清被迷惑,研究後發現它跟 C#、JavaScript 有些不同,特別寫篇筆記備忘。

參考資料:

SS64 將 PowerShell Scope 分為四種:

  • Global - 從 PowerShell 一啟動就存在,預設變數、自動變數以及 Profile 建立的變數別名都放在這區。
  • Local - 目前所在的 Scope,可能是 Global、Script 或是任何 Scope
  • Script - ps1 或 ScriptBlock 形成的 Scope
  • 階層數字 - Scope 有巢狀關係,有父 Scope 跟子 Scope 之分,0 代表目前所在的 Scope,1 代表父層 Scope、2 代表祖父層 Scope,以此類推。

不同 Scope 間存取規則如下:

  1. Scope 有巢狀關係,子 Scope 可以看到父 Scope 的變數
  2. 在該 Scope 所建立的變數只有該 Scope 及其子 Scope 可以看到,設成 private 可防止其他 Scope 存取
  3. 變數只能被建立它的 Scope 修改,子 Scope 能讀不能改 (除非指定 Scope)

第三點跟 JavaScript 或 C# 比較不同,是讓迷惑的原因,看完文件才弄懂。來個範例演練一下:

$x = "x from outside"
$y = "y from outside"
$g = "g from outside"

function func() {
    Write-Host "inside func()" -ForegroundColor Yellow
    Write-Host "x = $x"
    $y = "y from inside"
    Write-Host "y = $y"
    $z = "z from inside"
    Write-Host "z = $z"
    $global:g = "g from inside"
    Write-Host "g = $g"
    Write-Host "global:g = $global:g"
}

func
Write-Host "outside func()" -ForegroundColor Yellow
Write-Host "x = $x"
Write-Host "y = $y"
Write-Host "z = $z"
Write-Host "g = $g"
Write-Host "global:g = $global:g"

我在 test1.ps1 宣告了 $x, $y, $g 三個變數,在 function func() 中讀取 $x、修改 $y、宣告一個 $z 及 $global:g 變數。func() 內部會形成一個子 Scope,藉此觀察父子 Scope 間的變數存取行為。

測試時先呼叫 func() 檢查及修改這些變數內容,再檢查父 Scope 變數的狀況。

實測結果如下:

func() 的區域 Scope 可以看到上一層的 $x、$y、$g。區域 Scope 設定的 $z,父 Scope 看不到。比較有趣的是,對 $y 的修改只有在 func() 執行期間有效,結束後父 Scope 看到的 $y 還是原本的值。可以想成子 Scope 為變數複製一個分身,子 Scope 對分身做的修改並不影響本尊,這點跟 JavaScript 或 C# 子 Scope 存取父 Scope 變數概念不太一樣,值得留意。另外,子 Scope 跟父 Scope 可用 $global:g 共用 Global 變數,可讀可寫。而 $globa:g 與 test1.ps1 的 $g 不是同一個變數,test1.ps1 的 $g 相當於 $script:g。

以下這個例子更清楚地展示了 $global:v、$script:v、$local:v (等同 $v) 的差異:

如何在 func() 裡更改 .ps1 層變數的內容?由上面測試可知,寫成 $script:variableName = "..." 就可以了,若是較複雜的巢狀結構,可透過 Set-Variable -Scope 1 修改父層或祖父、曾祖父層的變數,如以下示範:

$x = "x from outside"
function func() {
    Write-Host "inside func()" -ForegroundColor Yellow
    Write-Host "x = $x"
    Write-Host "set x as new value"
    # Scope 0 - current or local
    # Scope 1 - parent scope 
    # Scope 2 - parent scope of parent
    Set-Variable -Name x -Value "x from inside" -Scope 1 
    Write-Host "x = $x"
}

func
Write-Host "outside func()" -ForegroundColor Yellow
Write-Host "x = $x"

不過,使用全域變數或更動上層變數的做法,易造成變值異動難以追蹤、讓程式不易理解、提高相依性妨礙重構或函式重複利用,通常被視為不好的設計方式。故要在函式內修改外部 Scope 變數的另一種做法是函式參數 加註 [ref],接著在函式內部即可使用 $refVarName.Value 存取及修改變數內容,如下示範:

$x = "x from outside"
function func([ref]$v) {
    Write-Host "inside func()" -ForegroundColor Yellow
    Write-Host "v = " $v.Value
    Write-Host "set new value"
    $v.Value = "value from insdie"
    Write-Host "v = " $v.Value
}

func ([ref]$x)
Write-Host "outside func()" -ForegroundColor Yellow
Write-Host "x = $x"

以上就是針對 PowerShell Scope 的簡單介紹,提供給未來忘記這是怎麼一回事的我大家參考。

Introduction to the basic concept of PowerShell scope.


Comments

# by Huang

ref感覺像call by reference power shell的變數範圍有集大成的感覺,朝向簡單化使用但取了嚴謹的優點來避免靈異件

Post a comment