PowerShell:将JSON字符串传递给curl

19

我正在尝试使用curl将一个JSON字符串从powershell脚本传递给build.phonegap.com API。
根据phonegap的论坛,在Windows机器上运行时,JSON数据必须格式化为:

curl.exe -ku user@email:mypass -X PUT -d "data={\"password\":\"keypass\"}" https://build.phonegap.com/api/v1/key
确实,在命令行中调用时可以正常运行。 然而,当我尝试从PowerShell脚本中调用时,双引号似乎被剥离了。 到目前为止,我尝试过: 将JSON放入单引号字符串中: curl.exe -ku user@email:mypass -X PUT -d '"data={\"password\":\"keypass\"}"' https://build.phonegap.com/api/v1/key 将JSON放入不带DOS转义反斜杠的单引号字符串中: curl.exe -ku user@email:mypass -X PUT -d '"data={"password":"keypass"}"' https://build.phonegap.com/api/v1/key 将JSON放入单引号字符串中,并转义双引号和反斜杠(带有反斜杠的DOS样式): curl.exe -ku user@email:mypass -X PUT -d '\"data={\\\"password\\\":\\\"keypass\\\"}\"' https://build.phonegap.com/api/v1/key 将JSON放入双引号字符串中,并使用PowerShell反引号字符(`)转义双引号: curl.exe -ku user@email:mypass -X PUT -d "`"data={\`"password\`":\`"build*2014`\`"}`"" https://build.phonegap.com/api/v1/key 有什么办法可以实现这一点吗? 感谢您的时间, Koen

1
双引号似乎被剥离了。你如何断言它? - njzk2
我创建了一个虚拟的控制台应用程序,只是回显传入的参数。 - KoenJ
重新审视您的其他解决方案尝试:最后一个实际上应该有效,尽管内部包含的"不是必需的,虽然使用额外的\转义是有效的,但它(a)不应该是必需的,而且(b)如果底层PowerShell问题得到修复,它将会中断,尽管这可能通过修复来缓解 - 目前(自PowerShell Core 7.2.0-preview.5以来)仅作为实验性功能PSNativeCommandArgumentPassing可用,可能需要选择加入假定它成为正式功能。 - mklement0
如果您想使用来自文件的有效载荷数据,请参见此答案 - meJustAndrew
5个回答

21
尝试使用--%运算符将PowerShell置于简单(愚蠢)的参数解析模式中:
curl.exe --% -ku user@email:mypass -X PUT -d "data={\"password\":\"keypass\"}" https://build.phonegap.com/api/v1/key

这对于使用与PowerShell参数语法不兼容的参数语法调用exe程序是非常有用的。这需要PowerShell V3或更高版本。


1
我也在几年前写了一篇关于这个功能的博客文章 http://rkeithhill.wordpress.com/2012/01/02/powershell-v3-ctp2-provides-better-argument-passing-to-exes/ - Keith Hill
@StevenPenny,答案中有一个错别字(已经更正);除此之外,它仍然有效,但是使用 --%停止解析符号会带来一些代价,正如先前的评论所讨论的那样。 - mklement0
@mklement0 甚至纠正了 --% 运算符也是无意义的,因为它仍然会随机删除双引号,并且使得 shell 的使用体验非常糟糕。 - Zombo
@StevenPenny 是的,这会导致糟糕的shell体验,并且现在已经提供了许多指针来解释为什么以及如何解决。不,它不会随机删除双引号。 - mklement0
1
更新:这也在 Powershell 7.2 上正常工作。 - Arindam Roychowdhury

12

更新:

  • PowerShell 7.3.0 基本上修复了问题,但在Windows上存在选择性的异常 - 详细信息请参见this answer

  • 对于跨版本、跨版本的代码,下面讨论的Native模块可能仍然是感兴趣的。

tl;dr:

Windows PowerShellPowerShell (Core) v7.2.x之前的版本中,不幸的是,您必须手动\转义嵌入在传递给外部程序的参数中的"字符。

# From inside PowerShell:
# Note the outer '...' quoting and the unexpected need to escape
# the embedded " chars. as \" (unexpected, because PowerShell itself doesn't
# require " inside '...' to be escaped; also, PowerShell's escape char. is `).
# If outer "..." quoting must be used, use \`" (sic) to escape the embeded "
curl.exe -ku user@email:mypass -X PUT -d 'data={\"password\":\"keypass\"}' https://build.phonegap.com/api/v1/key

