Powershell 7:如何在字符串常量中使用“&”符号

6

我试图在PowerShell中执行以下命令,但是我不知道如何转义URL中的“&”字符。

  az rest `
    --method GET `
    --uri ("https://graph.microsoft.com/v1.0/groups?`$count=true&`$filter=startsWith(displayName,'some+filter+text')&`$select=id,displayName") `
    --headers 'Content-Type=application/json'

作为开始新命令的符号,&字符会打断URL并尝试执行其余部分。
有没有办法告诉PowerShell不要这样做?

1
你尝试使用与转义美元符号相同的字符了吗? - Olaf
你可以尝试使用 stop-parsing 标记 --% - Olaf
我会只使用单引号来表示该URI,这样它就不会被扩展并且可以删除所有反引号。另外,根据Olaf的评论,也可以将反引号添加到和号后面。 - Sage Pourpre
1
@Olaf 两个建议都没有起作用。我以为诀窍是将字符串分成几部分,然后使用单引号连接&元素。例如 ("https://graph.microsoft.com/v1.0/groups?`$count=true" + '"&"' + "$filter=startsWith(displayName,'some+filter+text')" + '"&"' + "$select=id,displayName")。但是这样会添加双引号,如果不加双引号,它又会失败。 - verbedr
我实际上建议使用反引号来转义和美元符号一样的和号,或者使用停止解析标记。你读了我在第二条评论中链接的帮助文档吗? - Olaf
@SagePourpre 反引号不起作用,将所有内容放在单引号中也不行。典型错误消息如下:"$filter" 不被识别为内部或外部命令、可执行程序或批处理文件。"$select" 不被识别为内部或外部命令、可执行程序或批处理文件。 - verbedr
2个回答

10

Olaf的回答提供了一个有效的解决方案;让我来解释一下:

问题的根源是两种行为的结合:

  • 在调用外部程序时,PowerShell仅基于参数值是否包含空格执行按需双引号处理-否则,该参数将被传递不带引号-无论该值是否在PowerShell命令中原本被引用(例如,cmd /c echo abcmd /c echo 'ab'cmd /c echo "ab"都会导致未经引用的ab作为最后一个标记传递给PowerShell在幕后重建并最终用于执行的命令行)。

  • Azure az CLI是实现为批处理文件(az.cmd),当调用批处理文件时,它是cmd.exe解析给定的参数;令人惊讶的是-并且可以说是不适当的-它将它们解析为如果命令是从cmd.exe会话内部提交的。

因此,如果从PowerShell 传递参数到批处理文件,并且该参数(a)不包含空格,但是(b)包含&cmd.exe元字符,则调用将失败

使用cmd /c echo调用作为批处理文件的替代进行简单演示:

# !! Breaks, because PowerShell (justifiably) passes *unquoted* a&b
# !! when it rebuilds the command line to invoke behind the scenes.
PS> cmd /c echo 'a&b'
a
'b' is not recognized as an internal or external command,
operable program or batch file.

有三种解决方法

  • 使用嵌入式"..."引用:
# OK, but with a CAVEAT: 
#   Works as of PowerShell 7.2, but arguably *shouldn't*, because
#   PowerShell should automatically *escape* the embedded " chars. as ""
PS> cmd /c echo '"a&b"'
"a&b"

# Ditto, using an *expandable* (interpolating) PowerShell string:
PS> cmd /c echo "`"$HOME & Family; can't put a `$ value on that.`""
"C:\Users\jdoe & Family; can't put a $ value on that." # e.g. 
# OK, but with a CAVEAT:
#  Requires "..." quoting, but doesn't recognize *PowerShell* variables,
#  also doesn't support single-quoting and line continuation.
PS> cmd /c echo --% "a&b"
 "a&b"
  • 通过 cmd /c 调用并传递一个包含批处理文件调用及其所有参数的 单个 字符串,最终使用 cmd.exe 的语法
# OK (remember, cmd /c echo stands for a call to a batch file, such as az.cmd)
# Inside the single string passed to the outer cmd /c call,
# be sure to use "...", as that is the only quoting cmd.exe understands.
PS> cmd /c 'cmd /c echo "a&b"'
"a&b"

