是什么决定了 Powershell 管道是否展开集合?

发布于 2024-08-12 21:28:14 字数 1998 浏览 7 评论 0原文

# 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# 中编写 cmdlet 时,存在 WriteObject 重载< /a> 您可以在其中显式控制枚举,但据我所知,Posh 语言本身没有这样的构造。正如最后一个示例所示,Posh 解释器似乎认为通过管道传输的对象类型没有差异。我怀疑在幕后可能存在一些 Object 与 PSObject 的奇怪之处,但是当您编写纯粹的 Posh 并期望脚本语言“正常工作”时,这没什么用处。

/ 编辑/

Keith 正确地指出,在我的示例中,我传递的是单个 string[] 参数而不是 3 个字符串参数。换句话说,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

据我所知,还有另一条规则在发挥作用:如果您有一个仅包含一个元素的数组,并且解析器位于 < a href="http://keithhill.spaces.live.com/blog/cns!5A8D2641E0963A97!6058.entry" rel="noreferrer">表达式模式,那么解释器将“解开”该元素。我还缺少更多微妙之处吗?

# 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

The discrepancy with HashTables is fairly well known, although the official documentation only mentions it obliquely (via example).

The issue with functions, though, is news to me. I'm kind of shocked it hasn't bitten me before now. Is there some guiding principle we scripters can follow? I know that when writing cmdlets in C# there's an overload of WriteObject where you can control enumeration explicitly, but AFAIK there's no such construct in the Posh language itself. As the final example shows, the Posh interpreter seems to believe there is no difference in the type of objects being piped. I suspect there may be some Object vs PSObject weirdness under the hood, but that's of little use when you're writing pure Posh and expect the script language to "just work."

/ EDIT /

Keith is correct to point out that in my example, I'm passing in a single string[] argument rather than 3 string arguments. In other words, the reason Measure-Object says Count=1 is because it's seeing a single array-of-arrays whose first element is @("a", "b", "c"). Fair enough. This knowledge allows you to work around the issue in several ways:

# 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

However, it doesn't explain everything...

# 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

From what I can extrapolate there's another rule in play: if you have an array with exactly one element AND the parser is in expression mode, then the interpreter will "unwrap" said element. Any more subtleties I'm missing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

心安伴我暖 2024-08-19 21:28:14

$args 已展开。请记住,函数参数通常使用空格分隔来传递。当您传入 1,2,3 时,您将传入一个参数,该参数是一个由三个数字组成的数组,该数组被分配给 $args[0]: 将

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 返回两个项目 (a,b,c) 作为数组,d 作为标量。这两个项目分别沿着管道发送,结果计数为 2。

$args is unrolled. Remember that function parameters are normally passed using space to separate them. When you pass in 1,2,3 you are passing in a single argument that is an array of three numbers that gets assigned to $args[0]:

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

Count    : 3

Putting the results (an array) within a grouping expression (or subexpression e.g. $()) makes it eligible again for unrolling so the following unrolls the object[] containing 1,2,3 returned by UnrollMe:

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

which is equivalent to:

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

BTW it doesn't just apply to an array with one element.

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

Using an array subexpression (@()) on something that is already an array has no effect no matter how many times you apply it. :-) If you want to prevent unrolling use the comma operator because it will always create another outer array which gets unrolled. Note that in this scenario you don't really prevent unrolling, you just work around the unrolling by introducing an outer "wrapper" array that gets unrolled instead of your original array e.g.:

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

Finally, when you execute this:

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

You can see that UnrollMe returns two items (a,b,c) as an array and d as a scalar. Those two items get sent down the pipeline separately which is the resulting count is 2.

半仙 2024-08-19 21:28:14

它似乎与 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

It seem to have something to do with how Measure-Object works and how objects are passed along the pipeline.

When you say

1,2,3 | measure

you get 3 Int32 objects passed onto the pipeline, measure object then counts each object it sees on the pipeline.

When you "unroll it" using your function, you get a single array object passed onto the pipeline which measure object counts as 1, it makes no attempt to iterate through the objects in the array, as shown here:

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

A possible work-around is to "re-roll" the array onto the pipeline using foreach:

PS C:\> (UnrollMe 1,2,3 | %{$_} | measure).count
3
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文