谁见过这个潜在的 powershell 对象属性被视为方法调用诱杀陷阱?

Who has seen this potential powershell object property treated as method call booby trap?

提问人:Uber Kluger 提问时间:1/13/2022 更新时间:1/14/2022 访问量:100

问:

以下内容不是一个实际问题,而是关于一些意外的 PowerShell 语法的警示故事。唯一真正的问题是“这种行为是许多人都知道,还是只有少数 PowerShell 开发人员(即那些在 PowerShell 工作的人,而不仅仅是使用 PowerShell 的开发人员)知道?注意:这些示例只是为了演示效果,并不代表有意义的代码(无需询问目的是什么)。

在使用 PowerShell (5.1.18362.145) 语句时,我收到以下错误:switch

PS > $xx = gi somefile
PS > switch ($xx.directory) {
>> $xx.directory{6}
>> }
At line:2 char:17
+ $xx.directory{6}
+                     ~
Missing statement block in switch statement clause.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingSwitchStatementClause

鉴于之前对 的研究,我希望这两个表达式都会被计算并转换为(匹配)字符串。显然,这将是声明条款。也许发生了一些奇怪的解析。尝试将表达式与语句分开,switch$xx.directory{6}

PS > switch ($xx.directory) {
$xx.directory {6}
}
6
PS >

好的,那么如果我们两者都尝试会发生什么,

PS > switch ($xx.directory) {
>> $xx.directory{5} {6}
>> }
Method invocation failed because [System.IO.FileInfo] does not contain a method named 'directory'.
At line:2 char:1
+ $xx.directory{5} {6}
+ ~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

什么???我知道大括号有点像括号,但这里发生了什么?让我们用一个实际的方法来尝试一下,

PS > 'fred'.substring{1}
Cannot find an overload for "substring" and the argument count: "1".
At line:1 char:1
+ 'fred'.substring{1}
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodCountCouldNotFindBest

