转义 c # 中的命令行参数

简而言之:

用引号包装参数并转义 \"是否足够?

代码版本

我想使用 ProcessInfo.Arguments 将命令行参数 string[] args传递给另一个进程。

ProcessStartInfo info = new ProcessStartInfo();
info.FileName = Application.ExecutablePath;
info.UseShellExecute = true;
info.Verb = "runas"; // Provides Run as Administrator
info.Arguments = EscapeCommandLineArguments(args);
Process.Start(info);

问题是,我得到的参数作为一个数组,必须合并到一个单一的字符串。可以精心设计一个论点来欺骗我的程序。

my.exe "C:\Documents and Settings\MyPath \" --kill-all-humans \" except fry"

根据 这个答案,我创建了以下函数来转义一个参数,但是我可能遗漏了一些内容。

private static string EscapeCommandLineArguments(string[] args)
{
string arguments = "";
foreach (string arg in args)
{
arguments += " \"" +
arg.Replace ("\\", "\\\\").Replace("\"", "\\\"") +
"\"";
}
return arguments;
}

这样做是否足够好,或者有什么框架函数可以实现这一点?

41453 次浏览

我编写了一个小示例,向您展示如何在命令行中使用转义字符。

public static string BuildCommandLineArgs(List<string> argsList)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();


foreach (string arg in argsList)
{
sb.Append("\"\"" + arg.Replace("\"", @"\" + "\"") + "\"\" ");
}


if (sb.Length > 0)
{
sb = sb.Remove(sb.Length - 1, 1);
}


return sb.ToString();
}

这里有一个测试方法:

    List<string> myArgs = new List<string>();
myArgs.Add("test\"123"); // test"123
myArgs.Add("test\"\"123\"\"234"); // test""123""234
myArgs.Add("test123\"\"\"234"); // test123"""234


string cmargs = BuildCommandLineArgs(myArgs);


// result: ""test\"123"" ""test\"\"123\"\"234"" ""test123\"\"\"234""


// when you pass this result to your app, you will get this args list:
// test"123
// test""123""234
// test123"""234

重点是用双引号(“ arg”)包装每个 arg,并用转义引号替换 arg 值中的所有引号(测试“123”)。

添加参数的工作做得很好,但是没有转义。在方法中添加了转义序列应该去的地方的注释。

public static string ApplicationArguments()
{
List<string> args = Environment.GetCommandLineArgs().ToList();
args.RemoveAt(0); // remove executable
StringBuilder sb = new StringBuilder();
foreach (string s in args)
{
// todo: add escape double quotes here
sb.Append(string.Format("\"{0}\" ", s)); // wrap all args in quotes
}
return sb.ToString().Trim();
}

事情没那么简单!

我有相关的问题(写前端。将调用后端的所有参数传递 + 一些额外的) ,所以我看了人们如何做到这一点,遇到了你的问题。最初所有似乎都很好做,因为你建议 arg.Replace (@"\", @"\\").Replace(quote, @"\"+quote)

然而,当我使用参数 c:\temp a\\b调用时,这个函数被传递为 c:\tempa\\b,这导致后端被 "c:\\temp" "a\\\\b"调用-这是不正确的,因为将会有两个参数 c:\\tempa\\\\b-这不是我们想要的!我们一直过于热衷于逃避(windows 不是 unix!).

所以我详细阅读了 http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx,它实际上描述了这些情况是如何处理的: 在双引号前面,反斜杠被当作转义 只有

这里有一个关于如何处理多个 \的转折点,这个解释可能会让人头晕一阵子。这里我将试着重新表述 unescape 规则: 假设我们有一个 < em > N \的子字符串,后面跟着 "。当取消转义时,我们用 < em > int (N/2) \替换那个子串,如果 < em > N 是奇数,我们在末尾添加 "

这种解码的编码方式是这样的: 对于一个参数,找到每个0或更多 \的子字符串后跟 ",并将其替换为两倍于 \的子字符串后跟 \"。我们可以这样做:

s = Regex.Replace(arg, @"(\\*)" + "\"", @"$1$1\" + "\"");

仅此而已。

附言... ... 没有。等等,等等-还有更多! :)

