問題-一般的な説明
about_pipelines (help pipeline
)powershellは、パイプラインでオブジェクトを1つずつ送信します¹。したがって、Get-Process -Name notepad | Stop-Process
はパイプを一度に1プロセスずつ送信します。
サードパーティのCmdLet(Do-SomeStuff)があり、どのような方法でも変更または変更できないとしましょう。 Do-SomeStuffは、文字列の配列が渡された場合、または単一の文字列オブジェクトが渡された場合、異なる動作をします。
Do-SomeStuffは単なる例であり、ForEach-Object
、Select-Object
、Write-Host
(またはパイプライン入力を受け入れる他のCmdLet)に置き換えることができます
この例では、Do-SomeStuffは配列内の個々のアイテムを一度に1つずつ処理します。
$theArray = @("A", "B", "C")
$theArray | Do-SomeStuff
完全な配列を1つのオブジェクトとしてDo-SomeStuffに送信する場合は、次のようなものを試してください。
@($theArray) | Do-SomeStuff
しかし、PowerShellは新しい単一項目配列を「無視」するため、期待される結果を生成しません。
では、どのようにして$theArray
を一度に1つのコンテンツ項目ではなく単一の配列オブジェクトとしてパイプに渡すように「強制」するのでしょうか。
問題-実用例
以下に示すように、Write-Host
の出力は、配列を渡した場合、または配列の個々の項目を一度に1つずつ渡した場合は異なります。
PS C:\> $theArray = @("A", "B", "C")
PS C:\> Write-Host $theArray
A B C
PS C:\> $theArray | foreach{Write-Host $_}
A
B
C
PS C:\> @($theArray) | foreach{Write-Host $_}
A
B
C
$theArray | foreach{Write-Host $_}
を取得してWrite-Host $theArray
と同じ出力を生成するにはどうすればよいですか?
脚注
文字列の通常の配列
PS C:\> @("A", "B", "C").GetType().FullName
System.Object[]
Foreach-Objectにパイプされる文字列の通常の配列
PS C:\> @("A", "B", "C") | foreach{$_.GetType().FullName}
System.String
System.String
System.String
配列内の各文字列は、ForEach-Object CmdLetによって一度に1つずつ処理されます。
配列の配列。「内部」配列は文字列の配列です。
PS C:\> @(@("A", "B", "C"), @("D", "E", "F"), @("G", "H", "I")) | foreach{$_.GetType().FullName}
System.Object[]
System.Object[]
System.Object[]
配列内の各配列は、ForEach-Object CmdLetによって一度に1つずつ処理され、入力からの各サブ配列のコンテンツは、配列であっても1つのオブジェクトとして処理されます。
短い答え:単項配列演算子を使用する_,
_:
_,$theArray | foreach{Write-Host $_}
_
長答:@()
演算子について理解しておくべきことが1つあります。コンテンツが単なる表現であっても、常にコンテンツをステートメントとして解釈します。次のコードを検討してください。
_$a='A','B','C'
$b=@($a;)
$c=@($b;)
_
PowerShellで省略することができますが、ここで明示的に文の終わりマーク_;
_を追加します。 _$a
_は3つの要素の配列です。 _$a;
_ステートメントの結果は何ですか? _$a
_はコレクションなので、コレクションを列挙し、各アイテムをパイプラインで渡す必要があります。したがって、_$a;
_ステートメントの結果は、パイプラインに書き込まれた3つの要素です。 @($a;)
は、元の配列ではなく3つの要素を参照し、それらから配列を作成します。したがって、_$b
_は3つの要素の配列です。同じ方法_$c
_は、同じ3つの要素の配列です。したがって、@($collection)
と記述すると、単一要素の配列ではなく、_$collection
_の要素をコピーする配列を作成します。
コンマ文字は、データを配列にします。パイプラインで配列を配列として処理するには、各配列要素を個別に操作する代わりに、データを括弧で囲む必要があります。
これは、配列内の複数のアイテムのステータスを評価する必要がある場合に便利です。
次の関数を使用
function funTest {
param (
[parameter(Position=1, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[alias ("Target")]
[array]$Targets
) # end param
begin {}
process {
$RandomSeed = $( 1..1000 | Get-Random )
foreach ($Target in $Targets) {
Write-Host "$RandomSeed - $Target"
} # next target
} # end process
end {}
} # end function
次の例を考慮してください:
配列を括弧で囲むだけでは、関数が値の配列を1つのプロセス呼び出しで処理することを保証しません。この例では、配列の各要素の乱数の変化を確認します。
PS C:\> @(1,2,3,4,5) | funTest
153 - 1
87 - 2
96 - 3
96 - 4
986 - 5
先頭のカンマを追加するだけでも、関数が1回のプロセス呼び出しで値の配列を処理することを保証しません。この例では、配列の各要素の乱数の変化を確認します。
PS C:\> , 1,2,3,4,5 | funTest
1000 - 1
84 - 2
813 - 3
156 - 4
928 - 5
先頭のコンマと括弧内の値の配列では、関数のforeachコマンドが活用されているため、乱数が同じままであることがわかります。
PS C:\> , @( 1,2,3,4,5) | funTest
883 - 1
883 - 2
883 - 3
883 - 4
883 - 5
あなたのプロセスが機能であることを気にしないなら、昔ながらの解決策があります。
例:PSRemoting接続を使用せずに別のシステムで再度構築できるように、配列をクリップボードにコピーしたい場合。したがって、「A」、「B」、および「C」を含む配列を文字列に変換する必要があります:リテラル配列の代わりに@( "A"、 "B"、 "C")...
したがって、これをビルドします(他の理由から最適ではありませんが、トピックにとどまります)。
# Serialize-List
param
(
[Parameter(Mandatory, ValueFromPipeline)]
[string[]]$list
)
$output = "@(";
foreach ($element in $list)
{
$output += "`"$element`","
}
$output = $output.Substring(0, $output.Length - 1)
$output += ")"
$output
配列をパラメーターとして直接指定すると機能します。
Serialize-List $ list
@( "A"、 "B"、 "C")
...しかし、パイプラインを通過するときはそれほどではありません:
$ list |シリアル化リスト
@( "C")
ただし、begin、process、およびendブロックを使用して関数をリファクタリングします。
# Serialize-List
param
(
[Parameter(Mandatory, ValueFromPipeline)]
[string[]]$list
)
begin
{
$output = "@(";
}
process
{
foreach ($element in $list)
{
$output += "`"$element`","
}
}
end
{
$output = $output.Substring(0, $output.Length - 1)
$output += ")"
$output
}
...そして、両方の方法で目的の出力を取得します。
Serialize-List $ list
@( "A"、 "B"、 "C")
$ list |シリアル化リスト
@( "A"、 "B"、 "C")