PowerShell 的 -f 运算符的 RHS *究竟*如何工作?

How *exactly* does the RHS of PowerShell's -f operator work?

上次我对 PowerShell 急切展开集合的方式感到困惑时,Keith 总结了它的启发式如下:

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

我已将这个建议铭记于心,但仍然无法解释一些深奥的内容。特别是,格式运算符似乎不按规则行事。

$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 $($_)      

}

# 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

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

$rhs ="a" | SquareAndWrap

$rhs[0].GetType() # String



$rhs ="a","b" | SquareAndWrap

$rhs[0].GetType() # Object[]

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

"a" | SquareAndWrap | % {  

  # 9. only @() and $() succeed 

  $_.GetType().FullName

  $_.Length

  $lhs -f [object[]]$_

  $lhs -f [object[]]($_)  

  $lhs -f @($_)  

  $lhs -f $($_)      

}

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

应用我们在上一个问题中看到的相同模式,很明显为什么像 #1 和 #5 这样的情况表现不同:管道运算符向脚本引擎发出信号以展开另一个级别,而赋值运算符没有。换句话说,位于两个 | 之间的所有内容都被视为一个分组表达式,就像它在 () 中一样。

$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 $($_)      

}

# 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

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

$rhs ="a" | SquareAndWrap

$rhs[0].GetType() # String



$rhs ="a","b" | SquareAndWrap

$rhs[0].GetType() # Object[]

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

"a" | SquareAndWrap | % {  

  # 9. only @() and $() succeed 

  $_.GetType().FullName

  $_.Length

  $lhs -f [object[]]$_

  $lhs -f [object[]]($_)  

  $lhs -f @($_)  

  $lhs -f $($_)      

}

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

出于同样的原因,案例#7 与#5 相比没有任何改进。任何添加额外 Wrap 的尝试都将立即被额外的管道破坏。同上 #8 与 #6。有点令人沮丧,但我完全同意这一点。

剩下的问题:

  • 为什么案例#3 没有遭受与#4 相同的命运? $rhs 应该保存嵌套数组 (,("a","a")) 但它的外层正在展开......某处......
  • #9-10 中的各种分组运算符是怎么回事?为什么他们的行为如此不规律,为什么需要他们?
  • 为什么 #10 的失败不会像 #4 那样优雅地降级?

嗯,这肯定有一个错误。 (我昨天刚刚在 PoshCode Wiki 上写了一个关于它的页面,实际上,连接上有一个错误)。

先回答,后面有更多问题:

要从具有 -f 字符串格式的数组中获得一致的行为,您需要 100% 确保它们是 PSObject。我的建议是在分配它们时这样做。它应该由 PowerShell 自动完成,但由于某种原因,直到您访问属性或其他东西(如该 wiki 页面和错误中所述)才完成。例如( <##> 是我的提示):

$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 $($_)      

}

# 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

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

$rhs ="a" | SquareAndWrap

$rhs[0].GetType() # String



$rhs ="a","b" | SquareAndWrap

$rhs[0].GetType() # Object[]

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

"a" | SquareAndWrap | % {  

  # 9. only @() and $() succeed 

  $_.GetType().FullName

  $_.Length

  $lhs -f [object[]]$_

  $lhs -f [object[]]($_)  

  $lhs -f @($_)  

  $lhs -f $($_)      

}

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

请注意,当你这样做时,它们在传递给 -f 时不会展开,而是会正确输出——就像你将变量直接放在字符串中时那样。

为什么案例#3 没有遭受与#4 相同的命运? $rhs 应该保存嵌套数组 (,("a","a")) 但它的外层正在展开......某处......

答案的简单版本是 #3 和 #4 都在展开。不同的是,在4中,内部的内容是一个数组(即使在外部数组展开之后):

$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 $($_)      

}

# 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

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

$rhs ="a" | SquareAndWrap

$rhs[0].GetType() # String



$rhs ="a","b" | SquareAndWrap

$rhs[0].GetType() # Object[]

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

"a" | SquareAndWrap | % {  

  # 9. only @() and $() succeed 

  $_.GetType().FullName

  $_.Length

  $lhs -f [object[]]$_

  $lhs -f [object[]]($_)  

  $lhs -f @($_)  

  $lhs -f $($_)      

}

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