# Ditto, using an *expandable* (interpolating) PowerShell string:
PS> cmd /c "cmd /c echo `"$HOME & Family; can't put a `$ value on that.`""
"C:\Users\jdoe & Family; can't put a $ value on that." # e.g. 

退一步:

现在,如果你不必担心所有这些事情,那岂不是很好? 特别是当你可能不知道或者不关心一个给定的CLI - 比如说az - 是否刚好被实现为一个批处理文件?

作为一个shell,PowerShell应该尽其所能在幕后忠实地传递参数,并且允许调用者专注于仅满足PowerShell的语法规则:

  • 不幸的是,截至目前(PowerShell 7.2),PowerShell在这方面通常表现非常糟糕,无论cmd.exe的怪癖如何 - 请参见此答案进行总结。

  • 关于cmd.exe的(批处理调用)怪癖,PowerShell在将来的版本中可以可预测地补偿它们 - 但不幸的是,似乎不会发生,请参见GitHub问题#15143


非常感谢您的详细解释!不过,我有一个小问题。我正在使用您列表中的第一种解决方法。但是这种方法并不总是有效,虽然大部分时间都可以。由于某种原因,在我的问题调用中,双引号('&')会在最终输出中包含引号。 - verbedr
很高兴听到它对@verbedr有所帮助。关于第一个解决方法,这很奇怪。请注意,cmdecho始终包括它在命令行中看到的双引号,因此解决方法会原样输出"a&b"。你是说有额外的"存在吗?你能否给出一个简单的例子,最好使用cmd /c echo,或者只发生在az.cmd中吗? - mklement0
此外,@verbedr:除了使用--%技术时多出一个前导空格(这不应该有影响),所有cmd /c echo调用都应该接收相同的参数:逐字的"a&b" - mklement0
@verbedr,这个解决方法需要在整个字符串内容周围使用嵌入的"..."引用,而不是子串;如果你想将其应用于双引号的PowerShell字符串,请使用"\"...`""`。 - mklement0
1
@verbedr,我已经在第一个和第三个解决方法中添加了可扩展字符串的示例(基于“--%” 的第二个解决方法通常不支持字符串插值/PowerShell 变量的使用)。 - mklement0
显示剩余3条评论

1

我现在没有访问Azure租户的权限进行测试,而且我对于Azure CLI也没有经验,但我认为以下命令应该可以正常工作:

az rest `
    --method GET `
    --uri 'https://graph.microsoft.com/v1.0/groups?$count=true&$filter=startsWith(displayName,some+filter+text)&$select=id,displayName' `
    --headers 'Content-Type=application/json'

或者这个:
az rest --method GET --headers "Content-Type=application/json" `
    --% --uri "https://graph.microsoft.com/v1.0/groups?$count=true&$filter=startsWith(displayName,some+filter+text)&$select=id,displayName"

我只是为了更好的可读性添加了反引号 - 您可以在实际代码中删除它们。


当删除反引号(行延续符)并且一切都在一行上时,它可以工作。或者像这样将 stop-parsing 移动到最后一行。➜ az rest --method GET --headers 'Content-Type=application/json' --% --uri "https://graph.microsoft.com/v1.0/groups?$count=true&$filter=startsWith(displayName,'some+filter+totest')&$select=id,displayName"问题是需要传递筛选器部分,并且只能使用 %environ% 变量。此外,停止解析仅适用于 Windows 环境。但如果您更新了您的回答,我会标记它。 - verbedr
你搞定了吗? - Olaf
1
是的,谢谢你的帮助,这让我疯狂了。停止解析标记干扰了反引号行继续标记。但如果你改变顺序,它是可以管理的。 - verbedr
我已相应地更改了我的回答。谢谢。 - Olaf
Olaf,--%解决方案可行,但第一个解决方案 - 或许令人惊讶的是 - 不起作用,因为PowerShell不会在幕后双引号引用'https://...'参数,因为它不包含空格,这使得批处理文件az.cmd在包含字符的非双引号引用参数时中断。 - mklement0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接