Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Well, there's a bug in that for sure. (I just wrote up <a href="http://wiki.poshcode.org/FAQ/Considered_Harmful/Arrays_and_String_Formatting" rel="noreferrer">a page on the PoshCode Wiki</a> about it yesterday, actually, and there's a <a href="https://connect.microsoft.com/PowerShell/feedback/ViewFeedback.aspx?FeedbackID=518276" rel="noreferrer">bug on connect</a>).</p> <p>Answers first, more questions later:</p> <p>To get consistent behavior from arrays with the <code>-f</code> string formatting, you're going to need to make 100% sure they are PSObjects. My suggestion is to do that when assigning them. It is <em>supposed</em> to be done automatically by PowerShell, but for some reason isn't done until you access a property or something (as documented in that <a href="http://wiki.poshcode.org/FAQ/Considered_Harmful/Arrays_and_String_Formatting" rel="noreferrer">wiki page</a> and <a href="https://connect.microsoft.com/PowerShell/feedback/ViewFeedback.aspx?FeedbackID=518276" rel="noreferrer">bug</a>). E.g.( <code>&lt;##&gt;</code> is my prompt):</p> <pre><code>&lt;##&gt; $a = 1,2,3 &lt;##&gt; "$a" 1 2 3 &lt;##&gt; $OFS = "-" # Set the Output field separator &lt;##&gt; "$a" 1-2-3 &lt;##&gt; "{0}" -f $a 1 &lt;##&gt; $a.Length 3 &lt;##&gt; "{0}" -f $a 1-2-3 # You can enforce correct behavior by casting: &lt;##&gt; [PSObject]$b = 1,2,3 &lt;##&gt; "{0}" -f $a 1-2-3 </code></pre> <p>Note that when you've done that, they WILL NOT be unrolled when passing to -f but rather would be output correctly -- the way they would be if you placed the variable in the string directly.</p> <h2>Why doesn't case #3 suffer the same fate as #4? $rhs should hold the nested array (,("a", "a")) but its outer level is getting unrolled...somewhere...</h2> <p>The simple version of the answer is that BOTH #3 and #4 are getting unrolled. The difference is that in 4, the inner contents are an array (even after the outer array is unrolled):</p> <pre><code>$rhs = "a" | SquareAndWrap $rhs[0].GetType() # String $rhs = "a","b" | SquareAndWrap $rhs[0].GetType() # Object[] </code></pre> <h2>What's going on with the various grouping operators in #9-10? Why do they behave so erratically, and why are they needed at all?</h2> <p>As I said earlier, an array should count as a single parameter to the format and should be output using PowerShell's string-formatting rules (ie: separated by <code>$OFS</code>) <em>just as it would if you put $_ into the string directly</em> ... therefore, when PowerShell is behaving correctly, <code>$lhs -f $rhs</code> will fail if $lhs contains two place holders.</p> <p><em>Of course, we've already observed that there's a bug in it.</em></p> <p>I don't see anything erratic, however: @() and $() work the same for 9 and 10 as far as I can see (the main difference, in fact, is caused by the way the ForEach unrolls the top level array:</p> <pre><code>&gt; $rhs = "a", "b" | SquareAndWrap &gt; $rhs | % { $lhs -f @($_); " hi " } a a hi b b hi &gt; $rhs | % { $lhs -f $($_); " hi " } a a hi b b hi # Is the same as: &gt; [String]::Format( "{0} {1}", $rhs[0] ); " hi " a a hi &gt; [String]::Format( "{0} {1}", $rhs[1] ); " hi " b b hi </code></pre> <p>So you see the bug is that @() or $() will cause the array to be passed as [object[]] to the string format call instead of as a PSObject which has special to-string values.</p> <h2>Why don't the failures in case #10 degrade gracefully like #4 does?</h2> <p>This is basically the same bug, in a different manifestation. Arrays should never come out as "System.Object[]" in PowerShell unless you manually call their native <code>.ToString()</code> method, or pass them to String.Format() directly ... the reason they do in #4 is that bug:PowerShell has failed to extend them as PSOjbects before passing them to the String.Format call.</p> <p>You can see this if you access a property of the array before passing it in, or cast it to PSObject as in my original exampels. Technically, the errors in #10 are the correct output: you're only passing ONE thing (an array) to string.format, when it expected TWO things. If you changed your $lhs to just "{0}" you would see the array formatted with $OFS </p> <hr> <p>I wonder though, which behavior do you <strong>like</strong> and which do you think is <strong>correct</strong>, considering my first example? I think the $OFS-separated output is correct, as opposed to unrolling the array as happens if you @(wrap) it, or cast it to [object[]] (Incidentally, note what happens if you cast it to [int[]] is a <em>different</em> buggy behavior):</p> <pre><code>&gt; "{0}" -f [object[]]$a 1 &gt; "{0}, {1}" -f [object[]]$a # just to be clear... 1,2 &gt; "{0}, {1}" -f [object[]]$a, "two" # to demonstrate inconsistency System.Object[],two &gt; "{0}" -f [int[]]$a System.Int32[] </code></pre> <p>I'm sure lots of scripts have been written unknowingly taking advantage of this bug, but it still seems pretty clear to me that the unrolling that's happening in the <em>just to be clear</em> example is NOT the correct behavior, but is happening because, on the call (inside PowerShell's core) to the .Net <code>String.Format( "{0}", a )</code> ... <code>$a</code> is an <code>object[]</code> which is what String.Format expected as it's Params parameter...</p> <p>I think that has to be fixed. If there's any desire to keep the "functionality" of unrolling the array it should be done using the @ splatting operator, right?</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. COGreat summary - marked as answer even though I disagree with your opinion. I think "{0} {1}" -f "a", "b" should be equivalent to $arr="a","b"; "{0} {1}" -f $arr. That is, the RHS of the -f operator should be a [params object[]], to mix C# and PS lingo. I don't see why $arr should be converted to a string using OFS, unless you explicitly quote it on the RHS of the operator.
      singulars
    2. COWell, the main reason it should be converted to a string is that that's what string formatting is supposed to do. That is, if you don't use formatting codes like { "{0:X}" -f 42 } then string formatting is supposed to behave like casting the thing to string. That's how .Net's string formatting works. Changing the rules for PowerShell is confusing. Of course, they've already changed it with $OFS ... but changing it AGAIN would mean that arrays would output at least three different ways, and you'd never know which. For instance, what should happen if I do: { "{0}{1}" -f $arr,"hello" }?
      singulars
    3. COHonestly, part of me agrees with you, because it's convenient to be able to do that! But I want it to only happen explicitly so I can do BOTH of these: `{ $a = 1,"+",2; "{0} = {1}" -f $a,"three"; "{2}-{0}={3}" -f @a,"one" }` and have them come out as: "1 + 2 = three" and "2-1=one" ... right now you CAN do that, but only if you're crazy like a fox: `{ $a = 1,"+",2; "{0} = {1}" -f [PSObject]$a,"three"; "{2}-{0}={3}" -f [object[]]($a+"one") }` and THAT is just unknowable.
      singulars
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload