関数がなく、他のスクリプトに外部依存しない PowerShell スクリプトを書く場合、PowerShell スコープのコンセプトはあまり気にならないでしょう。 PowerShell グローバル変数の概念も前面に出てきません。 しかし、関数やモジュールを構築し、他のスクリプトからスクリプトを呼び出すことを学ぶようになると、このトピックはより重要になります。
この記事では、PowerShell のスコープとは何か、それがどのように機能し、スコープを考慮しながらコードを記述する方法について深く学びます。 終了する頃には、PowerShellのグローバル変数やその他多くのことを理解していることでしょう!
目次
- スコープ: Kinda Like Buckets
- スコープタイプ
- グローバルスコープ
- スクリプト スコープ
- ローカルスコープ
- スコープの参照
- 名前付きスコープ
- 番号付きスコープ
- スコープ階層と継承
- スコープ内のアイテムの定義とアクセス
- Get/Set-Variable
- ローカル スコープ
- プライベート/スクリプト/グローバル スコープ
- スコープ 「前景」
- ローカル スコープ
- プライベート/スクリプト/グローバル スコープ
- Scriptblocks におけるスコープ
- スクリプトのドットソース化 (ローカル スコープの交換)
- Using the AllScope Property (Option)
- Function Scopes
- Keep Items Private (Disabling Inheritance)
- スコープのベスト プラクティス
- 参考資料
スコープ: Kinda Like Buckets
変数を定義して、その変数の値をチェックしたら、別のものになっていたというスクリプトを書いたことがありますか? 明確に定義したはずなのに、どうしてその変数が変わってしまったのか、頭を悩ませることがあるかもしれません。
ある PowerShell 変数が、コンソールで参照したときには値を持つのに、スクリプトには存在しないことを不思議に思ったことがあるかもしれません。 それらの変数は、その時点では利用できない別の「バケツ」にある可能性があります。
スコープはバケツのようなものです。 スコープは、PowerShell が異なる領域間で変数、エイリアス、関数、および PSDrives を分離する方法に影響を与えます。 スコープはバケツのようなものです。
PowerShell が起動すると、自動的にこれらの「バケツ」が作成されます。 その時点で、あなたはすでに気づかないうちにスコープを使用しているのです。 すべてのスコープはPowerShellによって定義され、あなたが手伝うことなく作成されます。
スコープタイプ
PowerShellが起動すると、さまざまなアイテムを配置するための4つの「バケット」またはスコープが自動的に作成されます。 自分でスコープを作成することはできません。
グローバルスコープ
PowerShell が起動したときに定義される項目は、グローバルスコープに設定されます。 これらの項目には、PowerShell ドライブのようなシステムで作成されたオブジェクトや、起動時にプロファイルが実行されるため PowerShell プロファイルで定義したものが含まれます。 グローバル スコープのアイテムは、コンソールで対話的に、実行するスクリプトで、そして関数で参照することができます。 PowerShell のグローバル変数はどこにでもあります。 このため、PowerShell グローバル変数の一般的な使用方法は、スクリプト間で PowerShell グローバル変数を使用することです。
全体を支配するグローバル スコープは 1 つだけです。
スクリプト スコープ
PowerShell スクリプトが実行するたびにスクリプト スコープは自動的に作成されます。 多くの異なるスクリプト スコープ インスタンスを持つことができます。 スクリプト スコープは、PS1 スクリプトまたはモジュールなどを実行するときに作成されます。
その特定のスクリプト スコープ インスタンスで作成されたアイテムのみが、互いを参照できます。 プライベートスコープのアイテムは、他のスコープからは見えないオブジェクトを含んでいます。
ローカルスコープ
他のスコープと異なり、ローカルスコープは少し異なっています。 ローカルスコープは、グローバル、スクリプト、プライベートスコープへのポインタである。 ローカル スコープは、コードがその時点で実行されているどのようなコンテキストに対しても相対的です。
明示的にスコープを割り当てずに変数、エイリアス、関数、または PSDrive を作成すると、ローカル スコープに入ります (これについては後で説明します)。
スコープの参照
存在する 4 種類のスコープについて理解できたと思いますが、それらのスコープを参照する 2 つの方法があることも知っておいてください。 どちらの方法も、同じスコープを参照していますが、単に異なる方法で参照しています。 355>
名前付きスコープ
上記のスコープ タイプのセクションで、名前によって参照されるスコープについて学びました。 名前によってスコープを参照することは、直感的に、名前付きスコープと呼ばれます。 名前でスコープを参照する主な目的は、ある項目をスコープに割り当てることです。
番号付きスコープ
名前と一緒に、各スコープには常にローカルスコープとなる0から始まる番号が付けられています。 スコープは、現在のローカル スコープに関連して動的に番号付けされます。
たとえば、PowerShell セッションを開くとすぐに、グローバル スコープで操作することになります。 この時点で、グローバル スコープはローカル スコープです (ローカル スコープは単なるポインターであることを覚えておいてください)。
ローカル スコープは常にスコープ ゼロなので、この時点で、グローバル スコープも現在スコープ ゼロです。 しかし、その同じセッションからスクリプトを実行すると、スクリプト スコープが作成されます。 実行すると、そのときローカルスコープのポインターはスクリプトスコープに変更されています。
スコープに番号を付ける ローカルスコープが 0 であるスコープの数だけ、この処理を繰り返します。 スコープは、スコープ階層によって動的に番号を付けられます。
スコープ階層と継承
前述のように、PowerShell セッションを起動すると、PowerShell はグローバル スコープにいくつかの項目を作成します。 これらのアイテムは、関数、変数、エイリアス、または PSDrives である可能性があります。 PowerShell セッションで定義したものはすべて、グローバル スコープでも定義されます。
デフォルトでグローバル スコープにいるので、スクリプトの実行や関数の実行など、別のスコープを作成する操作を行うと、グローバル スコープを親として子スコープが作成されます。 スコープは親と子を持つプロセスのようなものです。
親スコープ、この場合はグローバル スコープで定義されているものはすべて、子スコープでアクセス可能になります。 しかし、これらの項目は、それらが定義されたスコープでのみ編集可能です。
例えば、Test.ps1 というスクリプトがあるとしましょう。 このスクリプトの内部には、以下のような1行があります。
$a = 'Hello world!'
このスクリプトを実行すると、$a
にはローカルスコープ(スクリプト実行中のスクリプト)で値が代入されます。 Test.ps1を実行すると、以下のように、スクリプト実行後に参照できなくなることがわかります。 $a
はスクリプト スコープ内で代入されたため、グローバル スコープ (対話型コンソール) では参照できません。
この例をさらに発展させて Test.ps1 スクリプトを以下のようにしましょう。 このスクリプトは今、同じスコープで設定する前に $a
の値を出力しようとしています。
Write-Output $a$a = 'Hello world!'
デモを行うには、対話型コンソールで $a
に値を代入します。 これにより、グローバルスコープで値が代入されます。
Test.ps1 を実行すると(グローバル スコープの子スコープを作成)、$a
の値が表示されることが確認できます。 また、このスコープは変数が設定された場所なので、グローバルスコープでも変数の値が利用できることがわかります。 つまり、$a
はスクリプト (子) と親 (グローバル) の両方のスコープで使用できます。
このスコープの継承動作を覚えておいてください。
スコープ内のアイテムの定義とアクセス
スコープとは何か、どのように機能するかが分かったところで、どのようにアクセスするのでしょうか。 PowerShell を使用して、変数スコープを設定する (そしてアクセスする) 方法を見てみましょう。
Get/Set-Variable
PowerShell には、Get-Variable
と Set-Variable
という変数を設定するための 2 つのコマンドレットがあります。 これらのコマンドレットは、変数の値を取得したり、値を定義したりすることができます。
両コマンドレットは、Name
とScope
パラメータを持つ類似したコマンドレットです。 これらのパラメーターを使用すると、PowerShell はすべてのスコープで変数値を設定および取得できます。
ローカル スコープ
ローカル スコープで変数を設定するには、Set-Variable
を使用して、以下に示すようにローカル変数名と値を指定します。
PS> Set-Variable -Name a -Value 'foo'
ローカル スコープは常にデフォルトなので Scope
パラメーターは使用しないとローカル スコープの変数が常に定義されます。
ローカル スコープの変数の値を取得するには、名前を指定して Get-Variable
を使用します。
PS> Get-Variable -Name aName Value---- -----a foo
プライベート/スクリプト/グローバル スコープ
プライベート、スクリプト、グローバル変数を扱う場合も同じパラメーター (Name
と Value
) を使用します。 唯一の違いは、今回、スコープ パラメータを使用して、明示的にスコープを定義することです。 Scope
パラメータに渡された値を Private
, Script
Global
と置き換えるだけです。
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> $afoo
プライベート/スクリプト/グローバル スコープ
スクリプトまたはグローバル スコープの定義および参照したい場合は、スコープ名とセミコロンで変数を先頭にすることができます。
例えば、変数 $a
をグローバル スコープに設定するには、a の前に $global:
を付けます。
$global:a = 'foo'
同じことは、スクリプト スコープの変数にもできます。
$script:a = 'foo'
変数が優先スコープに設定されたら、同じ方法でそれらを参照することになります。 また、定義されたスコープがローカル スコープの場合、スコープ プレフィスを除外できることに注意してください。
PS> $global:a = 'foo'PS> $global:afooPS> $afoo
Scriptblocks におけるスコープ
PowerShell にはスクリプトブロックという便利な構成があります。 スクリプトブロックを使用すると、コードのスニペットを移動して、ほぼどこでも実行することができます。
以下の例では、グローバル スコープで定義された変数が、スクリプト スコープで上書きされようとしているところに注目してください。 上で学んだように、子スコープが親スコープを参照することはできないため、これは機能しません。
この例では、$a
が scriptblock で変更されても、その scriptblock が script 子スコープであるので $a
のグローバル変数定義が変更されないことが示されています。
スクリプトのドットソース化 (ローカル スコープの交換)
PowerShell には、ドットソース化というコンセプトがあります。 これは、PS1 スクリプトを実行し、スクリプト スコープされるすべてのものを代わりにローカル スコープにする方法です。
PS1 スクリプトを参照する前にドット (.) を付けて実行することにより、スクリプトのコンテンツを「ドット ソース」して、すべてをローカル スコープに取り込むことが可能になります。
PowerShell コンソールで、$a
変数に値を設定し、次のようにこのスクリプトをドット ソース化します。 元の変数が上書きされたことに注意してください。 355>
Using the AllScope Property (Option)
特定のスコープでアイテムを操作する方法を見てきましたが、これまでのところ各アイテムはまだ単一のスコープで定義されています。
Set-Variable
コマンドレットで変数を定義するとき、変数を一度にすべてのスコープに配置することができます。 これを行うには、Option
パラメーターにAllScope
値を使用します。
デモンストレーションとして、Test.ps1 スクリプトを変更して、すべてのスコープに変数を設定するようにしました。
Set-Variable -Name a -Value 'Hello world!' -Option AllScopeWrite-Output $a
次に、グローバル スコープで $a に値が設定され、Test.ps1 スクリプトが実行されることを確認できます。 しかし、何の効果もないどころか、$a
の値は上書きされています。
AllScope
オプションは便利ですが、注意が必要です。 355>
Function Scopes
関数を実行すると、その関数内のすべてのコードはそれ自身の子スコープにあります。 関数スコープは、他のスコープと同じ子/親の動作に従います。
各関数に個別のスコープを持つことは良いアイデアです。 これは、アイテムの競合を心配することなく、アイテムに対してより良い制御を可能にします。
デモを行うには、以下の関数を PowerShell コンソールに直接コピー/貼り付けてください。
PS> Do-ThingPS> $var
Keep Items Private (Disabling Inheritance)
通常、親スコープで変数が定義されると、その変数は子スコープで定義されます。 しかし、おそらく、変数名を使用したいが、その変数名はセッションで実行されるスコープの 1 つですでに定義されているのでしょう。 その場合、別の変数名を選択するか、その変数をプライベート スコープで定義してプライベート変数にします。
競合を減らすためにスコープを使用する方法は、プライベート スコープを使用することです。 プライベート・スコープを使用すると、その特定の変数の継承が無効になります。
Test.ps1 スクリプトは、次のように $a の値を出力します。
Write-Output $a
以下では、グローバル スコープでプライベート スコープの変数を定義してから Test.ps1 スクリプトを実行していることがわかります。 通常、親スコープで変数を定義すると、その変数は子スコープでも使用できるようになりますが、privately-scoped 変数の場合はそうではありません。
以下の例では、Test.ps1 スクリプトを実行して作成されたスクリプトの子スコープが、親スコープで定義されたプライベート スコープの $a
変数を参照できないことがわかります。
グローバル スコープや Set-Variable
コマンドレットの AllScope
オプションとは異なり、プライベート スコープの変数はアイテムを区分けする優れた方法です。
スコープのベスト プラクティス
グローバル スコープで変数を定義したり、AllScope
オプションを使用するのが良い方法だと考えるのはよくあることです。 結局のところ、すべての変数はどこでも利用可能です。 スコープの複雑さを気にする必要はないのです。
スコープを使用しないようにする代わりに、以下のヒントに従います:
- 関数でスコープを指定する代わりに、パラメータを使用して必要な情報を関数に渡します。
- スクリプトからグローバル変数を定義する代わりに、
Write-Output
コマンドレットを使用して、コンソールから必要なときにすべてを出力し、変数に保存します。
ここでの要点は、スコープを受け入れ、スコープを回避しようとするのではなく、それを有利に使用することを学ぶことです。
参考資料
- 高度な関数と関数パラメーターについて
- PowerShellでの環境変数の使用