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 的命令行分析会删除提供的引号字符。 然后,分析后的参数将联接到单个字符串中,每个参数用空格分隔。 然后将此字符串分配给 ProcessStartInfo 对象的 Arguments 属性。 字符串中的引号必须使用额外的引号或反斜杠 (\) 字符进行转义。

注意

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

有关转义要求的详细信息,请参阅 ProcessStartInfo.Arguments 的文档。

注意

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

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

TestExe 的 echoargs 参数显示作为可执行文件的参数接收的值。 可使用此工具验证是否已正确转义参数中的字符。

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\\""

以下所有示例的输出相同:

Arg 0 is <"C:\Program Files (x86)\Microsoft\">

将参数传递给 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>

蒂尔德 (~)

波形符 (~) 在 PowerShell 中具有特殊含义。 当它与路径开头的 PowerShell 命令一起使用时,波形符将扩展到用户的主目录。 如果在路径中的其他任何位置使用波形符,则会将其视为文本字符。

PS D:\temp> $PWD

Path
----
D:\temp

PS D:\temp> Set-Location ~
PS C:\Users\user2> $PWD

Path
----
C:\Users\user2

在此示例中, 名称 参数 New-Item 需要字符串。 波形符被视为文本字符。 若要更改为新创建的目录,必须使用波形符限定路径。

PS D:\temp> Set-Location ~
PS C:\Users\user2> New-Item -Type Directory -Name ~

    Directory: C:\Users\user2

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----            5/6/2024  2:08 PM                ~

PS C:\Users\user2> Set-Location ~
PS C:\Users\user2> Set-Location .\~
PS C:\Users\user2\~> $PWD

Path
----
C:\Users\user2\~

将波形符与本机命令一起使用时,PowerShell 会将波形符作为文本字符传递。 在路径中使用波形符会导致 Windows 上不支持波形符的本机命令出错。

PS D:\temp> $PWD

Path
----
D:\temp

PS D:\temp> Get-Item ~\repocache.clixml

    Directory: C:\Users\user2

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           4/29/2024  3:42 PM          88177 repocache.clixml

PS D:\temp> more.com ~\repocache.clixml
Cannot access file D:\temp\~\repocache.clixml

另请参阅