但确实有一个参数的重载,尽管它应该是一个 .这是想通过什么?(提示:它是什么样子的?让我们来了解一下,String.Substringint

PS > Add-Type @'
>> public class huh {
>> public void passing(object o)
>> {
>> System.Console.WriteLine(o.GetType().ToString());
>> }
>> }
>> '@
PS > $whatsit=New-Object huh
PS > $whatsit.passing{1}
System.Management.Automation.ScriptBlock
PS >

谁砸了它?

关于唯一的另一个问题是,“有人知道文档中描述的是什么(假设它仍然在 7.2+ 中发生)?(说真的,我想知道是不是。

PowerShell 方法 语法 属性 调用

评论

1赞 Mathias R. Jessen 1/13/2022
虽然这个问题既有趣又切题(并且有一个简单但令人讨厌的解释),并且您的帖子中的内容很棒,请遵循正常惯例:要么制定一个包含可重现代码的问题,并适当地描述您遇到的问题/症状,然后在下面发布自我答案,或者(如果您正在寻求有关此行为的进一步解释), 请重新格式化您的帖子,明确询问“这里发生了什么?”,而不是“谁砸了它”:)
1赞 Uber Kluger 1/17/2022
虽然深埋在帖子中,但有一个问题:“这是否记录在案,如果有,在哪里?@mklement0完全回答了这一点(现在标记为这样)。至于“谁砸了它?”,这不是一个真正的问题,只是一种(过度)口语化的方式,评论从测试中发现看起来像脚本块的东西实际上是脚本块。(那时我已经知道了,因此有提示。

答:

3赞 mklement0 1/14/2022 #1

作为 - 也许是不幸的 - 句法糖,PowerShell 允许您缩短:

$object.Method({ ... })

自:

$object.Method{ ... }

注意:

  • 这两种情况下,方法名称后面都不能有空格(例如,而 C# 允许)。"foo".Substring (1)

  • Method在上面的示例中,只需在语法上作为方法名称有效,即可将两个表达式视为方法调用 - 即使不存在此类方法或名称恰好引用属性,也会尝试方法调用。

换言之:

  • 只接受一个(非可选)类型脚本块[scriptblock];{ ... })允许不带括号的调用 ((...))。

可以说,这样一个狭隘的用例不会要求语法糖:

  • 将支持限制为脚本块会将语法糖限制为 PowerShell 提供/目标的类型及其方法,因为脚本块是特定于 PowerShell 的功能。

  • 用空格分隔名称和开头的要求与通常将脚本块传递给 cmdlet 的方式不一致(例如 HSP的 - 见下文){1, 2, 3 | ForEach-Object { $_ + 1 }(1, 2, 3).ForEach{ $_ + 1 }

  • 一旦必须传递两个或多个参数,就必须切换回状态是很尴尬的。(...)

据推测,引入此方法是为了减少一种常见场景的“语法噪声”:使用 PSv4+ 和数组方法,为 DSC(所需状态配置)功能引入,通常仅使用脚本块调用;例如:.ForEach().Where()

  • (1, 2, 3).ForEach({ $_ + 1 })可以简化为(1, 2, 3).ForEach{ $_ + 1 }

至于文档

  • 该行为仅在上述上下文中描述,并在概念about_Arrays帮助主题的上下文中描述方法:.ForEach().Where()

该语法需要使用脚本块。如果 scriptblock 是唯一的参数,则括号是可选的。此外,方法与左括号或大括号之间不得有空格。

  • 鉴于它适用于具有适当签名的任何方法(无论 .NET 类型如何,也无论它是实例还是静态方法),因此可以说(也)应该将其记录在概念about_Methods帮助主题中,但截至撰写本文时情况并非如此。

然而,即使有覆盖率,挑战在于甚至在您没有预料到的情况下推断出正在尝试方法调用 - 至少在错误消息没有帮助的情况下。about_Methodsswitch


设计思考

请注意,鉴于大多数本机 PowerShell 功能不依赖于方法,并且即使是常规的方法调用语法也可能导致与用于调用所有其他命令(cmdlet、函数、脚本、外部程序)的类似 shell 的语法混淆 - 例如,) 与 ..ForEach().Where()$foo.Get('foo', bar'Get-Foo foo bar

拒绝了 GitHub 上提议引入和运算符的 RFC,这将允许以下更多 PowerShell 惯用语法:-foreach-where

  • 1, 2, 3 -foreach { $_ + 1 }
  • 1, 2, 3, 2 -where { $_ -eq 2 }, 'First'

评论

0赞 Mathias R. Jessen 1/14/2022
你认为这值得作为一个问题提出来吗?IMO 这里的解析逻辑有点过于简单 - 如果单个块令牌跟在成员名称后面,中间没有空格,并且据我所知没有进一步的验证/降低,PowerShell 会不分青红皂白地生成调用表达式。我们可能可以在某些上下文中排除这种行为(例如,当表达式的直接父级是正文时!switch
0赞 mklement0 1/14/2022
@MathiasR.Jessen,再想一想:我已经删除了我的建议,因为它需要根据在运行时之前不可用的信息(给定的成员名称是否引用实际方法)修改解析行为。最后,我认为正确记录当前行为是最好的选择——行为是模糊的,但它是可以预测的。这个例子很不幸,因为错误是如何浮出水面的,但它让我感到非常奇特的场景。switch
0赞 Mathias R. Jessen 1/14/2022
我也考虑过这一点,但是无论如何,当作为静态/独立测试用例提供时,方法调用表达式永远不会被正确调用: gist.github.com/IISResetMe/a0e455507c73824891ebff64d746bd4c - 我确实同意这是非常奇特的,而且“布线”会有点笨拙,但至少我们不会排除/破坏任何已经存在的用例:)
1赞 mklement0 1/15/2022
@MathiasR.Jessen,这样的方法调用确实有效,但它们的结果像往常一样与隐含匹配;更改为您的 Gist 以查看它是否有效;另一个例子:-eqswitch (1)switch ($true)switch (3) { 'foo'.Length { 'three' } }
1赞 Mathias R. Jessen 1/15/2022
不错的收获,没有考虑过。我想我们最好把它留在那里,然后:)