规则
此处记录了解析转义的规则:https ://msdn.microsoft.com/en-us/library/17w5ykft.aspx
Microsoft C/C++ 启动代码在解释操作系统命令行上给出的参数时使用以下规则:
参数由空格分隔,空格可以是空格,也可以是制表符。
插入字符 (^) 未被识别为转义字符或分隔符。该字符在传递到程序中的 argv 数组之前完全由操作系统中的命令行解析器处理。
由双引号 ("string") 包围的字符串被解释为单个参数,无论其中包含什么空格。带引号的字符串可以嵌入到参数中。
- 前面有反斜杠 (\") 的双引号被解释为文字双引号字符 (")。
- 反斜杠按字面意思解释,除非它们紧跟在双引号之前。
- 如果偶数个反斜杠后跟双引号,则在 argv 数组中为每对反斜杠放置一个反斜杠,并将双引号解释为字符串分隔符。
- 如果奇数个反斜杠后跟双引号,则每对反斜杠在 argv 数组中放置一个反斜杠,双引号被剩余的反斜杠“转义”,导致文字双引号(“ ) 放置在 argv 中。
应用于生成
不幸的是,没有关于如何正确转义参数的良好文档,即如何应用上述规则以确保将参数数组正确传递给目标应用程序。以下是我转义每个参数时遵循的规则:
如果参数包含空格或制表符,请将其括在 "(双引号)字符中。
如果参数包含 "(双引号),前面有 \(反斜杠)字符,则在附加转义的 "(双引号)之前用 \(反斜杠)转义前面的 \(反斜杠)字符。
如果参数以一个或多个 \(反斜杠)结尾,并且包含空格,请在添加封闭的 "(双引号)之前使用 \(反斜杠)转义最后的 \(反斜杠)字符。
编码
/// <summary>
/// Convert an argument array to an argument string for using
/// with Process.StartInfo.Arguments.
/// </summary>
/// <param name="argument">
/// The args to convert.
/// </param>
/// <returns>
/// The argument <see cref="string"/>.
/// </returns>
public static string EscapeArguments(string argument)
{
using (var characterEnumerator = argument.GetEnumerator())
{
var escapedArgument = new StringBuilder();
var backslashCount = 0;
var needsQuotes = false;
while (characterEnumerator.MoveNext())
{
switch (characterEnumerator.Current)
{
case '\\':
// Backslashes are simply passed through, except when they need
// to be escaped when followed by a \", e.g. the argument string
// \", which would be encoded to \\\"
backslashCount++;
escapedArgument.Append('\\');
break;
case '\"':
// Escape any preceding backslashes
for (var c = 0; c < backslashCount; c++)
{
escapedArgument.Append('\\');
}
// Append an escaped double quote.
escapedArgument.Append("\\\"");
// Reset the backslash counter.
backslashCount = 0;
break;
case ' ':
case '\t':
// White spaces are escaped by surrounding the entire string with
// double quotes, which should be done at the end to prevent
// multiple wrappings.
needsQuotes = true;
// Append the whitespace
escapedArgument.Append(characterEnumerator.Current);
// Reset the backslash counter.
backslashCount = 0;
break;
default:
// Reset the backslash counter.
backslashCount = 0;
// Append the current character
escapedArgument.Append(characterEnumerator.Current);
break;
}
}
// No need to wrap in quotes
if (!needsQuotes)
{
return escapedArgument.ToString();
}
// Prepend the "
escapedArgument.Insert(0, '"');
// Escape any preceding backslashes before appending the "
for (var c = 0; c < backslashCount; c++)
{
escapedArgument.Append('\\');
}
// Append the final "
escapedArgument.Append('\"');
return escapedArgument.ToString();
}
}
/// <summary>
/// Convert an argument array to an argument string for using
/// with Process.StartInfo.Arguments.
/// </summary>
/// <param name="args">
/// The args to convert.
/// </param>
/// <returns>
/// The argument <see cref="string"/>.
/// </returns>
public static string EscapeArguments(params string[] args)
{
var argEnumerator = args.GetEnumerator();
var arguments = new StringBuilder();
if (!argEnumerator.MoveNext())
{
return string.Empty;
}
arguments.Append(EscapeArguments((string)argEnumerator.Current));
while (argEnumerator.MoveNext())
{
arguments.Append(' ');
arguments.Append(EscapeArguments((string)argEnumerator.Current));
}
return arguments.ToString();
}
测试用例
这是我在验证上述代码时使用的测试用例(线束留给读者作为练习)
注意:我的测试用例是将以下情况的随机数作为输入 args 数组,将其编码为参数字符串,将字符串传递给将参数作为 JSON 数组输出的新进程,并验证输入 args数组匹配输出 JSON 数组。
+----------------------------------------+--------- ----------------------------------+
| 输入字符串 | 转义字符串 |
+----------------------------------------+--------- ----------------------------------+
| 引用的论点 | “引用的论点” |
| "报价 | \"报价 |
| "包装报价" | \"包装报价\" |
| “引用包装报价” | "\"引用包装的报价\"" |
| \反斜杠文字 | \反斜杠文字 |
| \\doubleBackslashLiteral | \\doubleBackslashLiteral |
| 尾随反斜杠\ | 尾随反斜杠\ |
| 双尾反斜杠\\ | 双尾反斜杠\\ |
| \ 带引号的反斜杠文字 | "\ 引用的反斜杠文字" |
| \\ 引用的双反斜杠文字 | "\\ 引用的双反斜杠文字" |
| 引用的尾随反斜杠\ | "引用尾随反斜杠\\" |
| 带引号的双尾反斜杠\\ | "引用的双尾反斜杠\\\\" |
| \"\backslashQuoteEscaping | "\\\"\backslashQuoteEscaping " |
| \\"\doubleBackslashQuoteEscaping | "\\\\\"\doubleBackslashQuoteEscaping " |
| \\"\\doubleBackslashQuoteEscaping | "\\\\"\\doubleBackslashQuoteEscaping " |
| \"\\doubleBackslashQuoteEscaping | "\\\"\\doubleBackslashQuoteEscaping " |
| \"\反斜杠引号转义 | "\\\"\反斜杠引号转义" |
| \\"\双反斜杠引号转义 | "\\\\\\"\双反斜杠引号转义" |
| \\"\\双反斜杠引号转义 | "\\\\\"\\双反斜杠引号转义" |
| \"\\双反斜杠引号转义 | "\\\"\\双反斜杠引号转义" |
| TrailingQuoteEscaping" | TrailingQuoteEscaping\" |
| TrailingQuoteEscaping\" | TrailingQuoteEscaping\\\" |
| TrailingQuoteEscaping\"\ | TrailingQuoteEscaping\\\"\ |
| TrailingQuoteEscaping"\ | TrailingQuoteEscaping\"\ |
| 尾随引号转义" | "尾随引号转义\"" |
| 尾随引号转义\" | "尾随引号转义\\\"" |
| 尾随引号转义\"\ | "尾随引号转义\\\"\\" |
| 尾随引号转义"\ | "尾随引号转义\"\\" |
+----------------------------------------+--------- ----------------------------------+
关于这个问题,这里还有其他答案。我只是更喜欢编码状态机而不是正则表达式(而且,它运行得更快)。
https://stackoverflow.com/a/6040946/3591916很好地解释了如何做到这一点。