涉及变量引用和子表达式的参数模式中不带引号的标记:为啥它们有时会分成多个参数?

Posted

技术标签:

【中文标题】涉及变量引用和子表达式的参数模式中不带引号的标记:为啥它们有时会分成多个参数?【英文标题】:Unquoted tokens in argument mode involving variable references and subexpressions: why are they sometimes split into multiple arguments?涉及变量引用和子表达式的参数模式中不带引号的标记:为什么它们有时会分成多个参数? 【发布时间】:2017-06-25 07:24:44 【问题描述】:

注意:此问题的摘要已被 posted at the PowerShell GitHub repository 取代,随后被 this more comprehensive issue 取代。

在 PowerShell 中传递给命令的参数以 参数模式(与 表达式模式相反 - 请参阅 Get-Help about_Parsing)进行解析。

方便地,(双)引用不包含空格或元字符的参数通常是可选的,即使这些参数涉及变量引用(例如$HOME\sub)或子表达式(例如version=$($PsVersionTable.PsVersion) .

在大多数情况下,这些不带引号的参数被视为双引号字符串,并且通常的string-interpolation rules 应用(除了诸如, 之类的元字符需要转义)。

我尝试在this answer 中总结参数模式下未引用标记的解析规则,但有奇怪的边缘情况

具体来说(从 Windows PowerShell v5.1 开始),为什么以下每个命令中的不带引号的参数标记不被识别为单个可扩展字符串,并导致 2 个参数获得通过(变量引用/子表达式保持其类型)?

$(...) 在令牌的开始处:

Write-Output $(Get-Date)/today # -> 2 arguments: [datetime] obj. and string '/today'

请注意,以下工作按预期工作:

Write-Output $HOME/sub - 简单的变量。开头参考 Write-Output today/$(Get-Date) - 子表达式不在开头

.$ 在令牌的开始处:

Write-Output .$HOME  # -> 2 arguments: string '.' and value of $HOME

请注意,以下工作按预期工作:

Write-Output /$HOME - 不同的初始字符。前面$ Write-Output .-$HOME - 初始 . 不直接跟在 $ 后面 Write-Output a.$HOME - . 不是初始字符。

顺便说一句:从 PowerShell Core v6.0.0-alpha.15 开始,= 跟随一个简单的 var。在令牌的 start 处的引用似乎也将令牌分成 2 个参数,这在 Windows PowerShell v5.1 中不会发生 ;例如,Write-Output $HOME=dir

注意:

主要是为所描述的行为寻找一个设计原理,或者,视情况而定,确认它是一个错误。如果它不是错误,我想要一些东西来帮助我将行为概念化,这样我就可以记住它并避免它的陷阱。

所有这些边缘情况都可以通过显式双引号来避免,鉴于上述不明显的行为,这可能是常规使用的最安全选择。


可选阅读:文档状态和设计思路

在撰写本文时,v5.1 Get-Help about_Parsing page:

规则描述不完整

使用的术语既未在主题中定义,也未在 PowerShell 世界中普遍使用(“可扩展字符串”、“值表达式”——尽管人们可以猜到它们的含义)

来自链接页面(强调):

在参数模式下,每个值被视为可扩展字符串 除非开始以下特殊字符之一:美元符号 ($)、at 符号 (@)、单引号 (')、双引号 (") 或左括号 ( ()。

如果前面有这些字符之一,则该值被视为值表达式

顺便说一句:以" 开头的标记当然,根据定义,也是一个可扩展字符串(插值字符串)。 奇怪的是,关于引用的概念性帮助主题 Get-Help about_Quoting_Rules 设法避免使用“扩展”和“插值”这两个术语。

请注意文章如何说明当(非元)字符直接跟随以这些特殊字符开头的标记时会发生什么,特别是@987654356 @

但是,该页面包含一个示例,它表明以变量引用开头的标记也被解释为可扩展字符串

如果$a 包含4Write-Output $a/H 的计算结果为(单个字符串参数)4/H

请注意,段落 确实 暗示了未引用标记(不以特殊字符开头)的 interior 中的变量引用/子表达式。 就像在双引号字符串中一样展开(“视为可展开字符串”)。

如果这些工作:

$a = 4
Write-Output $a/H         # -> '4/H'
Write-Output H/$a         # -> 'H/4'
Write-Output H/$(2 + 2)   # -> 'H/4'

为什么Write-Output $(2 + 2)/H 也不应该扩展到'4/H'(而不是被视为 2 个参数? 为什么一开始的子表达式与变量引用的处理方式不同?

这种微妙的区别很难记住,尤其是在没有理由的情况下。

对我来说更有意义的规则是无条件处理 $ 并且具有附加字符的标记 em> 在变量引用/子表达式之后也是一个可扩展的字符串。 (相比之下,独立变量引用/子表达式保留其类型是有意义的,就像现在一样。)


请注意,以.$ 开头的标记被拆分为两个参数的情况根本不在帮助主题中。


更多可选阅读:跟随一个以 other 特殊字符之一开头的标记和附加字符。

其他特殊标记起始字符中,以下无条件将结构末尾之后的任何字符视为单独的参数(有道理):( ' "

Write-Output (2 + 2)/H   # -> 2 arguments: 4 and '/H'
Write-Output "2 + $a"/H  # -> 2 arguments: '2 + 4' and '/H', assuming $a equals 4
Write-Output '2 + 2'/H   # -> 2 arguments: '2 + 2' and '/H'

顺便说一句:这表明bash 样式的字符串连接 - 将带引号和不带引号的标记的任何组合放在一起 - 在 PowerShell 中通常不支持;它第一个子字符串/变量引用恰好是未引用时起作用。例如,Write-Output H/'2 + 2',与上面的子字符串反转示例不同,只产生一个 single 参数。

例外是@:而@ 确实有特殊含义(请参阅Get-Help about_Splatting),当只是一个语法上有效的变量名(例如,@parms),其他任何事情都会导致令牌再次被视为可扩展字符串

Write-Output @parms    # splatting (results in no arguments if $parms is undefined)

Write-Output @parms$a  # *expandable string*: '@parms4', if $a equals 4

【问题讨论】:

不是powershell开源,技术上可以看源码确定根本原因吗? :) 我一直发现将$var/string 实际解析为一个可扩展字符串与文档所说的相反,因为表达式以$ 开头。 $(date)/sub 作为两个不同的参数(一个值表达式和一个裸字字符串)非常有意义 @briantist:感谢Hello'Hi'sup"whatup" 示例 - 不知道这可行,但事实证明,它仅在第一个子字符串 未引用 时才有效(试试Write-Host -Object 'Hello'sup"whatup"),这正是引发这个问题的那种晦涩的行为。 @mklement0 我能感觉到你。在不通过调试器的情况下跟随 github 上的代码可能会令人抓狂。很高兴看到开发团队中有人参与了这个问题! 我也注意到了这一点,并且养成了一个习惯,如果我想确保我的所有输出都是实心字符串,我会将所有内容都封装在双引号中。写输出“$(Get-Date)/today” 【参考方案1】:

认为你在这里打的更像是“暗示”类型。

您正在使用 Write-Output,它在其概要中指定它

将指定的对象发送到管道中的下一个命令。

此命令旨在接收一个数组。当它像今天一样将第一个项目视为字符串时/它会将其视为字符串。当第一项最终成为函数调用的结果时,它可能是也可能不是字符串,因此它启动了一个数组。

这表明,如果您对 Write-Host 运行相同的命令(旨在将字符串输入输出),它会按照您的预期工作:

 Write-Host $(Get-Date)/today

输出

2018 年 7 月 25 日下午 1:30:43/今天

所以我认为你遇到的边缘情况不是关于解析,而是关于 powershell 使用(并试图隐藏)的类型。

【讨论】:

谢谢,但 Write-Host 也将 $(Get-Date)/today 解析为 2 个不同的参数 - 这不太明显,因为它使用 空格 来分隔输出中的元素,而Write-Output 在自己的行上输出每个元素。换句话说:解析是一致的,在这两种情况下,我都不希望令牌被视为 2 参数,因为简单地 swapping 所涉及的令牌被视为1 参数:Write-Output today/$(Get-Date) 顺便说一句:$(Get-Date)/today 产生的 2 个参数不作为数组传递;它们作为单独的位置参数传递,仅以数组结尾,因为 Write-OutputWrite-Host 使用特殊的 ValueFromRemainingArguments 参数属性标志声明了它们的 -InputObject / -Object 参数,这会导致单独传递的参数在数组中隐式收集。由于这个标志,实际上Write-Output 1, 2 - 传递一个 array - 与 Write-Output 1 2 - 传递单个参数相同。

以上是关于涉及变量引用和子表达式的参数模式中不带引号的标记:为啥它们有时会分成多个参数?的主要内容,如果未能解决你的问题,请参考以下文章

捕获第二个引用的字符串,不带尾随引号/分隔符

在awk代码中引用shell变量的方法

正则表达式将引号添加到不带引号的 CSV 列

winhex脚本命令教程

正则表达式捕获引号内和带/不带空格的数字

如何接受不带引号的文本/字符参数