windows PowerShell 从命令行参数中去除双引号
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6714165/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
PowerShell stripping double quotes from command line arguments
提问by nw.
Recently I have been having some trouble using GnuWin32 from PowerShell whenever double quotes are involved.
最近,每当涉及双引号时,我都在使用 PowerShell 中的 GnuWin32 时遇到了一些麻烦。
Upon further investigation, it appears PowerShell is stripping double quotes from command line arguments, even when properly escaped.
经过进一步调查,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"
Notice that the double quotes are there when passed to PowerShell's echocmdlet, but when passed as an argument to echo.exe, the double quotes are stripped unless escaped with a backslash (even though PowerShell's escape character is a backtick, not a backslash).
请注意,当传递给 PowerShell 的echocmdlet 时,双引号是存在的,但是当作为参数传递给echo.exe 时,除非使用反斜杠转义,否则双引号将被去除(即使 PowerShell 的转义字符是反斜杠,而不是反斜杠)。
This seems like a bug to me. If I am passing the correct escaped strings to PowerShell, then PowerShell should take care of whatever escaping may be necessary for however it invokes the command.
这对我来说似乎是一个错误。如果我将正确的转义字符串传递给 PowerShell,那么 PowerShell 应该处理它调用命令时可能需要的任何转义。
What is going on here?
这里发生了什么?
For now, the fix is to escape command line arguments in accordance with these rules (which seem to be used by the CreateProcess
API call which PowerShell uses to invoke .exe files):
目前,修复方法是根据这些规则转义命令行参数(CreateProcess
PowerShell 用来调用 .exe 文件的API 调用似乎使用了这些规则):
- To pass a double quote, escape with a backslash:
\"
->"
- To pass a one or more backslashes followed by a double quote, escape each backslash with another backslash and escape the quote:
\\\\\"
->\\"
- If not followed by a double quote, no escaping is necessary for backslashes:
\\
->\\
- 要传递双引号,请使用反斜杠转义:
\"
->"
- 要传递一个或多个反斜杠后跟双引号,请使用另一个反斜杠转义每个反斜杠并转义引号:
\\\\\"
->\\"
- 如果后面没有双引号,则反斜杠不需要转义:
\\
->\\
Note that further escaping of double quotes may be necessary to escape the double quotes in the Windows API escaped string to PowerShell.
请注意,可能需要进一步转义双引号以将 Windows API 转义字符串中的双引号转义到 PowerShell。
Here are some examples, with echo.exefrom GnuWin32:
下面是一些例子,与echo.exe从的GnuWin32:
PS C:\Documents and Settings\Nick> echo.exe "\`""
"
PS C:\Documents and Settings\Nick> echo.exe "\\\`""
\"
PS C:\Documents and Settings\Nick> echo.exe "\"
\
I imagine that this can quickly become hell if you need to pass a complicated command line parameter. Of course, none of this documented in the CreateProcess()
or PowerShell documentation.
我想如果你需要传递一个复杂的命令行参数,这很快就会变成地狱。当然,这些都没有记录在CreateProcess()
或 PowerShell 文档中。
Also note that this is not necessary to pass arguments with double quotes to .NET functions or PowerShell cmdlets. For that, you need only escape your double quotes to PowerShell.
另请注意,这不是将带双引号的参数传递给 .NET 函数或 PowerShell cmdlet 所必需的。为此,您只需要将双引号转义到 PowerShell。
采纳答案by manojlds
It is a known thing:
这是众所周知的事情:
It's FAR TOO HARD to pass parameters to applications which require quoted strings. I asked this question in IRC with a "roomful" of PowerShell experts, and it took hour for someone to figure out a way (I originally started to post here that it is simply not possible). This completely breaks PowerShell's ability to serve as a general purpose shell, because we can't do simple things like executing sqlcmd. The number one job of a command shell should be running command-line applications... As an example, trying to use SqlCmd from SQL Server 2008, there is a -v parameter which takes a series of name:value parameters. If the value has spaces in it, you must quote it...
...there is no single way to write a command line to invoke this application correctly, so even after you master all 4 or 5 different ways of quoting and escaping things, you're still guessing as to which will work when ... or, you can just shell out to cmd, and be done with it.
将参数传递给需要带引号的字符串的应用程序太难了。我在 IRC 中与“满屋”的 PowerShell 专家一起问了这个问题,有人花了一个小时才想出办法(我最初开始在这里发帖说这根本不可能)。这完全破坏了 PowerShell 作为通用 shell 的能力,因为我们不能做诸如执行 sqlcmd 之类的简单事情。命令外壳的首要任务应该是运行命令行应用程序...例如,尝试使用 SQL Server 2008 中的 SqlCmd,有一个 -v 参数,它采用一系列 name:value 参数。如果值中有空格,则必须引用它...
...没有单一的方法可以编写命令行来正确调用此应用程序,因此即使您掌握了所有 4 或 5 种不同的引用和转义方法,您仍然在猜测哪种方法在...或者,您可以直接使用 cmd 并完成它。
回答by Droj
I personally avoid using '\' to escape things in PowerShell, because it's not technically a shell escape character. I've gotten unpredictable results with it. In double-quoted strings, you can use ""
to get an embedded double-quote, or escape it with a back-tick:
我个人避免使用 '\' 来转义 PowerShell 中的内容,因为它在技术上不是 shell 转义字符。我得到了不可预测的结果。在双引号字符串中,您可以使用""
获取嵌入的双引号,或使用反引号将其转义:
PS C:\Users\Droj> "string ""with`" quotes"
string "with" quotes
The same goes for single quotes:
单引号也是如此:
PS C:\Users\Droj> 'string ''with'' quotes'
string 'with' quotes
The weird thing about sending parameters to external programs is that there is additional level of quote evaluation. I don't know if this is a bug, but I'm guessing it won't be changed, because the behavior is the same when you use Start-Processand pass in arguments. Start-Process takes an array for the arguments, which makes things a bit clearer, in terms of how many arguments are actually being sent, but those arguments seem to be evaluated an extra time.
将参数发送到外部程序的奇怪之处在于有额外的报价评估级别。我不知道这是否是一个错误,但我猜它不会改变,因为当您使用Start-Process并传入参数时,行为是相同的。Start-Process 使用一个数组作为参数,这使得实际发送的参数数量变得更加清晰,但这些参数似乎需要额外计算一次。
So, if I have an array, I can set the argument values to have embedded quotes:
所以,如果我有一个数组,我可以将参数值设置为嵌入引号:
PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""'
PS C:\cygwin\home\Droj> echo $aa
arg="foo"
arg=""""bar""""
The 'bar' argument has enough to cover the extra hidden evaluation. It's as if I send that value to a cmdlet in double-quotes, then send that result again in double-quotes:
'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"
One would expect these arguments to be passed to external commands as-is, as they are to cmdlets like 'echo'/'write-output', but they are not, because of that hidden level:
人们会期望这些参数按原样传递给外部命令,就像它们传递给像“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"
I don't know the exact reason for it, but the behavior is as if there is another, undocumented step being taken under the covers that re-parses the strings. For example, I get the same result if I send the array to a cmdlet, but add a parsing level by doing it through invoke-expression
:
我不知道它的确切原因,但这种行为好像在幕后采取了另一个未记录的步骤来重新解析字符串。例如,如果我将数组发送到 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"
...which is exactly what I get when I send these arguments to my external Cygwin instance's 'echo.exe':
...这正是我将这些参数发送到外部 Cygwin 实例的“echo.exe”时得到的:
PS C:\cygwin\home\Droj> c:\cygwin\bin\echo 'arg="foo"' 'arg=""""bar""""'
arg=foo arg="bar"
回答by Martin Ba
TL;DR
TL; 博士
If you just want a solution for Powershell 5, see:
如果您只想要 Powershell 5 的解决方案,请参阅:
ConvertTo-ArgvQuoteForPoSh.ps
:Powershell V5(和 C# 代码)允许转义本机命令参数
The Question I will try to answer
我将尝试回答的问题
..., it appears PowerShell is stripping double quotes from command line arguments, even when properly escaped.
PS C:\Documents and Settings\Nick> echo.exe '"hello"' hello PS C:\Documents and Settings\Nick> echo.exe '\"hello\"' "hello"
Notice that the double quotes are there when passed to PowerShell's echo cmdlet, but when passed as an argument to echo.exe, the double quotes are stripped unless escaped with a backslash(even though PowerShell's escape character is a backtick, not a backslash).
This seems like a bug to me. If I am passing the correct escaped strings to PowerShell, then PowerShell should take care of whatever escaping may be necessaryfor however it invokes the command.
What is going on here?
...,看来 PowerShell 正在从命令行参数中去除双引号,即使正确转义也是如此。
PS C:\Documents and Settings\Nick> echo.exe '"hello"' hello PS C:\Documents and Settings\Nick> echo.exe '\"hello\"' "hello"
请注意,当传递给 PowerShell 的 echo cmdlet 时,双引号是存在的,但是当作为参数传递给 echo.exe 时,双引号将被去除,除非用反斜杠转义(即使 PowerShell 的转义字符是反斜杠,而不是反斜杠)。
这对我来说似乎是一个错误。如果我将正确的转义字符串传递给 PowerShell,那么PowerShell 应该处理它调用命令时可能需要的任何转义。
这里发生了什么?
The Non-Powershell Background
非 Powershell 背景
The fact that you need to escape the quotes with backslashes \
has nothingto to with powershell, but with the CommandLineToArgvW
function that is used by all msvcrt and C# programs to build the argv
array from the single-string command line that the Windows process gets passed.
你需要用反斜杠转义引号的事实\
有没有对PowerShell的,但与CommandLineToArgvW
所使用的所有的msvcrt和C#程序构建功能argv
从单一字符串命令行的Windows进程被传递的数组。
The details are explained at Everyone quotes command line arguments the wrong wayand it basically boils down to the fact that this function historically has very uninutitive escaping rules:
详细信息在每个人都以错误的方式引用命令行参数中进行了解释,它基本上归结为这个函数历史上具有非常单调的转义规则的事实:
- 2n backslashes followed by a quotation mark produce n backslashes followed by begin/end quote. This does not become part of the parsed argument, but toggles the "in quotes" mode.
- (2n) + 1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark literal ("). This does not toggle the "in quotes" mode.
- n backslashes not followed by a quotation mark simply produce n backslashes.
- 2n 个反斜杠后跟一个引号产生 n 个反斜杠后跟开始/结束引号。这不会成为解析参数的一部分,而是切换“在引号中”模式。
- (2n) + 1 个反斜杠后跟一个引号再次产生 n 个反斜杠后跟一个引号文字 (")。这不会切换“在引号中”模式。
- n 个反斜杠后面没有引号只会产生 n 个反斜杠。
leading to the described generic escaping function (shortquote of the logic here):
导致描述的通用转义函数(此处逻辑的简短引用):
CommandLine.push_back (L'"'); for (auto It = Argument.begin () ; ; ++It) { unsigned NumberBackslashes = 0; while (It != Argument.end () && *It == L'\') { ++It; ++NumberBackslashes; } if (It == Argument.end ()) { // Escape all backslashes, but let the terminating // double quotation mark we add below be interpreted // as a metacharacter. CommandLine.append (NumberBackslashes * 2, L'\'); break; } else if (*It == L'"') { // Escape all backslashes and the following // double quotation mark. CommandLine.append (NumberBackslashes * 2 + 1, L'\'); CommandLine.push_back (*It); } else { // Backslashes aren't special here. CommandLine.append (NumberBackslashes, L'\'); CommandLine.push_back (*It); } } CommandLine.push_back (L'"');
CommandLine.push_back (L'"'); for (auto It = Argument.begin () ; ; ++It) { unsigned NumberBackslashes = 0; while (It != Argument.end () && *It == L'\') { ++It; ++NumberBackslashes; } if (It == Argument.end ()) { // Escape all backslashes, but let the terminating // double quotation mark we add below be interpreted // as a metacharacter. CommandLine.append (NumberBackslashes * 2, L'\'); break; } else if (*It == L'"') { // Escape all backslashes and the following // double quotation mark. CommandLine.append (NumberBackslashes * 2 + 1, L'\'); CommandLine.push_back (*It); } else { // Backslashes aren't special here. CommandLine.append (NumberBackslashes, L'\'); CommandLine.push_back (*It); } } CommandLine.push_back (L'"');
The Powershell specifics
Powershell 细节
Now, up to Powershell 5 (including PoSh 5.1.18362.145 on Win10/1909) PoSh knows basically diddly about these rules, nor should it arguably, because these rules are not really general, because any executable you call could, in theory, use some other means to interpret the passed command line.
现在,直到 Powershell 5(包括 Win10/1909 上的 PoSh 5.1.18362.145),PoSh 基本上都知道这些规则,也不应该有争议,因为这些规则并不是真正通用的,因为从理论上讲,您调用的任何可执行文件都可以使用一些解释传递的命令行的其他方法。
Which leads us to -
这导致我们 -
The Powershell Quoting Rules
Powershell 引用规则
What PoSh doesdo however is try to figure out whether the stringsyou pass it as arguments to the native commands need to be quoted because they contain whitespace.
什么辣妹也不过做的是揣摩的字串是否š你把它作为参数传递给本机命令必须用引号括起来,因为它们含有空格。
PoSh - in contrast to cmd.exe
- does a lot more parsing on the command you hand it, since it has to resolve variables and knows about multiple arguments.
PoSh -与此相反cmd.exe
- 对你交给它的命令做了更多的解析,因为它必须解析变量并知道多个参数。
So, given a command like
所以,给定一个命令
$firs = 'whaddyaknow'
$secnd = 'it may have spaces'
$third = 'it may also have "quotes" and other \" weird \ stuff'
EchoArgs.exe $firs $secnd $third
Powershell has to take a stance on how to create the singlestring CommandLine for the Win32 CreateProcess
(or rather the C# Process.Start
) call it will evetually have to do.
Powershell 必须就如何为 Win32 (或者更确切地说是 C# )调用创建单个字符串 CommandLine采取立场,它最终必须这样做。CreateProcess
Process.Start
The approach Powershell takes is weirdand got more complicated in PoSh V7, and as far as I can follow, it's got to do how powershell treats unbalanced quotes in unquoted string. The long stories short is this:
Powershell 采用的方法很奇怪,并且在 PoSh V7 中变得更加复杂,据我所知,它必须处理 powershell 如何处理未加引号的字符串中的不平衡引号。长话短说是这样的:
Powershell will auto-quote (enclose in <"
>) a single argument
string, if it contains spaces andthe spaces don't mix with an
uneven number of (unsescaped) double quotes.
Powershell 将自动引用(用 < "
>括起来)单个参数字符串,如果它包含空格并且空格不与奇数个(未转义的)双引号混合。
The specific quoting rules of PoSh V5 make it impossibleto pass a certain category of string as single argument to a child process.
PoSh V5 的特定引用规则使得无法将特定类别的字符串作为单个参数传递给子进程。
PoSh V7 fixed this, so that as long as all quotes are \"
escaped -- which they need to be anyway to get them through CommandLineToArgvW
-- we can pass any aribtrary string from PoSh to a child executable that uses CommandLineToArgvW
.
PoSh V7 修复了这个问题,因此只要所有引号都被\"
转义了——无论如何它们都需要被转义CommandLineToArgvW
——我们可以将任何来自 PoSh 的任意字符串传递给使用CommandLineToArgvW
.
Here's the rules as C# code as extracted from the PoSh github repo for a tool class of ours:
以下是从 PoSh github 存储库中提取的 C# 代码规则,用于我们的工具类:
PoSh Quoting Rules V5
PoSh 引用规则 V5
public static bool NeedQuotesPoshV5(string arg)
{
// bool needQuotes = false;
int quoteCount = 0;
for (int i = 0; i < arg.Length; i++)
{
if (arg[i] == '"')
{
quoteCount += 1;
}
else if (char.IsWhiteSpace(arg[i]) && (quoteCount % 2 == 0))
{
// needQuotes = true;
return true;
}
}
return false;
}
PoSh Quoting Rules V7
PoSh 报价规则 V7
internal static bool NeedQuotesPoshV7(string arg)
{
bool followingBackslash = false;
// bool needQuotes = false;
int quoteCount = 0;
for (int i = 0; i < arg.Length; i++)
{
if (arg[i] == '"' && !followingBackslash)
{
quoteCount += 1;
}
else if (char.IsWhiteSpace(arg[i]) && (quoteCount % 2 == 0))
{
// needQuotes = true;
return true;
}
followingBackslash = arg[i] == '\';
}
// return needQuotes;
return false;
}
Oh yeah, and they also added ina half baked attempt to correctly escape the and of the quoted string in V7:
哦,是的,他们还添加了半成品的尝试,以正确转义 V7 中引用字符串的 和 :
if (NeedQuotes(arg)) { _arguments.Append('"'); // need to escape all trailing backslashes so the native command receives it correctly // according to http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESDOC _arguments.Append(arg); for (int i = arg.Length - 1; i >= 0 && arg[i] == '\'; i--) { _arguments.Append('\'); } _arguments.Append('"');
if (NeedQuotes(arg)) { _arguments.Append('"'); // need to escape all trailing backslashes so the native command receives it correctly // according to http://www.daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESDOC _arguments.Append(arg); for (int i = arg.Length - 1; i >= 0 && arg[i] == '\'; i--) { _arguments.Append('\'); } _arguments.Append('"');
The Powershell Situation
Powershell 情况
Input to EchoArgs | Output V5 (powershell.exe) | Output V7 (pwsh.exe)
===================================================================================
EchoArgs.exe 'abc def' | Arg 0 is <abc def> | Arg 0 is <abc def>
------------------------------|-----------------------------|---------------------------
EchoArgs.exe '\"nospace\"' | Arg 0 is <"nospace"> | Arg 0 is <"nospace">
------------------------------|-----------------------------|---------------------------
EchoArgs.exe '"\"nospace\""' | Arg 0 is <"nospace"> | Arg 0 is <"nospace">
------------------------------|-----------------------------|---------------------------
EchoArgs.exe 'a\"bc def' | Arg 0 is <a"bc> | Arg 0 is <a"bc def>
| Arg 1 is <def> |
------------------------------|-----------------------------|---------------------------
...
I'm snipping further examples here for time reasons. They shouldn't add overmuch to the answer anyways.
由于时间原因,我在这里截取更多示例。无论如何,他们不应该在答案中添加过多内容。
The Powershell Solution
Powershell 解决方案
To pass arbitrary Strings from Powershell to a native command using CommandLineToArgvW
, we have to:
要使用 将任意字符串从 Powershell 传递到本机命令CommandLineToArgvW
,我们必须:
- properly escape all quotes and Backslashes in the source argument
- This means recognizing the special string-end handling for backslashes that V7 has. (This part is not implemented in the code below.)
- anddetermine whether powershell will auto-quote our escaped string and if it won't auto-quote it, quote it ourselves.
- andmake sure that the string we quoted ourselves then doesn't get auto-quoted by powershell: This is what breaks V5.
- 正确转义源参数中的所有引号和反斜杠
- 这意味着识别 V7 对反斜杠的特殊字符串结尾处理。(这部分在下面的代码中没有实现。)
- 并确定 powershell 是否会自动引用我们的转义字符串,如果它不会自动引用它,请自己引用它。
- 并确保我们自己引用的字符串不会被 powershell 自动引用:这就是破坏 V5 的原因。
Powershell V5 Source code for correctly escaping all arguments to any native command
Powershell V5 源代码,用于正确转义任何本机命令的所有参数
I've put the full code on Gist, as it got too long to include here: ConvertTo-ArgvQuoteForPoSh.ps
: Powershell V5 (and C# Code) to allow escaping native command arguments
我已将完整代码放在 Gist 上,因为这里包含的代码太长ConvertTo-ArgvQuoteForPoSh.ps
::Powershell V5(和 C# 代码)允许转义本机命令参数
- Note that this code tries it's best, but for some strings with quotes in the payload and V5 you simply must add in leading space to the arguments you pass. (See code for logic details).
- 请注意,此代码尽力而为,但对于某些在有效负载和 V5 中带有引号的字符串,您只需在传递的参数中添加前导空格即可。(有关逻辑详细信息,请参阅代码)。
回答by Florian Winter
This seems to be fixed in recent versions of PowerShell at the time of this writing, so it is no longer something to worry about.
在撰写本文时,这似乎已在最新版本的 PowerShell 中得到修复,因此不再需要担心。
If you still think you see this issue, remember it may be related to something else, such as the program that invokes PowerShell, so if you cannot reproduce it when invoking PowerShell directly from a command prompt or the ISE, you should debug elsewhere.
如果您仍然认为您看到此问题,请记住它可能与其他内容有关,例如调用 PowerShell 的程序,因此如果在直接从命令提示符或ISE调用 PowerShell 时无法重现该问题,则应在其他地方进行调试。
For example, I found this question when investigating a problem of disappearing quotes when running a PowerShell script from C# code using Process.Start
. The issue was actually C# Process Start needs Arguments with double quotes - they disappear.
例如,我在调查使用Process.Start
. 问题实际上是C# Process Start 需要带双引号的参数 - 它们消失了。
回答by GGirard
Relying on the CMD to shell out the issue as indicated in the accepted answerdidn't work for me as double quotes were still stripped out when calling the CMD executable.
依靠 CMD 解决已接受的答案中指出的问题对我来说不起作用,因为在调用 CMD 可执行文件时双引号仍然被去掉。
The good solution for me was to structure my command line as an array of strings instead of a single full string containing all the arguments. Then simply pass that array as the arguments for the binary invocation:
对我来说,好的解决方案是将我的命令行构造为一个字符串数组,而不是一个包含所有参数的完整字符串。然后简单地将该数组作为二进制调用的参数传递:
$args = New-Object System.Collections.ArrayList
$args.Add("-U") | Out-Null
$args.Add($cred.UserName) | Out-Null
$args.Add("-P") | Out-Null
$args.Add("""$($cred.Password)""")
$args.Add("-i") | Out-Null
$args.Add("""$SqlScriptPath""") | Out-Null
& SQLCMD $args
In that case, double quotes surrounding arguments are properly passed to the invoked command.
在这种情况下,参数周围的双引号会正确传递给调用的命令。
If you need, you can test and debug it with EchoArgs from the PowerShell Community Extensions.
如果需要,您可以使用PowerShell Community Extensions 中的EchoArgs 对其进行测试和调试。