#9-10 中的各种分组运算符是怎么回事?为什么他们的行为如此不规律,为什么需要他们?

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

当然,我们已经观察到其中有一个错误。

我没有看到任何异常,但是:就我所见,@() 和 $() 对于 9 和 10 的工作方式相同(实际上,主要区别是由 ForEach 展开的方式引起的顶级数组:

$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 $($_)      

}

# 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

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

$rhs ="a" | SquareAndWrap

$rhs[0].GetType() # String



$rhs ="a","b" | SquareAndWrap

$rhs[0].GetType() # Object[]

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

"a" | SquareAndWrap | % {  

  # 9. only @() and $() succeed 

  $_.GetType().FullName

  $_.Length

  $lhs -f [object[]]$_

  $lhs -f [object[]]($_)  

  $lhs -f @($_)  

  $lhs -f $($_)      

}

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

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

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

这基本上是相同的错误,但表现形式不同。数组不应该在 PowerShell 中以"System.Object[]"的形式出现,除非您手动调用它们的原生 .ToString() 方法,或者直接将它们传递给 String.Format() ......他们在 #4 中这样做的原因是那个错误:在将它们传递给 String.Format 调用之前,PowerShell 未能将它们扩展为 PSOjbect。

如果您在传入数组的属性之前访问它,或者将它转换为 PSObject,就像我的原始示例中一样,您可以看到这一点。从技术上讲,#10 中的错误是正确的输出:您只将一个东西(一个数组)传递给 string.format,而它需要两个东西。如果您将 $lhs 更改为"{0}",您会看到使用 $OFS

格式化的数组

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

$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 $($_)      

}

# 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

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

$rhs ="a" | SquareAndWrap

$rhs[0].GetType() # String



$rhs ="a","b" | SquareAndWrap

$rhs[0].GetType() # Object[]

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

"a" | SquareAndWrap | % {  

  # 9. only @() and $() succeed 

  $_.GetType().FullName

  $_.Length

  $lhs -f [object[]]$_

  $lhs -f [object[]]($_)  

  $lhs -f @($_)  

  $lhs -f $($_)      

}

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

我确信很多脚本是在不知不觉中利用这个错误编写的,但我仍然很清楚,在只是为了清楚的例子中发生的展开不是正确的行为,而是因为, 在对 .Net String.Format("{0}", a ) ... $a 的调用(在 PowerShell 的核心内部)是一个 object[],这是 String.Format 所期望的,因为它是 Params 参数...

我认为必须解决这个问题。如果想要保持展开数组的"功能",应该使用@splatting 操作符来完成,对吧?


Square 和 Wrap 都不会执行您在 # 的 5 和 7 中尝试的操作。无论您是像在 Square 中那样将数组放在分组表达式 () 中,还是像在 Square 中那样使用逗号运算符总结一下,当您在管道中使用这些函数时,它们的输出将展开,因为它一次一个地馈送到下一个管道阶段。类似地,在 6 和 8 中,管道中的多个对象并不重要,Square 和 Wrap 都会一次将它们送出到您的 foreach 阶段。

案例 9 和 10 似乎表明 PowerShell 中存在错误。拿这个修改后的片段试试看:

$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 $($_)      

}

# 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

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

$rhs ="a" | SquareAndWrap

$rhs[0].GetType() # String



$rhs ="a","b" | SquareAndWrap

$rhs[0].GetType() # Object[]

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

"a" | SquareAndWrap | % {  

  # 9. only @() and $() succeed 

  $_.GetType().FullName

  $_.Length

  $lhs -f [object[]]$_

  $lhs -f [object[]]($_)  

  $lhs -f @($_)  

  $lhs -f $($_)      

}

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

它有效。它还表明,foreach 已经接收到一个大小为 2 的 object[],因此 $_ 应该可以在不强制转换为 [object[]] 或package在子表达式或数组子表达式中的情况下工作。我们已经看到一些与 psobjects 相关的 V2 错误没有正确展开,这似乎是另一个例子。如果您手动解开 psobject 它可以工作,例如$_.psobject.baseobject.

我"认为"你在 Wrap 中的目标是:

$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 $($_)      

}

# 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

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

