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

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

以下不是实际问题,而是关于一些意外的 PowerShell 语法的警示故事。唯一真正的问题是“这种行为是为许多人所熟知还是仅为少数 PowerShell 开发人员所熟知(即那些工作 ON PowerShell 而不仅仅是 WITH 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 上的 previous research,我希望 $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

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

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.Substring 确实有一个参数的重载,尽管它应该是一个 int。这是要传递什么? (提示:它长什么样子?)让我们来看看,

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 允许您缩短:

$object.Method({ ... })

至:

$object.Method{ ... }

注:

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

  • 上例中的
  • Method 仅需 作为方法名称在语法上有效 才能将两个表达式视为方法调用- 尝试调用方法,即使不存在这样的方法或名称恰好引用 属性

换句话说:

  • 接受完全一个(非可选)参数类型script block的方法([scriptblock]; { ... }) 允许不带括号的调用 ((...)).

可以说,如此狭窄的用例不需要语法糖:

  • 限制对脚本块的支持将语法糖限制为PowerShell-provided/-targeted类型及其方法,假设脚本块是特定于 PowerShell 的功能。

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

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

据推测,这是为了减少一种常见情况的“语法噪音”:使用 PSv4+ .ForEach().Where() array methods, introduced for the DSC (Desired State Configuration) 功能,通常 仅通过脚本块调用;例如:

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

关于文档:

  • 行为是 described only in the context of the aforementioned .ForEach() and .Where() methods in the context of the conceptual about_Arrays 帮助主题:

The syntax requires the usage of a script block. Parentheses are optional if the scriptblock is the only parameter. Also, there must not be a space between the method and the opening parenthesis or brace.

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

即使在 about_Methods 中有覆盖,但挑战是甚至 推断 正在尝试 方法调用 当您不希望出现这种情况时 - 至少在 switch 情况下错误消息没有帮助。


设计思路:

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

This rejected RFC on GitHub 提议引入 -foreach-where 运算符 ,这将允许以下更符合 PowerShell 惯用的语法:

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