提问人:Uber Kluger 提问时间:1/13/2022 更新时间:1/14/2022 访问量:100
谁见过这个潜在的 powershell 对象属性被视为方法调用诱杀陷阱?
Who has seen this potential powershell object property treated as method call booby trap?
问:
以下内容不是一个实际问题,而是关于一些意外的 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.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{ ... }
注意:
在这两种情况下,方法名称后面都不能有空格(例如,而 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_Methods
switch
设计思考:
请注意,鉴于大多数本机 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'
评论
switch
switch
-eq
switch (1)
switch ($true)
switch (3) { 'foo'.Length { 'three' } }
评论