继续阅读以了解为什么这是必要的。
PowerShell的转义字符是`(所谓的反引号),因此为了在“…”(双引号,插值)字符串中嵌入“”字符,应该使用`"(或"")而不是\";相比之下,在'...'(单引号,逐字)字符串内部,不需要转义"。
在你的尝试中,PowerShell没有将\"视为转义的",因此看到了多个"..."字符串,最终当PowerShell在幕后必要时应用其按需重新引用时,传递了两个单独的字符串参数,它们分别不需要双引号,因为它们不包含空格,即:verbatim data={\和password:\keypass\}
根据PowerShell的引用规则,你应该使用:
要么: "data={`"password`":`"keypass`"}"
要么,更简单地,由于不需要字符串插值,通过一个逐字、单引号字符串,其中"字符不需要转义: 'data={"password":"keypass"}'
然而,遗憾的是,直到PowerShell 7.2.x,这还不够;请继续阅读详细信息。
在PowerShell 7.2.x之前,需要使用额外的转义层来转义嵌入的"字符,使用\转义调用(大多数)外部程序时:
在Windows PowerShell中,有一些边缘情况下,这种方法不起作用,这种情况下需要使用--%(参见下文)。特别是,将'"foo bar"'转义为'\"foo bar\"'不起作用,因为包围的\"位于字符串的开头和结尾-有关详细信息,请参阅this answer
此外,Windows上的一些外部程序只支持""转义(例如msiexec);对于这些程序,可以使用-replace '"', '""'来以编程方式执行额外的转义,假设值至少包含一个空格。对于根本不支持嵌入"字符的程序(WSH),也要这样做,以便嵌入的"至少不会破坏参数边界(但它们将被删除)。
对于期望\"转义的程序,使用以下-replace操作来以鲁棒的方式以编程方式执行额外的转义:
'...' -replace '(\\*)"', '$1$1\"' 如果输入字符串不包含现有的verbatim \"序列,则可以简化为'...' -replace '"', '\"'
# Note: Escaping the embedded " chars. as `" is enough for PowerShell itself,
#       but, unfortunately, not when calling *external programs*.
#       The `-replace` operation performs the necessary additional \-escaping.
$passwd = 'foo'
curl.exe -ku user@email:mypass -X PUT -d (
  "data={`"password`": `"$passwd`"}" -replace '(\\*)"', '$1$1\"'
) https://build.phonegap.com/api/v1/key
  • 这不应该是必需的,但由于自 v1 以来的一个错误而没有修复,因为担心破坏向后兼容性 - 参见 this answer

  • PowerShell v7.3 大部分修复了这个问题,但在 Windows 上有选择性例外

    • 这些选择性例外 - 见$PSNativeCommandArgumentPassing 首选项变量 的描述 - 是很不幸的,因为它们保留了例外列表中程序的旧的、有问题的行为,特别是 cmd.exe 和 WSH (Windows 脚本宿主) 可执行文件以及它们相关的脚本文件 (.cmd, .bat, .js, .vbs, .wsf),存在将来逐步添加到例外列表中的风险 - 参见来自GitHub issue #18660的摘要

    • 遗憾的是,错过了一次通过在 Windows 上为高知名度的 CLIs 进行幕后适应而避免需要这些例外的机会;参见来自GitHub issue #15143的摘要。

  • 一个 向后和向前兼容的辅助函数是来自Native 模块 (Install-Module Native) 的ie 函数,它消除了额外转义的需要,在 Windows 上包含了高知名度的 CLIs 的重要适应性,并且即使修复已经完成,也将按预期工作:
    ie curl.exe ... -d "data={`"password`": `"$passwd`"}" ... )

  • 使用--%,即 停止解析符号,就像Keith Hill的回答中所示,是一个次优解决方法,它也不需要额外的 \ 转义,然而

    • --% 有固有的限制 - 参见GitHub 文档问题 #6149 - 在类 Unix 平台上几乎无用 - 参见 GitHub 文档问题 #4963
    • --% 后面的参数中嵌入 PowerShell 的变量值的唯一 - 令人尴尬且产生副作用的 - 方法是 (a) 将它们定义为环境变量(例如,$env:passwd = 'foo'),并且 (b) 引用这些变量时cmd.exe 风格的方式,即使在 Unix 上也是如此(例如,%passwd%)。
  • 一种替代方法 - 特别是如果您需要在调用中包含 PowerShell 变量或表达式的值 - 是通过单个参数调用 cmd /c 并包含整个命令行;为了方便引用,以下示例使用了一个here-string(请参见this answer底部部分了解 PowerShell 的字符串字面量):

    # Use @"<newline>...<newline>"@ if you need to embed PowerShell variables / expressions.
    cmd /c @'
    curl.exe -ku user@email:mypass -X PUT -d "data={\"password\":"\keypass\"}" https://build.phonegap.com/api/v1/key
    '@
    

