PowerShell のセッション変数とスコープ

スコープという概念は、多くのプログラミング言語にあるので、親しみやすい概念ではあると思います。
いくつかの代表的なプログラミング言語とは違い、PowerShell では

  • 親スコープに作成したものと同じ名前を持つ変数を作成できます。
  • 関数やスクリプトを実行する際に、子スコープを作らないことができます。
  • 子スコープには見えない現在スコープにだけ有効な変数を作成できます。

PowerShell では現在のスコープをローカルスコープと呼びますが、0番スコープと呼ばれることもあります。親スコープは1番スコープと呼ばれることがあり、親の親は2番スコープになります。
PowerShell で最も外側のスコープは Runspace です。各 Runspace の持つスコープには global という名前がついているので、環境変数が env: というプレフィックスでアクセスできるように、このもっとも外側にあるスコープには global: というプレフィックスを指定して、Runspace 内の任意の場所からアクセスできるようになっています。
global 以外のスコープ指定プレフィックスとしては、script: があります。スクリプトスコープは、スクリプトを実行したときに作成されたローカルスコープを指します。

あと、変数変数と書いていますが、実際は関数もスコープ毎に定義されますので、スコープに関することは関数定義を行う際にも有効です。

同じ名前の変数が作れるということ

たとえば、

PS > function foo { write $a; $a = $a + 1; write $a; }

このような関数を作成して次のように実行すると、こんな結果になります。

PS > $a = 3
PS > $a
3

PS > foo
3
4

PS > $a
3
  • foo を実行すると、関数呼び出しによって新しいスコープが作成されます。
  • 最初の write $a; では、$a の内容を出力しますが、作成されたばかりのローカルスコープには $a という変数はありません。このため、親スコープの $a が表示されます。
  • 次の $a = $a + 1; では、同様に親スコープの $a の値を取り出し、1 を追加したあとでローカルスコープに $a という変数を新規作成します。
  • 最後の write $a; では、$a というローカルスコープの変数を表示しています。

foo が実行されても呼び出し元には影響がないため、実行後に $a を表示しても 3 が表示されるわけです。

スコープを作らないということ

先ほどの実行例に、少しだけ変更を行います。

PS > $a = 10
PS > $a
10

PS > . foo
10
11

PS > $a
11

foo を呼び出す際に、foo の前に . を付けただけです。これだけで foo を現在スコープで実行することができるようになります。なんか便利!ですよね?
自分のスコープにスクリプトや関数による影響を取り込むことは、ちょっとあぶなっかしいことですが、ちょっとしたスクリプトデバッグ用途にドット指定で呼び出して、実行後の変数の中身を見たりとか、自分で自分を書き換える関数とか、意外に便利なこともあります。

子スコープから見えない変数

変数の一般的ではない機能を使用するためには、$name 形式ではなく Set-Variable コマンドレットや Get-Variable, New-Variable コマンドレット等を使用する必要があります。たとえば、-scope オプションを使用すると親や先祖のスコープにある変数を直接操作することができます。

PS > $x = 1;
PS > Get-Variable "x" -Scope 0
1

PS > Get-Variable "x" -Scope 1
100

こんなかんじで、指定した番号のスコープの変数を取得できたりします。子スコープに見えない変数には、Private というオプションを指定します。

# 既に存在する $a を削除する
PS > Remove-Variable "a"

# 新しい変数 $a を Private オプション付きで作成
PS > New-Variable "a" 10 -Option Private
PS > $a
10

# foo からは $a が見えない
PS > foo
1

PS > $a
10

# スコープを作成しないので $a を参照し、書き換えることができる
PS > . foo
10
11

PS > $a
11

Private の他には、定数を作成する Constant や読み取り専用の変数を作成する ReadOnly といったオプションが指定できます。ドット指定の呼び出しとよく似た機能として、AllScope というオプションもあります。これは変数を子スコープに引き継ぐことができるというものです。

PS > Remove-Variable a
PS > New-Variable a 20 -Option AllScope
PS > $a
20

PS > foo
20
21

PS > $a
21

PS > . foo
21
22

PS > $a
22

実行結果を見てわかると思いますが、ドット指定の有無に限らず呼び出し先での変更の影響を受けています。