ルール
エスケープを解析するためのルールは、https ://msdn.microsoft.com/en-us/library/17w5ykft.aspx に記載されています。
Microsoft C/C++ スタートアップ コードは、オペレーティング システムのコマンド ラインで指定された引数を解釈するときに、次の規則を使用します。
引数は空白 (スペースまたはタブ) で区切られます。
キャレット文字 (^) は、エスケープ文字または区切り文字として認識されません。文字は、プログラムの argv 配列に渡される前に、オペレーティング システムのコマンド ライン パーサーによって完全に処理されます。
二重引用符で囲まれた文字列 ("string") は、含まれている空白に関係なく、1 つの引数として解釈されます。引用符で囲まれた文字列は、引数に埋め込むことができます。
- バックスラッシュ (\") が前にある二重引用符は、リテラルの二重引用符文字 (") として解釈されます。
- バックスラッシュは、二重引用符の直前にない限り、文字どおりに解釈されます。
- 偶数のバックスラッシュの後に二重引用符が続く場合、バックスラッシュのペアごとに 1 つのバックスラッシュが argv 配列に配置され、二重引用符は文字列の区切り記号として解釈されます。
- 奇数のバックスラッシュの後に二重引用符が続く場合、バックスラッシュのペアごとに 1 つのバックスラッシュが argv 配列に配置され、残りのバックスラッシュによって二重引用符が「エスケープ」され、リテラルの二重引用符 (" ) argv に配置されます。
世代への適用
残念ながら、引数を適切にエスケープする方法、つまり、上記の規則を適用して引数の配列がターゲット アプリケーションに正しく渡されるようにする方法に関する適切なドキュメントはありません。各引数をエスケープするために私が従ったルールは次のとおりです。
引数にスペースまたはタブが含まれる場合は、" (ダブル クォーテーション) 文字で囲みます。
引数に \ (バックスラッシュ) 文字が先行する " (二重引用符) が含まれている場合は、エスケープされた " (二重引用符) を追加する前に、先行する \ (バックスラッシュ) 文字を \ (バックスラッシュ) でエスケープします。
引数が 1 つ以上の \ (バックスラッシュ) で終わり、空白が含まれている場合は、最後の \ (バックスラッシュ) 文字を \ (バックスラッシュ) でエスケープしてから、" (二重引用符) を追加します。
コード
/// <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 配列として出力し、入力引数がarray は、出力 JSON 配列と一致します。
+-----------------------------------------------------+--------- -----------------------------------+
| | 入力文字列 | エスケープされた文字列 |
+-----------------------------------------------------+--------- -----------------------------------+
| | 引用された引数 | "引用された引数" |
| | "引用 | \"引用 |
| | "ラップされた引用" | \"wrappedQuote\" |
| | "引用されたラップされた引用" | "\"引用ラップ引用\"" |
| | \backslashLiteral | \backslashLiteral |
| | \\doubleBackslashLiteral | \\doubleBackslashLiteral |
| | 末尾のバックスラッシュ\ | 末尾のバックスラッシュ\ |
| | doubleTrailingBackslash\\ | doubleTrailingBackslash\\ |
| | \ 引用されたバックスラッシュ リテラル | "\ 引用符付きのバックスラッシュ リテラル" |
| | \\ 引用された二重バックスラッシュ リテラル | "\\ 二重バックスラッシュ リテラルを引用" |
| | 末尾のバックスラッシュを引用\ | "引用された末尾のバックスラッシュ\\" |
| | 二重の末尾のバックスラッシュを引用\\ | "二重末尾のバックスラッシュを引用\\\\" |
| | \"\backslashQuoteEscaping | "\\\"\backslashQuoteEscaping " |
| | \\"\doubleBackslashQuoteEscaping | "\\\\\"\doubleBackslashQuoteEscaping " |
| | \\"\\doubleBackslashQuoteEscaping | "\\\\\"\\doubleBackslashQuoteEscaping " |
| | \"\\doubleBackslashQuoteEscaping | "\\\"\\doubleBackslashQuoteEscaping " |
| | \"\バックスラッシュ引用エスケープ | "\\\"\バックスラッシュ引用エスケープ " |
| | \\"\二重バックスラッシュ引用符のエスケープ | "\\\\\"\二重バックスラッシュ引用符のエスケープ " |
| | \\"\\二重バックスラッシュ引用符のエスケープ | "\\\\\"\\二重バックスラッシュ引用符のエスケープ " |
| | \"\\二重バックスラッシュ引用符のエスケープ | "\\\"\\二重バックスラッシュ引用符のエスケープ " |
| | TrailingQuoteEscaping" | TrailingQuoteEscaping\" |
| | TrailingQuoteEscaping\" | TrailingQuoteEscaping\\\" |
| | TrailingQuoteEscaping\"\ | TrailingQuoteEscaping\\\"\ |
| | TrailingQuoteEscaping"\ | TrailingQuoteEscaping\"\ |
| | 末尾の引用符のエスケープ" | "末尾の引用符のエスケープ\"" |
| | 末尾の引用符のエスケープ\" | "末尾の引用符のエスケープ\\\"" |
| | 末尾の引用符のエスケープ\"\ | "末尾の引用符のエスケープ\\\"\\" |
| | 末尾の引用符のエスケープ"\ | "末尾の引用符のエスケープ\"\\" |
+-----------------------------------------------------+--------- -----------------------------------+
SO には、この質問に対する他の回答があります。私は単純に、正規表現よりもコード化されたステート マシンを好みます (また、より高速に実行されます)。
https://stackoverflow.com/a/6040946/3591916には、その方法がよく説明されています。