オブジェクト渡し

UNIXのシェルではパイプラインを流れるデータは一方通行であり、かつ、データはそのままデータとして流れていく。一方、PowerShell Coreではパイプラインを流れていくのはオブジェクトであり、複数のオブジェクトを流すことができる。ただのデータとして流れていくのではなく、明確に複数のオブジェクトとして流れていくという違いがある。

これはつまり、データが明確に区切りのある対象として送信されてくるということだ。PowerShell Coreではパイプラインで送られてくるオブジェクトごとに処理を記述できるようになっている。

PowerShell Coreは明示的にシェルやコマンドプロンプトに近いニュアンスを実現するように設計されているが、背後にはオブジェクト指向プログラミング言語の考え方が取り込まれており、細かくなってくるとオブジェクト指向言語として考えた方が理解しやすくなる。

こうした特徴を備えているため、PowerShell Coreの関数は機能的に強力だ。関数の状態でパイプラインを流れてくる複数のオブジェクトを処理するコードが書けるようになっている。今回はこの書き方を取り上げる。

関数でのオブジェクト渡し

関数でオブジェクトを処理する場合、Begin、Process、Endという3つのブロックについて知る必要がある。

オブジェクト渡しで使われるブロック

function 関数名 {
    begin { 処理 }
    process { 処理 }
    end { 処理 }
}

ブロックはそれぞれ次の役割を持っている。

ブロック 内容
Begin 関数の開始時に1回だけ実行される
Process パイプラインを流れてくるオブジェクトごとに1回ずつ実行される。パイプラインを流れてくるオブジェクトは$_に割り当てられる
End 関数の終了時に1回だけ実行される。Processブロックが使われていない場合、$inputからすべてのオブジェクトにアクセスできる。Processブロックが使われている場合、$inputには何も入っていない

BeginブロックとEndブロックは名前のとおり関数が実行される最初の段階と最後の段階で1回だけ実行される。Processブロックはオブジェクトの入力があるたびに実行される。つまり、パイプラインを経由して渡ってくるデータを処理する場合、Processブロックに処理を書けばよいことになる。

Processブロックでは$_という自動変数に渡ってくるオブジェクトが代入される仕組みになっている。つまり、Processブロックで$_を使って処理を書くというのが、Processブロックでの主なコーディング作業ということになる。

たとえば次のサンプルは1から5までの整数を作成した関数に流し込んだ場合の実行例だ。Processブロックが入力される数字の分だけ動作していることがわかる。

Processブロックが入力されてくるオブジェクトの分だけ動作していることがわかる

PS /Users/daichi> function Do-PipelineTest
>> {
>>     begin {"begin: $input"}
>>     process {"process: $_"}
>>     end {"end: $input"}
>> }
PS /Users/daichi> 1,2,3,4,5 | Do-PipelineTest
begin:
process: 1
process: 2
process: 3
process: 4
process: 5
end:
PS /Users/daichi>

Endブロックでは$inputという変数が扱えることも覚えておきたい。関数の中でProcessブロックが使われていない場合、Endブロックで$inputという変数から入力されてくるすべてのオブジェクトにアクセスできる。

Processブロックがない場合、Endブロックで$inputを使って入力されたオブジェクトにアクセスできる

PS /Users/daichi> function Do-PipelineTest
>> {
>>     begin {"begin: $input"}
>>     end {"end: $input"}
>> }
PS /Users/daichi> 1,2,3,4,5 | Do-PipelineTest
begin:
end: 1 2 3 4 5
PS /Users/daichi>

ただし、この機能はProcessブロックが存在する場合には機能しない。次のようにProcessブロックの中で$_を使っていないとしても、Processブロックが存在するとEndブロックで$inputにアクセスしてもなにも表示されなくなる。

Processブロックが存在する場合、Endブロックで$inputにアクセスしても何も表示されない

PS /Users/daichi> function Do-PipelineTest
>> {
>>     begin {"begin: $input"}
>>     process {"process: "}
>>     end {"end: $input"}
>> }
PS /Users/daichi> 1,2,3,4,5 | Do-PipelineTest
begin:
process:
process:
process:
process:
process:
end:
PS /Users/daichi>

最低でもProcessブロックでオブジェクトごとの処理を記述する、ということを覚えておきたい。

フィルタ

PowerShell Coreには関数の特別なスタイルとしてフィルタという機能が用意されている。フィルタは基本的に次のように定義する。

フィルタの書き方

filter 関数名
{
    処理
}

これは要するに次のように記述したのと同じだ。フィルタとして定義することでどのように機能するのかがよりわかりやすくなる。

関数でフィルタと同じ書き方をした場合

function 関数名
{
    Process
    {
        処理
    }
}

フィルタを使って処理を書くと、たとえば次のような感じになる。

フィルタを使った場合

PS /Users/daichi> filter Do-FilterTest([switch]$upper,[switch]$lower)
>> {
>>     if ($upper) {$_.ToUpper()}
>>     elseif ($lower) {$_.ToLower()}
>>     else {$_}
>> }
PS /Users/daichi> "a","B","c","D","e" | Do-FilterTest
a
B
c
D
e
PS /Users/daichi> "a","B","c","D","e" | Do-FilterTest -upper
A
B
C
D
E
PS /Users/daichi> "a","B","c","D","e" | Do-FilterTest -lower
a
b
c
d
e
PS /Users/daichi>

上記サンプルではパラメータとして-upperと-lowerが指定できるようになっており、-upperが指定されると文字列を大文字にして、-lowerが指定されると文字列を小文字にして出力が行われる。何も指定がなければそのまま出力される。

フィルタとして定義されているので、入力されるオブジェクトに対して何らかの処理を行うものだ、ということがよくわかる。

パラメータとオブジェクト

これまで3回に渡ってPowerShell Coreの関数を取り上げてきた。これで関数の名前の付け方、各種パラメータ(名前付きパラメータ、ポジションパラメータ、スイッチパラメータ、パラメータ渡し)、オブジェクト渡し、フィルタを説明したことになる。あとは関数におけるスコープあたりを取り上げれば、PowerShell Coreの提供している関数の主な機能は網羅することになる。

PowerShell Coreでは関数はコマンドレットに相当するような重要な機能である。関数が定義できるようになると、再利用性の高いコードを記述しやすくなる。PowerShell Coreの関数はシェルの関数とも汎用プログラミング言語の関数ともちょっと違った側面を持っているので、そのあたりも含めていろいろ試してもらえればと思う。

参考資料