1
感谢详细的回答,但坦白说这些都不是可接受的解决方案。我想要运行像这样的命令:curl.exe -d '{"north": "east west"}' https://reqbin.com/echo/post/json,而不需要所有额外的转义。因为我使用的是单引号,所以应该可以工作。在Bash中,您可以使用单引号来代替转义。PowerShell不能做到这一点,这是可悲的。 - Zombo
答案阐述了情况的现实,尽管你(和我)可能不喜欢它,并提供了目前可用的最佳解决方案。我同意PowerShell在这个领域的缺点非常不幸,我一直在努力让他们修复它。一个“选择加入”的修复程序可能终于要来了,但它将在多大程度上解决Windows上的问题还有待观察。你不喜欢这种情况的现实并不是这个答案的缺点。 - mklement0
1
@LuizFelipe,归根结底,这里的问题在于Windows让应用程序自己解析命令行,为每个人制定自己的规则打开了大门。cmd.exe确实制定了古怪的规则,而PowerShell添加了自己的错误行为。cmd.exe不再得到积极维护,所以我的希望是PowerShell不仅会修复自己的错误,还会弥补cmd.exe的错误 - 不幸的是,只有前者会发生,甚至可能需要选择加入。Base64编码很好(powershell.exe也支持它),但要有用,您需要一种从调用shell创建它的简单方法。 - mklement0
是的,但那艘船在几十年前就已经开走了。看起来他们将在下一个版本中使用新选项进行那样的操作,他们将对cmd.exe和其他旧工具进行特殊处理。 - Luiz Felipe
1
@LuizFelipe,不幸的是,这并不是特例(如果做得正确,将是一种福音):当前计划是保留旧的、_损坏的_行为(在PowerShell方面损坏)。 - mklement0
显示剩余4条评论

2

终于找到解决方案了。不要使用一个 ",而是使用三个("""),就可以了。所以代码应该是这样的:

data={"""password""":"""keypass"""}

美丽的 pwsh -nop -c ' bash -c ''printf """""""\\033[38;2;255;0;0mHi\\033[0m"""""""'' '。谢谢 - Luiz Felipe
1
@LuizFelipe,使用PowerShell的_CLI_(powershell.exepwsh)与-Command(-c)参数引入了额外的引号考虑因素,这些考虑因素与此问题无关。简而言之:为了从Windows上的PowerShell外部传递"作为-Command参数的一部分,请使用\"。在边缘情况下,当从cmd.exe调用时,请使用"^""powershell.exe""pwsh.exe - 请参见此答案 - mklement0
卡在 PSVersion 5.1.19041.2673 上了,最终不得不使用命令行中的三重引号。还没有尝试从脚本中执行…… - GTAE86

0
这是我在PowerShell 7.2中成功发送json请求的方法 输入图像描述

你的回答重复了之前回答中的技巧,没有解释,并且淹没在大量的次要信息中。 - mklement0
你的回答重复了之前回答中的技巧,没有解释,并且埋藏在大量的次要信息中。 - undefined

-1

设置内容类型:

curl -H "Content-Type: application/json" -d '{"password":"keypass"}' https://build.phonegap.com/api/v1/key

2
这没有任何区别。请注意,当从命令行调用该命令时,它可以正常工作,因此问题不在于传递的参数;它与Powershell和转义字符有关。 - KoenJ
3
如果在 PowerShell 中调用 curl,它实际上是 Invoke-WebRequest 的别名,这可能不是你想要的,你应该使用 curl.exe。你可以使用 Get-Alias 命令来查看所有别名。 - MortenB
不错的观点,@MortenB - 这绝对是在 Windows PowerShell (PowerShell 版本高达 v5.1) 中的一个要求。相比之下,在 PowerShell 的跨平台版本 PowerShell (Core) 7+ 中,curl 不再是内置别名,并且会调用 curl.exe - mklement0

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