PowerShell -f操作系统的RHS如何正常工作? [英] How *exactly* does the RHS of PowerShell's -f operator work?

查看:165
本文介绍了PowerShell -f操作系统的RHS如何正常工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

上次我感到困惑通过 PowerShell 热切地展开集合,Keith总结了其启发式,如下所示:

Last time I got confused by the way PowerShell eagerly unrolls collections, Keith summarized its heuristic like so:


将结果(数组)放在分组表达式(或子表达式,例如$())中,使其再次合格展开。

Putting the results (an array) within a grouping expression (or subexpression e.g. $()) makes it eligible again for unrolling.

我已经采取了这个建议,但仍然发现自己无法解释一些esoterica。特别是格式运算符似乎不符合规则。

I've taken that advice to heart, but still find myself unable to explain a few esoterica. In particular, the Format operator doesn't seem to play by the rules.

$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这样的案例会有所不同:管道操作员发出脚本引擎来展开另一个级别,而赋值运算符没有。换句话说,两个|之间的一切都被视为一个分组的表达式,就像它在里面一样。

Applying the same patterns we saw in the previous question, it's clear why cases like #1 and #5 behave different: the pipeline operator signals the script engine to unroll another level, while the assignment operator does not. Put another way, everything that lies between two |'s is treated as a grouped expression, just as if it were inside ()'s.

# 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没有改进。任何添加额外 Wrap 的尝试将立即被额外的管道颠覆。同上#8 vs#6。有点令人沮丧,但我完全在这一点上。

For the same reason, case #7 is no improvement over #5. Any attempt to add an extra Wrap will be immediately subverted by the extra pipe. Ditto #8 vs #6. A little frustrating, but I'm totally on board up to this point.

剩余的问题:


  • 为什么#3的情况不一样命运为#4? $ rhs 应该保留嵌套数组(,(a,a)),但其外层级正在展开...某处...

  • #9-10中的各种分组运算符发生了什么?为什么他们的行为如此不稳定,为什么他们需要?

  • 为什么#10的失败不会像#4那样优雅地降级?

  • 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...
  • 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?
  • Why don't the failures in case #10 degrade gracefully like #4 does?

推荐答案

那么肯定有一个错误。实际上,我刚刚写了 PoshCode Wiki 上的一个页面,实际上, 连接错误)。

Well, there's a bug in that for sure. (I just wrote up a page on the PoshCode Wiki about it yesterday, actually, and there's a bug on connect).

首先回答更多问题:

要从 -f 字符串格式化,您将需要使100%确定它们是PSObjects。我的建议是在分配时这样做。这是由PowerShell自动完成的,但是由于某种原因,直到您访问某个属性或某些东西(如 wiki页面错误)。例如(< ##> 是我的提示):

To get consistent behavior from arrays with the -f 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 supposed to be done automatically by PowerShell, but for some reason isn't done until you access a property or something (as documented in that wiki page and bug). E.g.( <##> is my prompt):

<##> $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时,它们不会展开,而是会正确输出 - 如果直接将变量放在字符串中,那将是他们的方式。

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.

答案的版本是,第3和第4号正在展开。不同之处在于,内部内容是一个数组(即使在外部数组展开后):

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):

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

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



与#9-10中的各种分组运算符?为什么它们的行为如此不稳定,为什么需要它们?



如前所述,数组应该作为格式的单个参数计算,并应该输出使用PowerShell的字符串格式化规则(即:由 $ OFS 分隔)就像将$ _直接放入字符串一样...因此,当PowerShell的行为正确时,如果$ lhs包含两个占位符, $ lhs -f $ rhs 将失败。

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?

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 $OFS) just as it would if you put $_ into the string directly ... therefore, when PowerShell is behaving correctly, $lhs -f $rhs will fail if $lhs contains two place holders.

当然,我们已经看到它有一个错误。

然而,我看不到任何不规则的东西:@ ()和$()对于9和10工作相同,只要我看到(主要区别,其实是由ForEach展开顶级数组的方式引起的:

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:

> $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     

所以你看到的错误是@( )或$()将使数组作为[object []]传递给字符串格式调用,而不是作为具有特殊字符串值的PSObject。

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.

这是基本相同的错误,在不同的表现。在PowerShell中,除非您手动调用自己的本地 .ToString()方法,否则直接将数组传递给String.Format(),否则在PowerShell中不应该出现System.Object [] ...他们在#4中的原因是这个错误:PowerShell在将它们传递给String.Format调用之前未能将其扩展为PSOjbect。

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 .ToString() 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.

您可以看到如果您在传入数组之前访问数组的属性,或者将其转换为PSObject,就像我原来的例子中那样。从技术上讲,#10中的错误是正确的输出:当你想要两个东西时,你只传递一个东西(一个数组)到string.format。如果您将$ lh更改为{0},您将看到使用$ OFS格式化的阵列

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

我不知道但是,考虑到我的第一个例子,你喜欢哪种行为,你觉得正确是什么?我认为$ OFS分隔的输出是正确的,而不是展开数组,如果你@(包装),或者将其转换为[object []](顺便提一下,注意如果你将它转换为[int [ ]]是一种不同的错误行为):

I wonder though, which behavior do you like and which do you think is correct, 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 different buggy behavior):

> "{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[]

我相信很多脚本都是在不知不觉中被利用这个bug而编写的,但是对于我来说,仍然很明显的是,在正在发生的例子之间的展开不是正确的行为,但是正在发生,因为在PowerShell的核心中调用.Net String.Format({0},a) ... $ a 是一个对象[] 这是String.Format预期的,因为它是Params参数...

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 just to be clear example is NOT the correct behavior, but is happening because, on the call (inside PowerShell's core) to the .Net String.Format( "{0}", a ) ... $a is an object[] which is what String.Format expected as it's Params parameter...

我认为这是必须解决的。如果有任何希望保留展开数组的功能,那么应该使用@ splatting运算符来完成,对吗?

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?

这篇关于PowerShell -f操作系统的RHS如何正常工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