10
# array
C:\> (1,2,3).count
3
C:\> (1,2,3 | measure).count
3

# hashtable
C:\> @{1=1; 2=2; 3=3}.count
3
C:\> (@{1=1; 2=2; 3=3} | measure).count
1

# array returned from function
C:\> function UnrollMe { $args }
C:\> (UnrollMe a,b,c).count
3
C:\> (UnrollMe a,b,c | measure).count
1
C:\> (1,2,3).gettype() -eq (UnrollMe a,b,c).gettype()
True

HashTables との不一致はかなりよく知られていますが、公式ドキュメントでは斜めにしか言及されていません (例を介して)。

ただし、関数の問題は私にとってニュースです。今まで噛まれたことがなかったのでちょっとショックです。スクリプターが従うことができる指針となる原則はありますか? C# でコマンドレットを記述する場合、明示的に列挙を制御できるWriteObject のオーバーロードがあることは知っていますが、知る限り、Posh 言語自体にはそのような構造はありません。最後の例が示すように、Posh インタープリターは、パイプされるオブジェクトのタイプに違いはないと考えているようです。内部で Object と PSObject の奇妙な点があるのではないかと思いますが、純粋な Posh を作成していて、スクリプト言語が「正常に機能する」ことを期待している場合、それはほとんど役に立ちません。

/ 編集 /

私の例では、3 つの文字列引数ではなく単一の string[] 引数を渡していることを Keith が指摘するのは正しいです。つまり、Measure-Object が Count=1 と言う理由は、最初の要素が @("a", "b", "c") である単一の配列の配列を参照しているためです。けっこうだ。この知識により、いくつかの方法で問題を回避できます。

# stick to single objects
C:\> (UnrollMe a b c | measure).count
3

# rewrite the function to handle nesting
C:\> function UnrollMe2 { $args[0] }
C:\> (UnrollMe2 a,b,c | measure).count
3

# ditto
C:\> function UnrollMe3 { $args | %{ $_ } }
C:\> (UnrollMe3 a,b,c | measure).count
3

しかし、それはすべてを説明していません...

# as seen earlier - if we're truly returning @( @("a","b","c") ) why not count=1?
C:\> (UnrollMe a,b,c).count
3

# our theory must also explain these results:
C:\> ((UnrollMe a,b,c) | measure).count
3
C:\> ( @(@("a","b","c")) | measure).count
3
C:\> ((UnrollMe a,b,c d) | measure).count
2

私が推定できることから、別のルールが適用されます。要素が1つだけの配列があり、パーサーが式モードの場合、インタープリターはその要素を「ラップ解除」します。私が見逃している機微はありますか?

4

2 に答える 2

12

$args が展開されます。関数パラメーターは通常、スペースを使用して区切って渡されることに注意してください。1,2,3 を渡す場合、$args[0] に割り当てられる 3 つの数値の配列である単一の引数を渡します。

PS> function UnrollMe { $args }
PS> UnrollMe 1 2 3 | measure

Count    : 3

結果 (配列) をグループ化式 (またはサブ式 (例$()) ) 内に配置すると、再びアンロールできるようになるため、以下は UnrollMe によって返された 1,2,3 を含む object[] をアンロールします。

PS> ((UnrollMe 1,2,3) | measure).Count
3

これは次と同等です:

PS> ((1,2,3) | measure).Count
3

ところで、1 つの要素を持つ配列だけに適用されるわけではありません。

PS> ((1,2),3) | %{$_.GetType().Name}
Object[]
Int32

すでに配列になっているものに配列部分式 ( @()) を使用しても、何度適用しても効果はありません。:-) アンロールを防ぎたい場合は、コンマ演算子を使用します。これは、アンロールされる別の外部配列を常に作成するためです。このシナリオでは、実際には展開を妨げないことに注意してください。元の配列の代わりに展開される外側の「ラッパー」配列を導入することで、展開を回避するだけです。

PS> (,(1,2,3) | measure).Count
1

最後に、これを実行すると:

PS> (UnrollMe a,b,c d) | %{$_.GetType().Name}
Object[]
String

UnrollMe が 2 つの項目 (a、b、c) を配列として返し、d をスカラーとして返すことがわかります。これら 2 つのアイテムはパイプラインに別々に送信され、結果のカウントは 2 になります。

于 2009-12-01T22:31:21.573 に答える
1

Measure-Object がどのように機能し、オブジェクトがパイプラインに沿ってどのように渡されるかに関係があるようです。

あなたが言う時

1,2,3 | measure

パイプラインに渡された 3 つの Int32 オブジェクトを取得し、オブジェクトを測定してから、パイプラインで検出された各オブジェクトをカウントします。

関数を使用して "展開" すると、オブジェクト カウントを 1 として測定するパイプラインに渡される単一の配列オブジェクトが取得されます。次に示すように、配列内のオブジェクトを反復処理する試みは行われません。

PS C:\> (measure -input 1,2,3).count
1

考えられる回避策は、foreach を使用して配列をパイプラインに「再ロール」することです。

PS C:\> (UnrollMe 1,2,3 | %{$_} | measure).count
3
于 2009-12-01T18:44:22.597 に答える