💙

PowerShellのファンクションで引数やパイプラインから値を受け取る

2021/01/24に公開

はじめに

PowerShell は、ファンクションを作れます。ファンクションを呼び出すときに値を渡す方法としてファンクションの引数で渡す方法とパイプラインで渡す方法があります。しかし、コマンドレットや、パラメータの属性などがあり、どこで何をかけばどうなるか、がうまくまとまったメモが見つけられませんでした。

この記事に書かれているのは何か?

この記事では、ファンクションに引数でデータを渡す方法と、ファンクションにパイプラインでデータを渡す方法を書きます。

この記事の前提

実機動作は PowerShell 5.1 で確認しています。

ドキュメントは執筆時点の latest である 7.1 で確認しています。

PS tmp> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.19041.610
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.610
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

引数で渡す方法

ファンクション内の param で引数を指定します。

function f {
    param($Item)
    Write-Host "Item: $Item"
}

次のように引数でファンクションに値や配列などのデータを入力できます。

Item: の行からファンクション内でデータを参照できることがわかります。

PS tmp> f 1
Item: 1

引数に配列を指定した場合、 Item が配列となり、ファンクション内で引数のデータを参照できることがわかります。

PS tmp> f @(1,2)
Item: 1 2

なお、この方法ではパイプラインで $Item 変数を得ることができません。この方法を使う場合はパイプラインを使わないことを想定しましょう。

PS tmp> 123456 | f
Item:

パイプラインで渡す方法: 変数 $PSItem で受け取る場合

process ブロックを定義することでパイプラインを利用できるようになります。より厳密には、パイプラインが配列でなければ受け取ること自体は可能ですが、公式のドキュメントの推奨の通り、配列で読めるようにしておくほうがよいし、パイプラインを利用するために process ブロックを定義しない理由がないため、process ブロックを書く、のがよいです。

次のようにファンクションを定義します。

function f {
    process {
        Write-Host "PSItem: $PSItem"
    }
}

次のようにパイプラインでファンクションにデータを入力できます。 PSItem: の出力回数が Process ブロックの実行回数です。配列をパイプラインでファンクション f に渡すと、その要素数分 Processブロックが実行されることがわかります。

なお、変数 $PSItem は自動変数です。パイプラインオブジェクトの中で、現在のオブジェクトです。

次の出力はパイプラインオブジェクトとして整数を指定した場合の実行結果です。

PS tmp> 123456 | f
PSItem: 123456

次の出力はパイプラインオブジェクトとして配列を指定した場合の実行結果です。配列の各要素ごとに Process ブロックが実行されている様子がわかります。

PS tmp> @(1,2) | f
PSItem: 1
PSItem: 2

しかし、この方法では引数を指定しても $PSItem に値はありません。この方法を使う場合は引数の指定に配慮しましょう。

PS tmp> f 1
PSItem:

パイプラインで渡す方法: 変数 $Var で受け取る場合

パラメータ属性 ValueFromPipeline を指定することで、パイプラインで渡されたオブジェクトを $PSItem 以外の方法で参照できます。

function f {
    param ([Parameter(Mandatory,ValueFromPipeline)]$Item)
    process {
        Write-Host "Item  : $Item"
        Write-Host "PSItem: $PSItem"
    }
}

この方法では、パイプラインでも引数でもファンクションにデータを入力することがわかります。

PS tmp> 123456 | f
Item  : 123456
PSItem: 123456
PS tmp> @(1,2) | f
Item  : 1
PSItem: 1
Item  : 2
PSItem: 2

引数で指定する場合は $PSItem に値がないことに注意しましょう。

PS tmp> f 1
Item  : 1
PSItem:

なお、この方法では引数に配列を指定しても配列の要素数分の実行は行われません。

次の出力では、引数に配列を指定したとき Process ブロックが1回だけ実行されていることがわかります。

PS tmp> f @(1,2)
Item  : 1 2
PSItem:

ちなみに、このファンクション fGet-Help すると次のようになります。

PS tmp> get-help f
...(途中省略)...
構文
    f [-Item] <Object>  [<CommonParameters>]

変数の型を次のように変えてみます。

function f {
    param ([Parameter(Mandatory,ValueFromPipeline)][Object[]]$Item)
    process {
        Write-Host "Item  : $Item"
        Write-Host "PSItem: $PSItem"
    }
}

Get-Help の出力は次のようになり、これは Remove-Item などの構文における [-Path] <String[]> と同じ構成となります。

PS tmp> get-help f
...(途中省略)...
構文
    f [-Item] <Object[]>  [<CommonParameters>]
PS tmp> get-help Remove-Item
...(途中省略)...
構文
    Remove-Item [-Path] <String[]> [-Confirm] [-Credential <PSCr
    edential>] [-Exclude <String[]>] [-Filter <String>] [-Force]
     [-Include <String[]>] [-Recurse] [-Stream <String[]>] [-Use
    Transaction] [-WhatIf] [<CommonParameters>]

Remove-Item では次のようにファイルを削除できます。

PS tmp> @("a.txt", "b.txt") | remove-item

Remove-Item では次のようにファイルを削除できません。これは、本記事と同様の動きです。

PS tmp> remove-item a.txt b.txt
Remove-Item : 引数 'b.txt' を受け入れる位置指定パラメーターが見
つかりません。
発生場所 行:1 文字:1
+ remove-item a.txt b.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Remove-Item
   ]、ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Micro
   soft.PowerShell.Commands.RemoveItemCommand

Remove-Item の引数には、削除対象のファイルを指定できますが、配列を指定して次のように複数指定できます。おそらく、内部で引数の型が配列であるか判定し、配列であれば各ファイルについて削除するのでしょう。

PS tmp> remove-item @("a.txt", "b.txt")

まとめ

  • ファンクションを呼び出すとき、引数でデータを入力する方法を紹介しました。
  • ファンクションを呼び出すとき、パイプラインでデータを入力する方法を紹介しました。
  • パイプライン、引数のどちらでもデータを入力する方法を紹介しました。

Remove-Item などの PowerShell が備える機能は、コマンドレットへのパイプライン、引数での指定ができることがあります。この記事では、Remove-Itemを題材に標準で備えるようなコマンドレットと同じ実装ができる方法を紹介しました。

参考資料

Chapter 9 - Functions

https://docs.microsoft.com/en-us/powershell/scripting/learn/ps101/09-functions?view=powershell-7.1

Pipeline input comes in one item at a time similar to the way items are handled in a foreach loop. At a minimum, a process block is required to process each of these items if you're accepting an array as input. If you're only accepting a single value as input, a process block isn't necessary, but I still recommend specifying it for consistency.

Parameter Attribute Declaration

https://docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/parameter-attribute-declaration?view=powershell-7.1

About Automatic Variables

https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-7.1

Conceptually, these variables are considered to be read-only. Even though they can be written to, for backward compatibility they should not be written to.

Discussion