PowerShellスコープ:変数のスコープの理解

PowerShellスクリプトを書く際に、関数や他のスクリプトへの外部依存関係がない場合、PowerShellのスコープの概念はあまり関係ありません。PowerShellのグローバル変数の概念も特に重要ではありません。しかし、関数やモジュールの作成、他のスクリプトからのスクリプトの呼び出しを学び始めると、このトピックはより重要になります。

この記事では、PowerShellのスコープについての詳しいレッスンを受け、それがどのように機能し、スコープを考慮してコードを書く方法を理解していただきます。終わった時には、PowerShellのグローバル変数とその他の多くのことを理解しているでしょう。

スコープ:バケツのようなもの

変数を定義してその値を確認したら、別の値になっているというようなスクリプトを書いたことはありませんか?明確に定義したのに、なぜその変数が変わったのか頭を抱えることがあります。その理由の一つは、変数の値が別のスコープで上書きされている可能性があるからです。

また、コンソールで参照すると値があるのにスクリプトに存在しないというようなPowerShell変数がどのようにして値を持つのか疑問に思ったことはありませんか。それらの変数は、その時には利用できない別の「バケツ」にある可能性が高いです。

スコープはバケツのようなものです。スコープはPowerShellが変数、エイリアス、関数、PSDriveを異なる領域間で分離する方法に影響を与えます。スコープはバケツのようなものです。これらのアイテムをまとめる場所です。

PowerShellが開始されると、自動的にこれらの「バケツ」が作成されます。その時点で、あなたは気づかないうちに既にスコープを使用しています。すべてのスコープはPowerShellによって定義され、あなたの手助けなしに作成されます。

スコープの種類

PowerShellが起動すると、自動的にさまざまなアイテムを配置するための4つの「バケツ」またはスコープが作成されます。自分でスコープを作成することはできません。以下で定義されたこれらのスコープにアイテムを追加したり削除したりすることしかできません。

グローバルスコープ

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を明示的にスコープに割り当てないで作成すると、ローカルスコープに配置されます(後で説明します)。

スコープの参照

存在する4つのスコープの種類についてのアイデアがあるので、これらのスコープを参照する方法も2つあることを知るべきです。

PowerShellでは、名前付きまたは番号付きの2つの方法でスコープを参照することができます。両方の方法は同じスコープを参照していますが、単に異なる方法で行います。これらはスコープとのやり取りの2つの異なる方法です。

名前付きスコープ

スコープタイプのセクションで、名前によって参照されるスコープについて学びました。名前によってスコープを参照することは、直感的には名前付きスコープと呼ばれます。名前によってスコープを割り当てることが主な目的です。以下でその方法を学びます。

番号付きスコープ

名前とともに、各スコープにはゼロから始まる番号があり、常にローカルスコープになります。スコープは、現在のローカルスコープに対して動的に番号付けされます。

例えば、PowerShellセッションを開始した直後は、グローバルスコープで操作しています。この時点で、グローバルスコープはローカルスコープです(ローカルスコープは単なるポインターであることを覚えておいてください)。

ローカルスコープは常にスコープゼロですので、現時点ではグローバルスコープもスコープゼロです。しかし、同じセッションからスクリプトを実行すると、スクリプトスコープが作成されます。実行時、ローカルスコープのポインタはスクリプトスコープに変更されます。これにより、スクリプトスコープがスコープゼロとなり、グローバルスコープがスコープワンとなります。

スコープは番号付けられます。このプロセスは、ローカルスコープが0であるスコープの数だけ繰り返されます。スコープはスコープの階層によって動的に番号付けされます。

スコープの階層と継承

前述のように、PowerShellセッションを起動すると、PowerShellはグローバルスコープにいくつかのアイテムを作成します。これらのアイテムは、関数、変数、エイリアス、またはPSDriveです。PowerShellセッションで定義するものはすべて、グローバルスコープにも定義されます。

デフォルトではグローバルスコープにいるため、スクリプトの実行や関数の実行など、別のスコープを作成する操作を行うと、親スコープがグローバルスコープである子スコープが作成されます。スコープは、親と子を持つプロセスのようなものです。

親スコープで定義されたもの、この場合はグローバルスコープで定義されたものは、子スコープからアクセスできます。ただし、これらのアイテムは、定義されたスコープでのみ編集可能です。

例えば、Test.ps1という名前のスクリプトがあるとします。このスクリプト内には、次のような1行があります。

$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では、Get-VariableSet-Variableという2つのコマンドレットを使用して変数を設定できます。これらのコマンドレットを使用すると、変数の値を取得したり値を定義したりできます。

両方のコマンドレットは、NameScopeパラメータを持っています。これらのパラメータを使用することで、PowerShellはすべてのスコープで変数の値を設定および取得することができます。

ローカルスコープ

ローカルスコープに変数を設定するには、以下に示すようにSet-Variableを使用し、ローカル変数名と値を指定します。

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

ローカルスコープは常にデフォルトなので、Scopeパラメータを使用しない場合、変数は常にローカルスコープに定義されます。

ローカルスコープの変数の値を取得するには、Get-Variableを使用し、名前を指定します。

PS> Get-Variable -Name a

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

プライベート/スクリプト/グローバルスコープ

プライベート、スクリプト、グローバル変数についても、同じパラメータ(NameおよびValue)を使用します。唯一の違いは、今回はスコープパラメータを使用して明示的にスコープを定義する必要があることです。

プライベート、スクリプト、またはグローバルスコープの変数を設定する方法は同じです。単に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は2つのローカルスコープを「マージ」しました。

Original variable overwritten

AllScopeプロパティの使用(オプション)

特定のスコープでアイテムとやり取りする方法を見てきましたが、それぞれのアイテムはまだ単一のスコープで定義されています。しかし、変数がどのスコープで定義されているかわからない場合はどうでしょうか?

Set-Variableコマンドレットを使用して変数を定義する際、OptionパラメータにAllScope値を指定することで、変数をすべてのスコープに一括で配置することができます。

以下のように、Test.ps1スクリプトを変更して、変数をすべてのスコープに設定するデモを行っています。その変数は以下のように出力されます。

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

次に、グローバルスコープに$aの値が設定され、Test.ps1スクリプトが実行されていることが確認できます。しかし、影響がないはずの$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コマンドレットのAllScopeオプションとは異なり、プライベートスコープの変数はアイテムを区画化するための優れた方法です。

スコープのベストプラクティス

グローバルスコープで変数を定義したり、AllScopeオプションを使用することは一般的です。なぜなら、すべての変数がどこでも利用できるからです。スコープの複雑さを心配する必要はありません。定義されたものにアクセスするための追加の自由が提供されますが、すぐに手に負えなくなり、トラブルシューティングが困難になる可能性があります。

スコープの使用を防ぐ代わりに、次のヒントに従ってください:

  1. 関数でスコープを指定する代わりに、パラメータを使用して必要な情報を関数に渡します。
  2. できるだけローカルスコープ内にとどまるようにします。
  3. スクリプトからグローバル変数を定義する代わりに、必要な場合にはすべてを出力し、コンソールから変数に保存するためにWrite-Outputコマンドレットを使用します。

重要なポイントは、スコープを受け入れ、それを利用する方法を学ぶことです。スコープを回避しようとするのではなく、利用することです。

さらなる読み物

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