如何在Windows中转义任意字符串以用作命令行参数?

8

我有一组字符串,希望能够在单个Windows命令行调用中将这些字符串作为参数传递。对于简单的字母数字字符串,直接按原样传递即可:

> script.pl foo bar baz yes no
foo
bar
baz
yes
no

我知道如果一个参数包含空格或双引号,我需要反斜杠转义双引号和反斜杠,然后将参数用双引号括起来。

> script.pl foo bar baz "\"yes\"\\\"no\""
foo
bar
baz
"yes"\"no"

但是当我尝试传递带有字面百分号的参数时,会出现以下情况:

> script.pl %PATH%
C:\Program
Files\PHP\;C:\spaceless\perl\bin\;C:\Program
Files\IBM\Java60\bin;
(...etc.)

双引号无法使用:

> script.pl "%PATH%"
C:\Program Files\PHP\;C:\spaceless\perl\bin\;C:\Program Files\IBM\Java60\bin; (...etc.)

也不要反斜杠转义(注意输出中反斜杠的存在):
> script.pl \%PATH\%
\%PATH\%

此外,反斜杠转义反斜杠的规则不一致:
> script.pl "\\yes\\"
\\yes\
> script.pl "\yes\\"
\yes\
> script.pl "\yes\"
\yes"

此外,毫无疑问,在Windows命令行shell中有特殊字符,就像所有的shell一样。那么,在Windows命令行中安全转义任意命令行参数的一般过程是什么呢?
理想的答案将描述一个可以在以下情况下使用的函数escape()(Perl示例):
$cmd = join " ", map { escape($_); } @args;

以下是一些更多的字符串示例,这些字符串应该通过此函数进行安全转义(我知道其中一些看起来类似于Unix,这是故意的):
yes
no
child.exe
argument 1
Hello, world
Hello"world
\some\path with\spaces
C:\Program Files\
she said, "you had me at hello"
argument"2
\some\directory with\spaces\
"
\
\\
\\\
\\\\
\\\\\
"\
"\T
"\\T
!1
!A
"!\/'"
"Jeff's!"
$PATH
%PATH%
&
<>|&^
()%!^"<>&|
>\\.\nul
malicious argument"&whoami
*@$$A$@#?-_
2个回答

5

这里有一篇微软官方文档,展示了如何使用CommandLineToArgvW解析命令行参数(假设每个命令行程序内部都使用该方法来解析命令行参数,这是一个合理的假设,因为它是Shell32库的一部分)。

原始链接(可能无法访问):http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx

网页存档链接:https://web.archive.org/web/20190109172835/https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/


非常感谢您提供这个非常有用的链接。我的问题现在得到了解答。 - qntm
当然,这似乎只处理命令行参数。同样的过程似乎无法用于转义零号参数,即要启动的程序名称:^"C:\Program Files\p.pl^"会产生'"C:\Program' is not recognized as an internal or external command, operable program or batch file.的错误信息。有什么想法吗? - qntm
看起来http://support.microsoft.com/kb/103368提供了正确的答案。虽然文件名中不可能使用`|`,但是还有其他几个命令可以使用。 - qntm

4
为了转义命令行中的参数,请使用以下方法:
sub escapeArg {
  my $arg = shift;

  # Sequence of backslashes followed by a double quote:
  # double up all the backslashes and escape the double quote
  $arg =~ s/(\\*)"/$1$1\\"/g;

  # Sequence of backslashes followed by the end of the arg,
  # which will become a double quote later:
  # double up all the backslashes
  $arg =~ s/(\\*)$/$1$1/;

  # All other backslashes do not need modifying

  # Double-quote the whole thing
  $arg = "\"".$arg."\"";

  # Escape shell metacharacters
  $arg =~ s/([()%!^"<>&|;, ])/\^$1/g;

  return $arg;
}

为了避免在命令行中运行实际的命令,例如当您需要调用一个具有荒谬名称的命令时,比如()!&%PATH%^;, .exe(这是完全合法的),请使用以下方法:
sub escapeCmd {
  my $arg = shift;

  # Escape shell metacharacters
  $arg =~ s/([()%!^"<>&|;, ])/\^$1/g;
  return $arg;
}

请注意,对于命令使用escapeArg()将不起作用。
来源:

请注意,如果 $arg 不包含空格,则在技术上不需要将 $arg 放在双引号中(并且加倍尾随反斜杠的数量)。请参阅 解析 C++ 命令行参数。我也不认为您必须转义 shell 元字符,例如,如果您直接将命令行传递给 CreateProcess 并且没有涉及 cmd(但是在批处理文件和/或用于 cmd /c/cmd /k 的参数中,您需要转义 shell 元字符)。 - vladr

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