結論
ps1ファイル内で別のps1ファイルを呼び出すときは呼び出し演算子&
を使いましょう。
# 引数なし
& "./util.ps1"
# 引数あり
& "./util.ps1" $hoge $huga
はじめに
あるps1ファイル内で別のps1ファイルの処理を呼び出す状況を考えます。
共通機能を切り出して利用するイメージです。
以下test.ps1
から共通処理util.ps1
を呼び出すこととします。
単純な例
PowerShell内ではコマンドをそのまま記述して実行できるため、「.」を用いた ドットソース を使ってコンソールから実行するように別ps1ファイルを呼び出すことができます。
Write-Host "before"
. ./util.ps1
Write-Host "after"
Write-Host "util"
結果
>before
>util
>after
test.ps1
内の処理⇒util.ps1
の処理⇒test.ps1
内の後続の処理の順に実行されます。
期待通りの結果で、特に問題ないように見えます。
異常になる例
次のような例はどうでしょうか。
$hoge = "aaa"
Write-Host $hoge
. ./util.ps1
Write-Host $hoge
$hoge = "bbb"
変数$hoge
をutil.ps1
呼び出しの前後でコンソールに出力します。
util.ps1
は変数$hoge
を別途に宣言しているだけで、test.ps1
の処理には影響しないと予想されます。
期待される結果
>aaa
>aaa
しかし、実際には以下のようになります。
実際の結果
>aaa
>bbb
引数で渡したわけでもない変数の値が勝手に更新されてしまいました。
原因
公式ドキュメントに答えが書いてあります。
PowerShell では、コマンドの前にドットとスペースを入力することによってコマンドを開始すること。 ドット ソースのコマンドは、新しいスコープではなく、現在のスコープで実行されます。 コマンドが作成するすべての変数、エイリアス、関数、またはドライブは、現在のスコープで作成され、コマンドが完了すると使用できるようになります。
要は、ドットソースで呼び出すと処理が同一スコープ内で実行されてしまうのがポイントです。
$hoge = "aaa"
. ./util.ps1
Get-Variable -Name "hoge*" -Scope Local
実際に上のようなコードで、util.ps1
のhogeで始まるローカル変数を調べると、次のような結果になります。
Name Value
---- -----
hoge aaa
ドットソースで起動したことにより、呼び出し元とスコープを共有してしまい、util.ps1
内で宣言していないはずの変数にアクセスできてしまったというわけです。
対策
呼び出し演算子&
を使えばOKです。呼び出し元とは別のスコープで実行できます。
& ./util.ps1
経緯
「はじめに」の設定の通りで、共通処理を実装していてこの問題に直面しました。ドットソース呼び出しをしていたために意図せず変数の内容が変わってしまう事象が発生しました。共通処理の呼び出し自体は成功するせいで、一見問題なさそうに思ってしまうんですね……。
$date
や$path
など一般性の高い名前の変数で事故を起こしやすいため、スコープを分けることが必要でした。
ちょっと調べたところではあまりまとまった情報がなかったため記事にしました。
最初はInvoke-Expression
を代わりに使うという結論でしたが、コメントで教えていただいた呼び出し演算子に変更しました。コメントありがとうございました!
補足
Import-Module
も利用できるそうですが、本記事では扱いません。