How can I escape an arbitrary string for use as a

2020-03-21 05:23发布

问题:

I have a list of strings and I want to pass those strings as arguments in a single Windows command line call. For simple alphanumeric strings it suffices to just pass them verbatim:

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

I understand that if an argument contains spaces or double-quotes, I need to backslash-escape the double-quotes and backslashes, and then double-quote the argument.

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

But when I try to pass an argument with literal percent signs, this happens:

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

Double quoting doesn't work:

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

Nor does backslash-escaping (notice how the backslashes are present in the output):

> script.pl \%PATH\%
\%PATH\%

Also, the rules are inconsistent for backslash-escaping backslashes:

> script.pl "\\yes\\"
\\yes\
> script.pl "\yes\\"
\yes\
> script.pl "\yes\"
\yes"

Also, doubtless there are special characters in the Windows command line shell, much like there are in all shells. What, then, is the general procedure for safely escaping arbitrary command line arguments for use at the Windows command line?

The ideal answer will describe a function escape() which can be used in situations like the following (a Perl example):

$cmd = join " ", map { escape($_); } @args;

Here are some more example strings which should be safely escaped by this function (I know some of these look Unix-like, that's deliberate):

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$@#?-_

回答1:

Here is an msdn blogpost showing how. It however assumes that every command line program internally uses CommandLineToArgvW to parse it's command line (not a shabby assumption, since it's part of the Shell32 library).

Original link (may not work): http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx

Web archive link: https://web.archive.org/web/20190109172835/https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/



回答2:

To escape a command line argument, use the following:

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;
}

To escape the actual command line command, for example when invoking a command with a ridiculous name such as ()!&%PATH%^;, .exe (which is perfectly legal), use the following:

sub escapeCmd {
  my $arg = shift;

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

Note that using escapeArg() for the command will not work.

Sources:

  • http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
  • http://support.microsoft.com/kb/103368