晴耕雨読

working in the fields on fine days and reading books on rainy days

[PowerShell] 関数とパイプラインの使い方

PowerShellにおける関数と引数、パイプラインからの入力、スクリプトブロックと高階関数について説明します。

関数

引数を指定する関数

PowerShellでは function キーワードを使用して関数を定義できます。return は不要です。

PS> function sayHello($name, $age) {
>>     "Hello {0}!" -f $name
>> }
PS> sayHello "world"
Hello world!

paramで引数を指定する関数

関数名の横で引数を指定する代わりに、paramキーワードを使って引数を指定することもできます。

PS> function sayHello {
>>     param($name, $age)
>>     "Hello {0}!" -f $name
>> }
PS> sayHello "world"
Hello world!

引数の型を指定する関数

引数の型を強制したい場合は、引数名の前に [int] などの型を追加します。paramキーワードを使う場合も同じです。

PS> function add([int]$a, [int]$b) {
>>     $a + $b
>> }
PS> add 7 3.14
10

引数を指定しない関数

引数を明示的に指定しないで引数にアクセスする場合は、配列 $args を使用します。

PS> function sayHello {
>>     "Hello {0}!" -f $args[0]
>> }
PS> sayHello "world"
Hello world!

引数の参照渡し

引数の参照渡しをする場合は、[ref] を使用します。 参照渡しされた関数で引数の値は、Value プロパティを介してアクセスすることができます。

PS> function swap([ref]$a, [ref]$b) {
>>     $tmp = $a.Value
>>     $a.Value = $b.Value
>>     $b.Value = $tmp
>> }
PS> $v1 = 123
PS> $v2 = 456
PS> swap ([ref]$v1) ([ref]$v2)
PS> $v1
456
PS> $v2
123

関数の戻り値

PowerShellの関数では、値を返す全ての文がそのまま返り値になります。 返り値を返す際には return は不要です。return 文は関数を終了するためだけに使用します (for文のbreakに相当します)。 関数内で戻り値を持った別の関数を呼ぶとき、その返り値を自分の関数の返り値にしたくない場合は [void] にキャストしておく必要があります。

PS> function sample {
>>     123
>>     2 * 3
>>     [void](4 * 5)
>>     "Hello!"
>> }
PS> sample
123
6
Hello!

パイプライン

パイプラインから入力を渡される関数

入力をパイプラインから渡したい場合は、$input を使用します。 入力が配列であっても対応できるように、foreach と組み合わせるのがおすすめです。

PS> function sayHello {
>>     foreach ($elem in $input) {
>>         "Hello {0}!" -f $elem
>>     }
>> }
PS> "world" | sayHello
Hello world!
PS> "Alice","Bob" | sayHello
Hello Alice!
Hello Bob!

begin, process, end でパイプライン入力の処理をする関数

関数がパイプラインを扱うために特化している場合、begin, process, end のキーワードを使うことで、関数の開始時に begin のブロックを実行し、各入力を process ブロックで処理し、関数の終了時に end ブロックを実行することができます。

PS> function sayHello {
>>     begin {
>>         $count = 0
>>     }
>>     process {
>>         $count++
>>         "Hello {0}! ({1})" -f ($_, $count)
>>     }
>>     end {
>>         "Bye!"
>>     }
>> }
PS> "Alice","Bob" | sayHello
Hello Alice! (1)
Hello Bob! (2)
Bye!

フィルタ関数

パイプライン入力から特定の条件を満たすものだけを抽出する関数を定義する場合は、filter キーワードで関数を定義します。

PS> filter Get-Odd {
>>     if ($_ % 2 -eq 1) {
>>         return $_
>>     }
>> }
PS> 1..10 | Get-Odd
1
3
5
7
9

パイプラインまたは引数から入力を受け取る

関数をコマンドレットとして作成して、関数に様々な属性を付与することで高度な関数を作ることができます。 高度な関数を作るには、関数の先頭で CmdletBinding 属性を付与します。 これにより、パラメータ属性を使用することができ、パラメータ値の検証などを要求することができます。 パラメータ属性で ValueFromPipeline=$true とすることで、入力を引数からでもパイプラインからでも受け付けることが可能になります。

PS> function sayHello {
>>     [CmdletBinding()]
>>     param(
>>         [Parameter(ValueFromPipeline=$true)] [string[]] $inputStrings
>>     )
>>     process {
>>         foreach ($inputString in $inputStrings) {
>>             "Hello {0}!" -f $inputString
>>         }
>>     }
>> }
PS> "Alice","Bob" | sayHello
Hello Alice!
Hello Bob!
PS> sayHello "Alice","Bob"
Hello Alice!
Hello Bob!

高階関数

スクリプトブロック (無名関数)

スクリプトブロックを使うことで、無名関数を定義することができます。 スクリプトブロックを呼び出す時は & を先頭に付けます。

PS> $answer = { 21 * 2 }
PS> & $answer
42

スクリプトブロックが引数を受け取るには param キーワードを使います。

PS> $add = { param($a, $b); $a + $b }
PS> & $add 3 4
7

スクリプトブロックを用いた高階関数

関数の引数としてスクリプトブロックを渡すことで、関数を使う関数(高階関数)を作ることができます。 以下は、foreach を自作の関数で実装する例です。

PS> function myForeach($block) {
>>     process { & $block $_ }
>> }
PS> 1..3 | myForeach { $_ * 2 }
2
4
6

以上です。

参考文献