3

前回、 PowerShellが熱心にコレクションをアンロールする方法に戸惑いましたが、Keith はそのヒューリスティックを次のようにまとめました。

結果 (配列) をグループ化式 (または $() などの部分式) 内に配置すると、再度展開できるようになります。

私はそのアドバイスを心に留めていますが、それでもいくつかの難解なことを説明することはできません. 特に、Format オペレーターはルールに従っていないようです。

$lhs = "{0} {1}"

filter Identity { $_ }
filter Square { ($_, $_) }
filter Wrap { (,$_) }
filter SquareAndWrap { (,($_, $_)) }

$rhs = "a" | Square        
# 1. all succeed
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)

$rhs = "a" | Square | Wrap       
# 2. all succeed
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)

$rhs = "a" | SquareAndWrap       
# 3. all succeed
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)

$rhs = "a", "b" | SquareAndWrap       
# 4. all succeed by coercing the inner array to the string "System.Object[]"
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)

"a" | Square | % {
    # 5. all fail
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

"a", "b" | Square | % {
    # 6. all fail
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

"a" | Square | Wrap | % {
    # 7. all fail
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

"a", "b" | Square | Wrap | % {
    # 8. all fail
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

"a" | SquareAndWrap | % {
    # 9. only @() and $() succeed
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

"a", "b" | SquareAndWrap | % {
    # 10. only $() succeeds
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

前の質問で見たのと同じパターンを適用すると、#1 と #5 のようなケースが異なる動作をする理由は明らかです。パイプライン オペレーターはスクリプト エンジンに別のレベルをアンロールするように信号を送りますが、割り当てオペレーターはそうしません。別の言い方をすれば、2 つの | の間にあるものはすべて、() の中にあるかのように、グループ化された式として扱われます。

# all of these output 2
("a" | Square).count                       # explicitly grouped
("a" | Square | measure).count             # grouped by pipes
("a" | Square | Identity).count            # pipe + ()
("a" | Square | Identity | measure).count  # pipe + pipe

同じ理由で、ケース #7 は #5 よりも改善されていません。余分なラップを追加しようとすると、余分なパイプによってすぐに破壊されます。同上 #8 vs #6。少しイライラしますが、私はこの時点まで完全に参加しています。

残りの質問:

  • ケース #3 が #4 と同じ運命をたどらないのはなぜですか? $rhsはネストされた配列(,("a", "a"))を保持する必要がありますが、その外側のレベルは展開されています...どこか...
  • #9-10 のさまざまなグループ化演算子で何が起こっているのでしょうか? なぜ彼らはそんなに不規則に振る舞うのですか、そしてなぜ彼らは必要なのですか?
  • #10 の場合の障害が #4 のように正常に劣化しないのはなぜですか?
4

2 に答える 2

5

まあ、確かにバグがあります。(昨日、PoshCode Wiki にページを書いたばかりですが、実際には connect にバグがあります)。

最初に回答し、後でさらに質問します。

文字列の書式設定を使用して配列から一貫した動作を得るには、配列-fが PSObject であることを 100% 確認する必要があります。私の提案は、それらを割り当てるときにそれを行うことです。これはPowerShell によって自動的に行われるはずですが、何らかの理由で、プロパティまたは何かにアクセスするまで行われません (そのwiki ページバグに記載されているように)。例(<##>私のプロンプトです):

<##> $a = 1,2,3
<##> "$a"
1 2 3

<##> $OFS = "-"  # Set the Output field separator
<##> "$a"
1-2-3

<##> "{0}" -f $a
1 

<##> $a.Length
3 

<##> "{0}" -f $a
1-2-3

# You can enforce correct behavior by casting:
<##> [PSObject]$b = 1,2,3
<##> "{0}" -f $a
1-2-3

これを行うと、 -f に渡すときにアンロールされず、変数を文字列に直接配置した場合と同じように正しく出力されることに注意してください。

ケース #3 が #4 と同じ運命をたどらないのはなぜですか? $rhs はネストされた配列 (,("a", "a")) を保持する必要がありますが、その外側のレベルは展開されています...どこか...

答えの簡単なバージョンは、#3 と #4 の両方が展開されているということです。違いは、4 では内部の内容が配列であることです (外部の配列が展開された後でも):

$rhs = "a" | SquareAndWrap
$rhs[0].GetType()  # String

$rhs = "a","b" | SquareAndWrap
$rhs[0].GetType()  # Object[]

#9-10 のさまざまなグループ化演算子で何が起こっているのでしょうか? なぜ彼らはそんなに不規則に振る舞うのですか、そしてなぜ彼らは必要なのですか?

前に述べたように、配列はフォーマットの単一のパラメーターとしてカウントされ、 $_ を文字列に直接挿入した場合と同様に、 PowerShell の文字列フォーマット規則を使用して (つまり、 で区切られて$OFS)出力される必要があります... したがって、 PowerShell が正しく動作している場合、$lhs に 2 つのプレースホルダーが含まれていると失敗します。$lhs -f $rhs

もちろん、バグがあることはすでに確認済みです。

@() と $() は、私が見る限り、9 と 10 で同じように機能します (実際、主な違いは、ForEach が最上位の配列をアンロールする方法によって引き起こされます)。 :

> $rhs = "a", "b" | SquareAndWrap
> $rhs | % { $lhs -f @($_); " hi " }
a a
 hi 
b b
 hi 

> $rhs | % { $lhs -f $($_); " hi " }
a a
 hi 
b b
 hi     

# Is the same as:
> [String]::Format( "{0} {1}", $rhs[0] ); " hi "
a a
 hi 

> [String]::Format( "{0} {1}", $rhs[1] ); " hi "
b b
 hi     

したがって、@() または $() を使用すると、配列が特殊な to-string 値を持つ PSObject としてではなく、[object[]] として文字列フォーマット呼び出しに渡されるというバグが見られます。

#10 の場合の障害が #4 のように正常に劣化しないのはなぜですか?

これは基本的に同じバグですが、別の症状があります。.ToString()配列は、ネイティブメソッドを手動で呼び出すか、String.Format() に直接渡さない限り、PowerShell で "System.Object[]" として出力されるべきではありません。 String.Format 呼び出しに渡す前に PSOjbects として拡張します。

これは、配列を渡す前にそのプロパティにアクセスするか、元の例のように PSObject にキャストするとわかります。技術的には、#10 のエラーは正しい出力です。string.format には 1 つのもの (配列) しか渡していませんが、2 つのものが必要です。$lhs を単に "{0}" に変更すると、$OFS でフォ​​ーマットされた配列が表示されます。


私の最初の例を考えると、どの動作が好きで、どの動作が正しいと思いますか? $OFS で区切られた出力は正しいと思います。これは、配列を @(ラップ) したり、[object[]] にキャストした場合のように配列をアンロールするのとは対照的です (ちなみに、[int[ にキャストするとどうなるか注意してください)。 ]] はのバグのある動作です):

> "{0}" -f [object[]]$a
1

> "{0}, {1}" -f [object[]]$a  # just to be clear...
1,2

>  "{0}, {1}" -f [object[]]$a, "two"  # to demonstrate inconsistency
System.Object[],two

> "{0}" -f [int[]]$a
System.Int32[]

このバグを利用して無意識のうちに多くのスクリプトが作成されていると確信していますが、明確にするために、例で発生している展開正しい動作ではなく、発生しているため、 (PowerShellのコア内で).Netへの呼び出しString.Format( "{0}", a )...$aは、object[]ParamsパラメーターとしてString.Formatが期待するものです...

それは直さなければならないと思います。配列をアンロールする「機能」を保持したい場合は、@ スプラッティング演算子を使用して行う必要がありますよね?

于 2009-12-09T20:46:22.497 に答える
2

Square not Wrap は、# の 5 と 7 で試みていることを行いません。これらの関数をパイプラインで使用すると、出力が次のパイプライン ステージに一度に 1 つずつフィードされるときに展開されます。同様に、6 と 8 では、複数のオブジェクトをパイプしても問題ありません。Square と Wrap の両方が、一度に 1 つずつ foreach ステージにフィードします。

ケース 9 と 10 は、PowerShell のバグを示しているようです。この変更されたスニペットを試してみてください。

"a" | SquareAndWrap | % {    
    # 9. only @() and $() succeed  
    $_.GetType().FullName
    $_.Length
    $lhs -f [object[]]$_
    $lhs -f [object[]]($_)    
    $lhs -f @($_)   
    $lhs -f $($_)            
}

できます。また、foreach がすでに object[] サイズ 2 を受け取っているため$_、[object[]] にキャストしたり、部分式または配列部分式でラップしたりしなくても機能するはずであることも示しています。psobjects が正しくアンラップされないことに関連する V2 バグがいくつか見られましたが、これはその別の例のようです。psobject を手動でアンラップすると、たとえば動作し$_.psobject.baseobjectます。

あなたがラップで撮影しているのはこれだと「思います」:

function Wrap2 { Begin {$coll = @();} Process {$coll += $_} End {,$coll} }

これにより、すべてのパイプライン入力が蓄積され、単一の配列として出力されます。-fこれはケース 8 で機能しますが、演算子の最初の 2 回の使用で [object[]] にキャストする必要があります。

ところで、Square と Wrap の両方の括弧と、SquareAndWrap の外側の括弧は不要です。

于 2009-12-09T16:32:47.960 に答える