PowerShellのForEach-Objectコマンドレットについて

powershell コンピュータ
powershell
ForEach-Objectはパイプラインで渡された配列の各要素を処理するコマンドレットになります。

エイリアス

ForEach-Objectエイリアス(Alias)は%になります。(以下ForEach-Objectはエイリアスの%で表記します。)

ファイル名の一覧を取得するサンプル

ls . | % { $_.Name }

ls(Get-ChildItem)でカレントディレクトリのファイルオブジェクトの一覧(配列など)をパイプラインで%(ForEach-Object)に渡します。{}内で各ファイルオブジェクトが一つずつ処理されます。(こちらの{}は-process {}の省略系になります)

$_はパイプラインで渡された配列などの一覧の要素になります。今回のサンプルではlsから出力されたファイルのファイルオブジェクトに当たります。ファイルオブジェクトのNameプロパティを取り出します。

{}内で変数や関数の戻り値を代入や引数などに用いない場合、コマンドレットの戻り値として扱われ、次のパイプラインがある場合コマンドレットに渡されます。

以下は一連のパイプラインの結果を$filesに代入するサンプルになります。

$files = ls . | % { $_.Name }

$filesには配列としてファイル名の一覧が代入されます。

結果をテキストファイルに出力する例

ls . | % { $_.Name } | Out-File output.txt

文字コードはPowerShellによって異なりますが、改行区切りのテキストファイルが出来上がります。

結果をクリップボードに出力する例

ls . | % { $_.Name } | Set-Clipboard

結果をメモ帳に貼り付けたり、Excelのセルに貼り付けたりすることが出来ます。

-begin,-process,-endオプションについて

前項のサンプルでは-beginと-endを省略しましたが、-begin{}が開始処理、-process{}が繰り返し処理、-end{}が終了処理になります。

以下のサンプルでは、0から4の値が表示されます。

@("1", "2", "3") | % -begin { "0" } -process { $_ } -end { "4" }

使い方として-begin{}には-process{}で実行する処理の前処理を-endは後始末を記述すると良いでしょう

-begin,-process,-endの実行される順番

@(1,2,3) | % -begin{"begin1"} { sleep 1; $_ } -end {"end1"} | % -begin{"begin2"} { Write-Host ("値:{0}" -f $_) } -end {"end2"}

結果

begin2
値:begin1
値:1
値:2
値:3
値:end1
end2

実行順番はbegin、process、endの順番で、初めの%(Foreach-Object)が完了後次の%(Foreach-Object)へ一括で渡されるのではなく、各要素ごと処理されていることが確認出来ます。一見するとbegin2とbegin1の出力順が逆の様に見えますが、実際の処理はbegin1→begin2の順番に処理されています。

単独オブジェクトを処理する

Windows.Fomrsのコントロールをnew()で生成し、sizeやlocationプロパティにセットしコントロールを返す処理をしています。

$txtbox1 = [System.Windows.Forms.TextBox]::new() % {
  $_.Size = "100,100"
  $_.Location = "10,10"
  $_
}

繰り返す処理は1回なのでForEach-Objectを使う意味は無さそうですが、プロパティの初期化を{}にまとめることが出来ます。

要素の追加及び削除

元の配列の要素は3つですがForEach-Objectを通すことで6つに増やすこととが出来ます。

@(1,2,3) | % { ($_ * 2 - 1); ($_ * 2) }

結果

1
2
3
4
5
6

同様に出力をコントロールすることで要素を削除することも出来ます。

@(1,2,3) | % { if ($_ -ne 2) { $_ } }
結果
1
3

要素の絞り込みであればForEach-ObjectよりもWhere-Objectを使う方法を検討した方が良いと思います。

複数のフィールドを持つデータを扱う

オブジェクトとプロパティを使えば複数のフィールドを持たせることが出来ます。

@("A","B","C") | % -begin{$i=0} { [PSCustomObject]@{"Index"=$i++; "Value"=$_} } 

結果

Index Value
----- -----
    0 A
    1 B
    2 C

PowerShellには汎用的に使えるPSCustomObjectがありますので、こちらオブジェクトを連想配列からキャストすることで生成します。

コメント