PowerShellの関数をコピーしたりラップしたりする

PowerShellで関数の呼び出しの前後に処理を追加したいとき(ラップすると表現するんですかね?)にハマったのでメモ。

こんなことがしたい。

function hoge{
    ls
}

function hoge{
    echo "lsを呼びます"
    hoge #古いhogeを呼びたい・・・
}

これは当然のことながらhogeを永遠に再起して無限ループになります。

うまくいかない例

C#などでは関数の参照(デリゲート)を変数に入れておいたりすれば後からそれを呼び出せます。PowerShellではうまく行きません。

function hoge{
    ls
}
$a = hoge
# この$aは「hogeの呼び出し結果」が格納されているだけ!
$b = { hoge }
&$b #=> (lsの結果)
# スクリプトブロックを使っていて呼び出すことができる。
function hoge{
    echo "piyo"
}
&$b #=> piyo
# 置き換え後の結果が表示されてしまう・・・

と、上のような方法ではうまくできません。PowerShellはダイナミックスコープのため、実行されたときのhogeの関数を呼び出してしまうためです。

ではどうするか

関数の中身を変数としてアクセスする方法があるので、それを利用するだけです。

$c = $function:prompt
&$c

PowerShellではFunctionをファイルシステムのように扱える「プロバイダ」が用意しているため、上のように$function:promptとすると変数のようにアクセスできます。 なんなら、$function:hoge = "echo test;"+$function:hogeのように文字列として強引に処理をねじ込むことも可能です。

ちなみに、一部の記号などを含む名前の場合、波括弧でくくる必要があります。

# 「-」が区切りと判断され、New関数の中身を見てしまう
function New(){ echo "new function!" }
echo $function:New-Guid #=> echo "new function!"-Guid

# {}でくくる
echo ${function:New-Guid} #=> New-Guid関数の中身

プロバイダについてはここらへんに詳しく書いてあります。 プロバイダとドライブ - Windows PowerShell | ++C++; // 未確認飛行 C

結構検索しても見つからなくて大変でしたが、PowerShellは十分高機能なので大抵のことはできるようになっているので、頑張って探せばきっと答えはある・・・