$rhs ="a" | SquareAndWrap

$rhs[0].GetType() # String



$rhs ="a","b" | SquareAndWrap

$rhs[0].GetType() # Object[]

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

"a" | SquareAndWrap | % {  

  # 9. only @() and $() succeed 

  $_.GetType().FullName

  $_.Length

  $lhs -f [object[]]$_

  $lhs -f [object[]]($_)  

  $lhs -f @($_)  

  $lhs -f $($_)      

}

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

这将累积所有管道输入,然后将其作为单个数组输出。这适用于案例 8,但您仍然需要在 -f 运算符的前两次使用中强制转换为 [object[]]。

顺便说一句,Square 和 Wrap 中的括号以及 SquareAndWrap 中的外部括号都是不必要的。


相关推荐

  • Spring部署设置openshift

    Springdeploymentsettingsopenshift我有一个问题让我抓狂了三天。我根据OpenShift帐户上的教程部署了spring-eap6-quickstart代码。我已配置调试选项,并且已将Eclipse工作区与OpehShift服务器同步-服务器上的一切工作正常,但在Eclipse中出现无法消除的错误。我有这个错误:cvc-complex-type.2.4.a:Invali…
    2025-04-161
  • 检查Java中正则表达式中模式的第n次出现

    CheckfornthoccurrenceofpatterninregularexpressioninJava本问题已经有最佳答案,请猛点这里访问。我想使用Java正则表达式检查输入字符串中特定模式的第n次出现。你能建议怎么做吗?这应该可以工作:MatchResultfindNthOccurance(intn,Patternp,CharSequencesrc){Matcherm=p.matcher…
    2025-04-161
  • 如何让 JTable 停留在已编辑的单元格上

    HowtohaveJTablestayingontheeditedcell如果有人编辑JTable的单元格内容并按Enter,则内容会被修改并且表格选择会移动到下一行。是否可以禁止JTable在单元格编辑后转到下一行?原因是我的程序使用ListSelectionListener在单元格选择上同步了其他一些小部件,并且我不想在编辑当前单元格后选择下一行。Enter的默认绑定是名为selectNext…
    2025-04-161
  • Weblogic 12c 部署

    Weblogic12cdeploy我正在尝试将我的应用程序从Tomcat迁移到Weblogic12.2.1.3.0。我能够毫无错误地部署应用程序,但我遇到了与持久性提供程序相关的运行时错误。这是堆栈跟踪:javax.validation.ValidationException:CalltoTraversableResolver.isReachable()threwanexceptionatorg.…
    2025-04-161
  • Resteasy Content-Type 默认值

    ResteasyContent-Typedefaults我正在使用Resteasy编写一个可以返回JSON和XML的应用程序,但可以选择默认为XML。这是我的方法:@GET@Path("/content")@Produces({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})publicStringcontentListRequestXm…
    2025-04-161
  • 代码不会停止运行,在 Java 中

    thecodedoesn'tstoprunning,inJava我正在用Java解决项目Euler中的问题10,即"Thesumoftheprimesbelow10is2+3+5+7=17.Findthesumofalltheprimesbelowtwomillion."我的代码是packageprojecteuler_1;importjava.math.BigInteger;importjava…
    2025-04-161
  • Out of memory java heap space

    Outofmemoryjavaheapspace我正在尝试将大量文件从服务器发送到多个客户端。当我尝试发送大小为700mb的文件时,它显示了"OutOfMemoryjavaheapspace"错误。我正在使用Netbeans7.1.2版本。我还在属性中尝试了VMoption。但仍然发生同样的错误。我认为阅读整个文件存在一些问题。下面的代码最多可用于300mb。请给我一些建议。提前致谢publicc…
    2025-04-161
  • Log4j 记录到共享日志文件

    Log4jLoggingtoaSharedLogFile有没有办法将log4j日志记录事件写入也被其他应用程序写入的日志文件。其他应用程序可以是非Java应用程序。有什么缺点?锁定问题?格式化?Log4j有一个SocketAppender,它将向服务发送事件,您可以自己实现或使用与Log4j捆绑的简单实现。它还支持syslogd和Windows事件日志,这对于尝试将日志输出与来自非Java应用程序…
    2025-04-161