about_Parsing

简短说明

介绍 PowerShell 如何分析命令。

长说明

在命令提示符处输入命令时,PowerShell 会将命令文本分解为一系列称为标记的段,然后确定如何解释每个标记。

例如,如果键入:

Write-Host book

PowerShell 会将命令拆分为两个标记(Write-Hostbook),并使用两种主要分析模式之一(表达式模式和参数模式)独立解释每个标记。

注意

当 PowerShell 分析命令输入时,它会尝试将命令名称解析为 cmdlet 或本机可执行文件。 如果命令名称没有完全匹配,PowerShell 会在命令前面添加 Get- 作为默认谓词。 例如,PowerShell 将 Service 分析为 Get-Service。 出于以下原因,建议不要使用此功能:

  • 效率低下。 这会导致 PowerShell 多次搜索。
  • 首先会解析具有相同名称的外部程序,因此可能无法执行预期的 cmdlet。
  • Get-HelpGet-Command 无法识别无谓词名称。
  • 命令名称可以是保留字或语言关键字。 Process 是这两者,无法解析为 Get-Process

表达式模式

表达式模式用于组合表达式,脚本语言中值操作需要这些表达式。 表达式是 PowerShell 语法中值的表示形式,可以是简单的,也可以是复合的,例如:

文本表达式是其值的直接表示形式:

'hello'
32

变量表达式具有它们引用的变量的值:

$x
$script:path

运算符将其他表达式组合在一起来进行计算:

-12
-not $Quiet
3 + 7
$input.Length -gt 1
  • 字符串字面量必须用引号括住。
  • 数字被视为数值而不是一系列字符(除非转义)。
  • 运算符(包括 --not 等一元运算符以及 +-gt 等二元运算符)被解释为运算符,并对其参数(操作数)应用各自的运算。
  • 属性和转换表达式被分析为表达式,并应用于从属表达式。 例如:[int] '7'
  • 变量引用的计算结果为其值,但禁止展开,这会导致出现分析程序错误。
  • 其他任何内容都被视为要调用的命令。

参数模式

分析时,PowerShell 首先将输入解释为表达式。 但是,当遇到命令调用时,分析将继续处于参数模式。 如果有包含空格(如路径)的参数,则必须将这些参数值用引号括住。

自变量模式旨在分析 shell 环境中的命令的自变量和参数。 所有输入都被视为可扩展字符串,除非它使用以下语法之一:

  • 美元符号 ($) 后跟变量名称是变量引用的开头,否则将其解释为可扩展字符串的一部分。 变量引用可以包括成员访问或索引。

    • 简单变量引用之后的其他字符(如 $HOME)被视为同一参数的一部分。 将变量名称括在大括号 ({}) 中以将其与后续字符分开。 例如 ${HOME}
    • 当变量引用包含成员访问时,任何其他字符中的第一个字符被视为新自变量的开头。 例如,$HOME.Length-more 生成两个参数:$HOME.Length 的值和字符串字面量 -more
  • 引号('")是字符串的开头

  • 大括号 ({}) 是新脚本块的开头

  • 逗号 (,) 引入了作为数组传递的列表,除非正在调用的命令是本机应用程序,在这种情况下,它们被解释为可扩展字符串的一部分。 不支持初始逗号、连续逗号或尾随逗号。

  • 括号 (()) 是新表达式的开头

  • 子表达式运算符 ($()) 是嵌入表达式的开头

  • 初始 @ 符号是表达式语法的开头,例如展开 (@args)、数组 (@(1,2,3)) 和哈希表文本 (@{a=1;b=2})。

  • 标记开头的 ()$()@() 创建一个新的分析上下文,该上下文可以包含表达式或嵌套命令。

    • 后跟其他字符时,第一个附加字符被视为新的单独参数的开头。
    • 如果前面是无引号的文本,$() 的工作方式类似于可展开的字符串,() 是作为表达式的新参数的开头,而 @() 被视为带有 () 的文本 @,它是作为表达式的新参数的开头。
  • 其他所有内容都被视为可扩展字符串,但仍然需要转义的元字符除外。 请参阅处理特殊字符

    • 参数模式元字符(具有特殊语法含义的字符)是:<space> ' " ` , ; ( ) { } | & < > @ #。 其中,< > @ # 仅在标记开头是特殊的。
  • 停止分析标记 (--%) 使所有剩余参数的解释发生变化。 有关详细信息,请参阅下面的停止分析标记部分。

示例

下表提供了在表达式模式和参数模式中处理的标记的几个示例,还提供了这些标记的计算。 对于这些示例,$a 变量的值为 4

示例 “模式” 结果
2 表达式 2(整数)
`2 表达式 "2"(命令)
Write-Output 2 表达式 2(整数)
2+2 表达式 4(整数)
Write-Output 2+2 参数 "2+2"(字符串)
Write-Output(2+2) 表达 4(整数)
$a 表达 4(整数)
Write-Output $a 表达 4(整数)
$a+2 表达式 6(整数)
Write-Output $a+2 参数 "4+2"(字符串)
$- 参数 "$-"(命令)
Write-Output $- 参数 "$-"(字符串)
a$a 表达式 "a$a"(命令)
Write-Output a$a 参数 "a4"(字符串)
a'$a' 表达 "a$a"(命令)
Write-Output a'$a' 参数 "a$a"(字符串)
a"$a" 表达式 "a$a"(命令)
Write-Output a"$a" 参数 "a4"(字符串)
a$(2) 表达 "a$(2)"(命令)
Write-Output a$(2) 参数 "a2"(字符串)

每个标记都可被解释为某种对象类型,例如布尔值字符串。 PowerShell 尝试根据表达式确定对象类型。 对象类型取决于命令期望的参数类型,以及 PowerShell 是否知道如何将参数转换为正确的类型。 下表显示了分配给表达式返回的值的类型的几个示例。

示例 “模式” 结果
Write-Output !1 argument "!1"(字符串)
Write-Output (!1) expression False(布尔值)
Write-Output (2) 表达式 2(整数)
Set-Variable AB A,B argument 'A','B'(数组)
CMD /CECHO A,B argument 'A,B'(字符串)
CMD /CECHO $AB 表达式 'A B'(数组)
CMD /CECHO :$AB argument ':A B'(字符串)

处理特殊字符

反撇号字符 (`) 可用于转义表达式中的任何特殊字符。 这最适用于转义要用作文本字符而不是元字符的参数模式元字符。 例如,若要使用美元符号 ($) 作为可展开字符串中的文本,可执行以下命令:

"The value of `$ErrorActionPreference is '$ErrorActionPreference'."
The value of $ErrorActionPreference is 'Continue'.

续行符

还可在行末尾使用反引号字符,以便在下一行继续输入。 这提高了命令的可读性,该命令采用多个具有长名称和参数值的参数。 例如:

New-AzVm `
    -ResourceGroupName "myResourceGroupVM" `
    -Name "myVM" `
    -Location "EastUS" `
    -VirtualNetworkName "myVnet" `
    -SubnetName "mySubnet" `
    -SecurityGroupName "myNetworkSecurityGroup" `
    -PublicIpAddressName "myPublicIpAddress" `
    -Credential $cred

但是,应避免使用续行符。

  • 反撇号字符很难看到,也很容易被忘记。
  • 反撇号后面加额外空格会中断续行符。 由于该空格很难看到,因此很难找到错误。

PowerShell 提供了多种在语法中的自然点进行换行的方式。

  • 在管道字符 (|) 后面
  • 在二元运算符(+--eq 等)后面
  • 在数组中的逗号 (,) 后面
  • [{( 等开始字符后面

对于大型参数集,请改用展开。 例如:

$parameters = @{
    ResourceGroupName = "myResourceGroupVM"
    Name = "myVM"
    Location = "EastUS"
    VirtualNetworkName = "myVnet"
    SubnetName = "mySubnet"
    SecurityGroupName = "myNetworkSecurityGroup"
    PublicIpAddressName = "myPublicIpAddress"
    Credential = $cred
}
New-AzVm @parameters

将自变量传递到本机命令

从 PowerShell 运行本机命令时,参数首先由 PowerShell 进行分析。 然后,分析后的参数将联接到单个字符串中,每个参数用空格分隔。

例如,以下命令调用 icacls.exe 程序。

icacls X:\VMS /grant Dom\HVAdmin:(CI)(OI)F

若要在 PowerShell 2.0 中运行此命令,必须使用转义字符来防止 PowerShell 错误解释括号。

icacls X:\VMS /grant Dom\HVAdmin:`(CI`)`(OI`)F

停止分析标记

从 PowerShell 3.0 开始,可以使用停止分析 (--%) 标记来阻止 PowerShell 将输入解释为 PowerShell 命令或表达式。

注意

停止分析令牌仅用于在 Windows 平台上使用本机命令。

调用本机命令时,请将停止分析标记放置在程序参数的前面。 相比于使用转义字符来防止错误解释,此方法要简单得多。

遇到停止分析标记时,PowerShell 会将行中的剩余字符视为文本。 它执行的唯一解释是替换使用标准 Windows 表示法的环境变量的值,例如 %USERPROFILE%

icacls X:\VMS --% /grant Dom\HVAdmin:(CI)(OI)F

PowerShell 将以下命令字符串发送到 icacls.exe 程序:

X:\VMS /grant Dom\HVAdmin:(CI)(OI)F

停止分析标记仅在下一个换行符或管道字符之前有效。 不能使用续行符 (`) 来扩展其效果,也不能使用命令分隔符 (;) 终止其效果。

%variable% 环境变量引用之外,不能在命令中嵌入任何其他动态元素。 不支持将 % 字符转义为 %%(可在批处理文件中执行操作的方式)。 %<name>% 标记不断扩展。 如果 <name> 未引用定义的环境变量,则标记将按原样传递。

不能使用流重定向(如 >file.txt),因为它们作为参数传递给目标命令。

在下例中,第一步将运行命令而不使用停止分析标记。 PowerShell 将计算带引号的字符串,并将值(不含引号)传递给 cmd.exe,这会导致错误。

PS> cmd /c echo "a|b"
'b' is not recognized as an internal or external command,
operable program or batch file.
PS> cmd /c --% echo "a|b"
"a|b"

注意

使用 PowerShell cmdlet 时不需要停止分析令牌。 但是,将参数传递给 PowerShell 函数可能很有用,该函数旨在使用这些参数调用本机命令。

传递包含引号字符的参数

某些本机命令需要包含引号字符的参数。 PowerShell 7.2 包括 PSNativeCommandArgumentPassing 试验功能,该功能改变命令行的本机命令分析方式。 有关详细信息,请参阅使用试验性功能

注意

新行为是对 Window PowerShell 5.1 行为的中断性变更。 在调用本机应用程序时,这可能会中断用于解决各种问题的脚本和自动化。 使用停止分析标记 (--%) 或 Start-Process cmdlet 可在需要时避开本机参数传递。

新的 $PSNativeCommandArgumentPassing 首选项变量控制此行为。 可使用此变量选择运行时的行为。 有效值是 LegacyStandardWindows。 默认行为特定于平台。 在 Windows 平台上,默认设置为 Windows,在非 Windows 平台上,默认为 Standard

Legacy 是历史行为。 WindowsStandard 模式的行为相同,但在 Windows 模式下,对以下文件的调用将自动使用 Legacy 样式参数传递。

  • cmd.exe
  • cscript.exe
  • wscript.exe
  • .bat 结尾
  • .cmd 结尾
  • .js 结尾
  • .vbs 结尾
  • .wsf 结尾

如果 $PSNativeCommandArgumentPassing 设置为 LegacyStandard,分析程序不会检查这些文件。

注意

下面的示例使用 TestExe.exe 工具。 可从源代码生成 TestExe。 请参阅 PowerShell 源存储库中的 TestExe

此更改提供的新行为:

  • 现在会保留带有嵌入引号的文本或可扩展字符串:

    PS> $a = 'a" "b'
    PS> TestExe -echoargs $a 'c" "d' e" "f
    Arg 0 is <a" "b>
    Arg 1 is <c" "d>
    Arg 2 is <e f>
    
  • 现在会保留作为参数的空字符串:

    PS> TestExe -echoargs '' a b ''
    Arg 0 is <>
    Arg 1 is <a>
    Arg 2 is <b>
    Arg 3 is <>
    

这些示例的目标是将目录路径(带空格和引号)"C:\Program Files (x86)\Microsoft\" 传递给本机命令,以便它以带引号的字符串形式接收路径。

WindowsStandard 模式下,以下示例产生预期结果:

TestExe -echoargs """${env:ProgramFiles(x86)}\Microsoft\"""
TestExe -echoargs '"C:\Program Files (x86)\Microsoft\"'

若要在 Legacy 模式下获得相同的结果,必须转义引号或使用停止分析标记 (--%):

TestExe -echoargs """""${env:ProgramFiles(x86)}\Microsoft\\"""""
TestExe -echoargs "\""C:\Program Files (x86)\Microsoft\\"""
TestExe -echoargs --% ""\""C:\Program Files (x86)\Microsoft\\"\"""
TestExe -echoargs --% """C:\Program Files (x86)\Microsoft\\""
TestExe -echoargs --% """%ProgramFiles(x86)%\Microsoft\\""

注意

PowerShell 无法将反斜杠 (\) 字符识别为转义字符。 它是基础 API 用于 ProcessStartInfo.ArgumentList 的转义字符。

PowerShell 7.3 还添加了跟踪本机命令的参数绑定的功能。 有关更多信息,请参阅 Trace-Command

将参数传递给 PowerShell 命令

从 PowerShell 3.0 开始,可以使用参数结束标记 (--) 让 PowerShell 停止将输入解释为 PowerShell 参数。 这是 POSIX Shell 和实用程序规范中指定的约定。

参数结束标记

参数结束标记 (--) 指示其后面的所有自变量都按其实际格式传递,就如同在它们周围添加了双引号一样。 例如,使用 -- 可以输出字符串 -InputObject,而无需使用引号或将其解释为参数:

Write-Output -- -InputObject
-InputObject

与停止分析标记 (--%) 不同,-- 标记后面的任何值都可以被 PowerShell 解释为表达式。

Write-Output -- -InputObject $env:PROCESSOR_ARCHITECTURE
-InputObject
AMD64

此行为仅适用于 PowerShell 命令。 如果在调用外部命令时使用 -- 标记,-- 字符串将作为参数传递给该命令。

TestExe -echoargs -a -b -- -c

输出显示 -- 作为参数传递给 TestExe

Arg 0 is <-a>
Arg 1 is <-b>
Arg 2 is <-->
Arg 3 is <-c>

另请参阅