配列要素ごとの処理 ループとパイプライン

配列に含まれる各要素に対して処理を行う場合の記述方法にはいくつかのバリエーションがあります。

(対象は配列に限りませんが・・・)
自分ではCやC++などの言語に慣れているので、
何も考えなければ for ループなどで書こうとするのですが、
パイプラインを使った方が簡潔に記述できる場合があります。
Foreach-Object に % という短く書ける alias が用意されているくらいですから、
PowerShell の作者にはパイプラインをどんどん使って欲しいという
意図もあるのかもしれません。
ここでは、例としてすべての要素に対して同じ処理を行うのではなく、先頭要素にのみ別の処理を行う場合を取り上げてみます。
入力として

'testad.example.co.jp'

のような文字列が変数 $adserver に与えられて、そこから

LDAP://testad/cn=Computers,DC=example,DC=net

という文字列を生成し変数 $tgtstr に格納する場合を例にとります。
私のようにパイプに慣れていない場合には、たとえば次のような記述が思い浮かびます。

$words = $adserver.split('.')
$tgtstr = "LDAP://{0}/cn=Computers" -f $words[0]
for ($idx = 1; $idx -lt $words.count; $idx++)
{
    $tgtstr += ",DC={0}" -f $words[$idx]
}

splitでピリオドを区切りとして分割された各単語要素は変数 $words に文字列配列として格納されます。先頭要素にインデックス0でアクセスして個別の処理を行い、1から最後までの$wordsの各要素にインデックス番号でアクセスしながら先頭とは別の共通処理を行います。これで動作としては問題ないのですが、インデックス用の変数 $idx を使うところが冗長に思えます。そこで、配列の先頭以外の要素をパイプラインに流して処理する方法を用いると次のように記述することができます。

$words = $adserver.split('.')
$tgtstr = "LDAP://{0}/cn=Computers" -f $words[0]
$words[1..($words.length – 1)] | % { $tgtstr += ",DC={0}" -f $_ }

パイプ記号 | の後ろの % は ForEach-Object コマンドレットの別名です。この例のように先頭要素を省くのではなく、先頭から指定個数要素のみを処理する場合や末尾から指定個数要素のみを処理する場合には、流す側の配列インデックスでの指定を行わなくとも、Select-Object の First および Last パラメターで指定することも可能です。

PS >1,2,3,4,5 | select -First 3 | % { Write-Host $_ }
1
2
3
PS>

また Where-Object を用いて何かの条件で対象を選別することも可能です。

PS >1,2,3,4,5,6 | where { $_ % 3 -eq 0 } | % { Write-Host $_ }
3
6
PS>

さらに分割した文字列要素を変数 $words にいったん格納することも避けるためには次のような記述も可能です。

$adserver.split('.') | % -begin {$tgtstr = ''} `
{
    if ($tgtstr -ne '') {$tgtstr += ",DC={0}" -f $_} 
    else {$tgtstr = "LDAP://{0}/cn=Computers" -f $_}
}

-begin で指定される ForEach-Object コマンドレットのパイプライン初期化時に実行されるスクリプトブロックにおいて出力用変数 $tgtstr の初期化を行います。それに続く、各要素ごとに実行されるスクリプトブロックの初回においては $tgtstr が空白なのでifによる判別で LDAP で始まる部分を生成する先頭要素固有の処理が実行され、それ以降は共通の処理が繰り返されることになります。
ただし最後の記述例とその前の記述例のどちらが望ましいかは、条件によって異なると考えられます。この例の場合であれば差がないでしょうが、大きなファイルからデータを読み込むような場合には、すべてメモリ上に読み終わってからそれ以降の処理が行われるか、逐次的に処理が行われるかの差が発生する場合があるのかな?

コメント

タイトルとURLをコピーしました