Code Aquarium

minazoko's blog -*- 水底のブログ -*-

(PowerShell) パイプラインはProcessで

2つのパススルー関数

サンプルコードその1

PowerShellの関数は一風変わっていて、内部に3つの役割をもったブロックを書くことができます。

function pass1 ([string] $Tag, [int]$MSec = 0){
    begin{
        Write-Host "BEGIN pass1 $Tag"
    }
    process{
        [System.Threading.Thread]::Sleep($MSec)
        Write-Host "$Tag $_"
        $_
    }
    end{
        Write-Host "END pass1 $Tag"
    }
}

process ブロックはパイプライン上流からのデータを $_ で受け取り、そして下流へ処理結果をひとつづつ流します。begin と end はパイプライン処理の前処理と後処理です。
関数 pass1 は上流からのデータを何も加工せずのそまま下流へ流します。
3つのブロックそれぞれの実行タイミングを確認するためにWrite-Host で標準出力への出力を行います。

さて、実行結果です。
パイプラインでpass1を3つ繋ぎ、どの関数が実行されたかわかるようタグを引数で渡します。また、待ち行列のキューイングが発生するかどうか見るために、真ん中の呼び出しだけ1秒のウェイトをかけてみます。

PS C:\> 1..3 | pass1 "<1st>" | pass1 "<2nd>" 1000 | pass1 "<3rd>" > $null
BEGIN pass1 <1st>
BEGIN pass1 <2nd>
BEGIN pass1 <3rd>
<1st> 1
<2nd> 1
<3rd> 1
<1st> 2
<2nd> 2
<3rd> 2
<1st> 3
<2nd> 3
<3rd> 3
END pass1 <1st>
END pass1 <2nd>
END pass1 <3rd>

3つのタグが出力される様子をみると、1st,2nd,3rdがパイプラインを使いデータ要素を1つずつ順繰りに処理しているのが分かります。例え途中にウェイトがかかるような処理があったとしても、キューイングして上流が先に処理を終わらせるようなことはないようです。
beginとendのタイミングは初め、おや?と思いましたが、このようなパイプラインの性質を考えると、このタイミングにならざるを得ないですね。

サンプルコードその2

次は、begin,process,endを書かない関数スタイルです。上流からのデータはEnumeratorである暗黙変数 $input で参照することができます。この $input をパイプ処理で流してその結果を下流に流す戦略です。

function pass2 ([string] $Tag, [int]$MSec = 0){
    Write-Host "BEGIN pass2 $Tag"
    $input | %{
        [System.Threading.Thread]::Sleep($MSec)
        Write-Host "$Tag $_";
        $_
    }
    Write-Host "END pass2 $Tag"
}

そして実行結果

PS C:\> 1..3 | pass2 "<1st>" | pass2 "<2nd>" 1000 | pass2 "<3rd>" > $null
BEGIN pass2 <1st>
<1st> 1
<1st> 2
<1st> 3
END pass2 <1st>
BEGIN pass2 <2nd>
<2nd> 1
<2nd> 2
<2nd> 3
END pass2 <2nd>
BEGIN pass2 <3rd>
<3rd> 1
<3rd> 2
<3rd> 3
END pass2 <3rd>

今度は、1stの処理がすべて終わってから2ndが始まり、2ndがすべて終わってから 3rdが始まっています。これはパイプラインに期待する動作ではありませんね。

パイプライン入力の $input は Enumerator です。それをパイプに流したら、その結果も Enumerator になり、一要素ずつの順繰り処理が下流に流れるような錯覚をもっていたのですがどうやらそれは誤りだったようです。
pass2の内部のパイプライン処理の出力は Enumerator ではなく、 object[] という配列になってしまうようです。
パイプライン上の関数内にネストしたパイプラインを作っても、その出力を上位のパイプラインの流れに戻すことはできないという事になりますね。
うーむ...ちょっと、いやかなり残念。