久しぶりにPowerShell関連の話題です。2chのスレッド「Windows PowerShell (正式版リリース)1.0」でのパイプの話題を受けて、NyaRuRuさんが「PowerShell で SIGPIPE 連鎖」で以下のようなコードを書いています。
PowerShell 1.0 filter yes { while($true){ "y" } } filter take([int] $n) { if(0 -gt --$n) { break } $_ }こうやって,
yes | take 10000 | take 1これがちゃんと 1 行だけ出力して止まる.SIGPIPE 連鎖っぽい.すげえ.
僕も以前似たようなことを試して引っかかったのですが、上記のコードではyesフィルタは1回しか値を返してないと見せかけて、2回返しています。わかりやすいようにyesフィルタを
filter yes { while ($true) { "say" | Out-Host "y" } }
として実行すると、
PS > yes | take 10000 | take 1 say y say
と出力されます。フィルタは基本的にprocess節のみの関数なのですが、process節に入るということはすでにパイプから値を取っていることになります。なので、上記のtakeフィルタでは
- パイプから値を取る
- 終了判定をする
- 値を返す
という動作を繰り返すことになり、終了条件を満たしたときにはすでに一個余分に値を取ってしまっているというわけです。
対策として、takeはフィルタではなく関数で以下のように定義します。
function take($n) { begin { if (0 -ge $n) { break } } process { $_ if (0 -ge --$n) { break } } }
関数のprocess節内ではパイプからの値はすでに取得してしまっているので終了判定は最後に持っていき、さらにそもそもprocess節に行くべきかどうかを判定するためのbegin節を追加します。実行すると
PS > yes | take 10000 | take 1 say y
となります。余分なsayがなくなりました。
ここまで書いておいてなんですが、僕も今回書いたことについてはあまり自信がありません。こういった、パイプ関連の動作について詳しく述べている文献は無いものでしょうか。
追記(01/15)ラベル:プログラミング PowerShell