PowerShell从命令行参数中剥离双引号

最近我一直在使用PowerShell中的GnuWin32时遇到一些麻烦,只要涉及到双引号。

经过进一步的调查,看来PowerShell从命令行参数中剥离了双引号,即使正确转义也是如此。

PS C:\Documents and Settings\Nick> echo '"hello"' "hello" PS C:\Documents and Settings\Nick> echo.exe '"hello"' hello PS C:\Documents and Settings\Nick> echo.exe '\"hello\"' "hello" 

请注意,当传递给PowerShell的echo cmdlet时,双引号是存在的,但是当作为parameter passing给echo.exe时 ,除非用反斜线转义(即使PowerShell的转义字符是反引号,而不是反斜线),否则双引号将被剥离。

这对我来说似乎是一个错误。 如果我将正确的转义string传递给PowerShell,那么PowerShell应该处理任何转义可能是必要的,但是它调用该命令。

这里发生了什么?

目前,解决方法是根据这些规则来逃避命令行参数(这些规则似乎被PowerShell用来调用.exe文件的CreateProcess API调用所使用):

  • 要传递一个双引号,用反斜线转义: \" – > "
  • 要传递一个或多个反斜杠,然后加双引号,用另一个反斜杠转义每个反斜杠,然后转义引号: \\\\\" – > \\"
  • 如果后面没有双引号,则反斜杠不需要转义: \\ – > \\

请注意,进一步转义双引号可能需要将Windows API转义string中的双引号转义为PowerShell。

下面是一些使用GnuWin32的echo.exe的例子:

 PS C:\Documents and Settings\Nick> echo.exe "\`"" " PS C:\Documents and Settings\Nick> echo.exe "\\\\\`"" \\" PS C:\Documents and Settings\Nick> echo.exe "\\" \\ 

我想如果你需要传递一个复杂的命令行参数,这可能会很快变成地狱。 当然,这些都不在CreateProcess()或PowerShell文档中logging。

另外请注意,这是不必要的双引号parameter passing给.NET函数或PowerShell cmdlet。 为此,您只需将双引号转义给PowerShell。

这是一个已知的事情 :

将参数传递给需要引用字符串的应用程序太远了。 我在IRC中用PowerShell专家的一个“房间”问了这个问题,花了一个小时的时间找出一个方法(我原本是在这里发布的,根本不可能)。 这完全破坏了PowerShell作为通用shell的能力,因为我们不能做像执行sqlcmd这样简单的事情。 命令外壳的头号工作应该是运行命令行应用程序…例如,试图从SQL server 2008使用SqlCmd,有一个-v参数,它需要一系列名称:值参数。 如果值中有空格,则必须引用它…

…没有一种方法可以编写一个命令行来正确调用这个应用程序,所以即使你掌握了所有4种或5种不同的引用和转义方法之后,你仍然猜测哪种方法可行?或者,你可以掏出cmd,并完成它。

我个人避免使用'\'在PowerShell中转义,因为它在技术上不是shell转义字符。 我得到了不可预知的结果。 在双引号的字符串中,可以使用""来获得一个嵌入的双引号,或者用反码来转义它:

 PS C:\Users\Droj> "string ""with`" quotes" string "with" quotes 

单引号也是一样:

 PS C:\Users\Droj> 'string ''with'' quotes' string 'with' quotes 

将参数发送到外部程序的奇怪之处在于还有额外的报价评估。 我不知道这是否是一个错误,但我猜测它不会被改变,因为当你使用Start-Process并传入参数时,行为是一样的。 Start-Process为参数提供了一个数组,这使得事情变得更加清晰,就实际发送了多少个参数而言,这些参数似乎在额外的时间被评估。

所以,如果我有一个数组,我可以设置参数值以嵌入引号:

 PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""' PS C:\cygwin\home\Droj> echo $aa arg="foo" arg=""""bar"""" 

“酒吧”的论点足以涵盖额外的隐藏评估。 就好像我将该值用双引号发送到cmdlet,然后再用双引号将结果发送出去:

 PS C:\cygwin\home\Droj> echo "arg=""""bar""""" # level one arg=""bar"" PS C:\cygwin\home\Droj> echo "arg=""bar""" # hidden level arg="bar" 

人们会希望这些参数按照原样传递给外部命令,因为它们是像'echo'/'write-output'这样的cmdlet,但是它们不是,因为隐藏的级别:

 PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""' PS C:\cygwin\home\Droj> start c:\cygwin\bin\echo $aa -nonew -wait arg=foo arg="bar" 

我不知道它的确切原因,但是这种行为好像还有另外一个无证的步骤在重新解析字符串的封面下进行。 例如,如果我将数组发送到cmdlet,则会得到相同的结果,但通过使用invoke-expression添加解析级别:

 PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""' PS C:\cygwin\home\Droj> iex "echo $aa" arg=foo arg="bar" 

…当我将这些参数发送到我的外部cygwin'echo.exe'时,正是我所得到的:

 PS C:\cygwin\home\Droj> c:\cygwin\bin\echo 'arg="foo"' 'arg=""""bar""""' arg=foo arg="bar" 

在撰写本文时,这似乎是在最新版本的PowerShell中修复的,因此不再需要担心。

如果您仍然认为您遇到此问题,请记住它可能与其他内容有关,例如调用PowerShell的程序,所以如果直接从命令提示符或ISE调用PowerShell时无法重现,则应该在别处进行调试。

例如,在调查使用Process.Start从C#代码运行PowerShell脚本时消失的引号问题时,我发现这个问题。 这个问题实际上是C#Process Start需要带双引号的参数 – 它们消失了