Windows PowerShellのFunction入門 (自動変数編)

この記事はWindows PowerShellのFunctionが引数を受け取る際、自動変数をの$args$_$inputを使って読み出す話です

書いておいてなんですが (というか書いて実感したのですが)、Begin/Process/Endブロックを書かない (Processブロックのみの) Filterを書く時に$_を使い、それ以外の場合は自動変数を使わずにParamキィワードを使ったほうが良いです


引数名を指定せずに渡した値は$argsに配列として格納されます

Function SampleFunction1 {
    $args.GetType().Name;
    $args.Length;
    $args[0];
    $args[0].GetType().Name;
    $args[1];
    $args[1].GetType().Name;
}

PS> SampleFunction1 'a'  1;
Object[]
2
a
String
1
Int32

Pipelineから渡された値の受け取りは若干複雑です

Processブロックがある場合、$_にその時のpipelineから渡された値が格納されます

$_Processブロック開始前 (Beginブロック内) には存在していませんが、Endブロックで読み出せます (必然的にpipelineから渡された最後の引数が格納されています)

Function SampleFunction2 {
    Begin{
        'Begin';
        # $_; 実行するとエラーになる
    }
    Process{
        'Process';
        $_;
    }
    End{
        'End';
        $_;
    }
}

PS> 1,2,3 | SampleFunction2
Begin
Process
1
Process
2
Process
3
End
3

次に、pipelineの値はArrayList+ArrayListEnumeratorSimpleとして$inputに格納されます

Windows PowerShellではArrayList+ArrayListEnumeratorSimpleは.NETのIEnumerator インターフェイス (System.Collections)として実装されています

Windows PowerShell: These members are defined in the interface System.IEnumerator, which is implemented by the types identified below.

(中略)

Windows PowerShell: For $input, this type is System.Collections.ArrayList+ArrayListEnumeratorSimple.

$inputBeginブロックでも読み出せますが値は入っておらず、何も入っていません

# 値は読み出せないが存在はしているので GetType() で型の名前は参照できる
Function SampleFunction3 {
    Begin{
        $input;
        $_.GetType().Name;
    }
}

PS> 1,2 | SampleFunction3
ArrayListEnumeratorSimple

Processブロックがあった場合、$_と同じ値を読み出せますが、1回読み出すと読み出せなくなります

Function SampleFunction4 {
    Process{
        $_;
        $input;
        $input;
    }
}
PS> 1,2 | SampleFunction4
1
1
2
2

ProcessブロックがなくEndブロックがあった場合、$inputは全てのpipelineから渡された値を配列として持っていますが、1回読み出すと読み出せなくなります

Function SampleFunction5 {
    End{
        $input;
    }
}

Function SampleFunction6 {
    End{
        $input;
        $input; # 2回目は読み出せないので、1回のみの場合と結果が同じになる
    }
}

PS> 1,2 | SampleFunction5
1
2
PS> 1,2 | SampleFunction6
1
2

ProcessブロックとEndブロックがあった場合、Processブロック内で読み出したかどうかに関わらず消費され、Endブロックでは読み出せなくなっています

Function SampleFunction7 {
    Process{}
    End{
        Write-Host 'End';
        $input; # Processブロックがあるため何も読み出せない
    }
}

PS> 1,2 | SampleFunction7
End

ここまで長々説明しておいてなんですが、$_Begin/Process/Endブロックを書かない (Processブロックのみの) Filterをさっと書いてその場で処理する場合には便利ですが、$inputは癖があり、メンテナンス時にバグを作りこみそうな気がしますので自動変数は極力使わないのがよかろうと思います