我们正确地进行了编码,但是有一个问题,因为您将所有参数都用双引号括起来(以防其中一些参数中有空格)。这里有一个边界问题-如果参数在 \上结束,在它之后添加 "将打破结束报价的含义。示例 c:\one\ two解析为 c:\one\two,然后将重新组装为 "c:\one\" "two",这将使我(错误)理解为一个参数 c:\one" two(我试过了,我没有编造它)。因此,我们还需要检查参数是否以 \结尾,如果是,则检查 双倍结尾的反斜杠数,如下所示:

s = "\"" + Regex.Replace(s, @"(\\+)$", @"$1$1") + "\"";
static string BuildCommandLineFromArgs(params string[] args)
{
if (args == null)
return null;
string result = "";


if (Environment.OSVersion.Platform == PlatformID.Unix
||
Environment.OSVersion.Platform == PlatformID.MacOSX)
{
foreach (string arg in args)
{
result += (result.Length > 0 ? " " : "")
+ arg
.Replace(@" ", @"\ ")
.Replace("\t", "\\\t")
.Replace(@"\", @"\\")
.Replace(@"""", @"\""")
.Replace(@"<", @"\<")
.Replace(@">", @"\>")
.Replace(@"|", @"\|")
.Replace(@"@", @"\@")
.Replace(@"&", @"\&");
}
}
else //Windows family
{
bool enclosedInApo, wasApo;
string subResult;
foreach (string arg in args)
{
enclosedInApo = arg.LastIndexOfAny(
new char[] { ' ', '\t', '|', '@', '^', '<', '>', '&'}) >= 0;
wasApo = enclosedInApo;
subResult = "";
for (int i = arg.Length - 1; i >= 0; i--)
{
switch (arg[i])
{
case '"':
subResult = @"\""" + subResult;
wasApo = true;
break;
case '\\':
subResult = (wasApo ? @"\\" : @"\") + subResult;
break;
default:
subResult = arg[i] + subResult;
wasApo = false;
break;
}
}
result += (result.Length > 0 ? " " : "")
+ (enclosedInApo ? "\"" + subResult + "\"" : subResult);
}
}


return result;
}

我的答案与纳斯 · 巴诺夫的答案相似,但我只在必要时才想要 双引号

去掉额外的不必要的双引号

当你接近参数的字符限制时,我的代码总是不必要地保存 双引号,这很重要。

/// <summary>
/// Encodes an argument for passing into a program
/// </summary>
/// <param name="original">The value that should be received by the program</param>
/// <returns>The value which needs to be passed to the program for the original value
/// to come through</returns>
public static string EncodeParameterArgument(string original)
{
if( string.IsNullOrEmpty(original))
return original;
string value = Regex.Replace(original, @"(\\*)" + "\"", @"$1\$0");
value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"");
return value;
}


// This is an EDIT
// Note that this version does the same but handles new lines in the arugments
public static string EncodeParameterArgumentMultiLine(string original)
{
if (string.IsNullOrEmpty(original))
return original;
string value = Regex.Replace(original, @"(\\*)" + "\"", @"$1\$0");
value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"", RegexOptions.Singleline);


return value;
}

解释

要正确地转义 反斜杠双引号,您只需将多个 反斜杠的任何实例后跟一个 双引号的实例替换为:

string value = Regex.Replace(original, @"(\\*)" + "\"", @"\$1$0");

一个额外的两倍原来的 反斜杠 + 1和原来的 双引号。例如,’’+ 原始反斜杠 + 原始反斜杠 +’”’。我使用 $1 $0,因为 $0有原来的 反斜杠和原来的 双引号,所以它使更换一个更好的阅读。

value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"");

这只能匹配包含空格的整行。

如果匹配,则将 双引号添加到开头和结尾。

如果原来有一个 反斜杠在论点的结尾,他们将不会被引用,现在有一个 双引号在结尾,他们需要。因此,它们是重复的,这将引用它们所有的内容,并防止无意中引用最终的 双引号

它对第一部分进行最小匹配,以便最后一个。*?不会影响到最终的 反斜杠

输出

因此,这些输入产生以下输出

你好

你好

你好123

你好123

你好,世界

“你好世界”

“你好”

“你好”

“你好,世界”

“你好,世界”

“你好,世界”

“你好,世界”

你好,世界

“你好世界”

我也遇到了这个问题。我没有解析 arg,而是使用了完整的原始命令行并删除了可执行文件。这样做还有一个额外的好处,即在调用中保留空白,即使不需要/使用它。它仍然必须在可执行文件中追踪转义,但这似乎比参数更容易。

var commandLine = Environment.CommandLine;
var argumentsString = "";


if(args.Length > 0)
{
// Re-escaping args to be the exact same as they were passed is hard and misses whitespace.
// Use the original command line and trim off the executable to get the args.
var argIndex = -1;
if(commandLine[0] == '"')
{
//Double-quotes mean we need to dig to find the closing double-quote.
var backslashPending = false;
var secondDoublequoteIndex = -1;
for(var i = 1; i < commandLine.Length; i++)
{
if(backslashPending)
{
backslashPending = false;
continue;
}
if(commandLine[i] == '\\')
{
backslashPending = true;
continue;
}
if(commandLine[i] == '"')
{
secondDoublequoteIndex = i + 1;
break;
}
}
argIndex = secondDoublequoteIndex;
}
else
{
// No double-quotes, so args begin after first whitespace.
argIndex = commandLine.IndexOf(" ", System.StringComparison.Ordinal);
}
if(argIndex != -1)
{
argumentsString = commandLine.Substring(argIndex + 1);
}
}


Console.WriteLine("argumentsString: " + argumentsString);

我在 GitHub 上发布了一个小项目,可以处理大多数命令行编码/转义的问题:

Https://github.com/ericpopivker/command-line-encoder

有一个 CommandLineEncoder. Utils.cs类,以及验证编码/解码功能的单元测试。

我从 每个人都以错误的方式引用命令行参数文章中移植了一个 C + + 函数。

它工作得很好,但是您应该注意,cmd.exe对命令行的解释是不同的。如果(像文章的原作者指出的那样,除非)您的命令行将由 cmd.exe解释,那么您还应该转义 shell 元字符。

/// <summary>
///     This routine appends the given argument to a command line such that
///     CommandLineToArgvW will return the argument string unchanged. Arguments
///     in a command line should be separated by spaces; this function does
///     not add these spaces.
/// </summary>
/// <param name="argument">Supplies the argument to encode.</param>
/// <param name="force">
///     Supplies an indication of whether we should quote the argument even if it
///     does not contain any characters that would ordinarily require quoting.
/// </param>
private static string EncodeParameterArgument(string argument, bool force = false)
{
if (argument == null) throw new ArgumentNullException(nameof(argument));


// Unless we're told otherwise, don't quote unless we actually
// need to do so --- hopefully avoid problems if programs won't
// parse quotes properly
if (force == false
&& argument.Length > 0
&& argument.IndexOfAny(" \t\n\v\"".ToCharArray()) == -1)
{
return argument;
}


var quoted = new StringBuilder();
quoted.Append('"');


var numberBackslashes = 0;


foreach (var chr in argument)
{
switch (chr)
{
case '\\':
numberBackslashes++;
continue;
case '"':
// Escape all backslashes and the following
// double quotation mark.
quoted.Append('\\', numberBackslashes*2 + 1);
quoted.Append(chr);
break;
default:
// Backslashes aren't special here.
quoted.Append('\\', numberBackslashes);
quoted.Append(chr);
break;
}
numberBackslashes = 0;
}


// Escape all backslashes, but let the terminating
// double quotation mark we add below be interpreted
// as a metacharacter.
quoted.Append('\\', numberBackslashes*2);
quoted.Append('"');


return quoted.ToString();
}

另一种方法

如果您传递的是一个复杂的对象,比如嵌套的 JSON,并且您可以控制接收命令行参数的系统,那么将命令行 arg/s 编码为 base64,然后从接收系统解码它们要容易得多。

看这里: 将字符串编码/解码到/从 Base64

用例: 我需要传递一个 JSON 对象,该对象包含一个 XML 字符串,这个属性过于复杂,无法转义。这就解决了。

从这个 URL 复制示例代码函数:

Http://csharptest.net/529/how-to-correctly-escape-command-line-arguments-in-c/index.html

您可以让命令行执行,例如:

String cmdLine = EscapeArguments(Environment.GetCommandLineArgs().Skip(1).ToArray());

Skip(1)跳过可执行文件名。