2015年3月7日

ProcessStartInfo.Arguments 設定時のエスケープ方法

外部プログラムのコマンドライン引数を指定する ProcessStartInfo.Arguments は、単なる string 型なので、コマンドラインのエスケープを考慮する必要がある。
エスケープを考慮してくれるプロパティがあってもいい気がするが、エスケープ仕様はプログラム(コンパイラ)依存なので、実装してないのだろう・・・たぶん。

C# コンパイラのエスケープ仕様は下記。
たぶん、大抵の Windows のプログラムは、この仕様に沿ってるはず・・・
(VC++ と java は確認済)

エスケープに関する要点。
  • スペース/タブが引数の区切り

    ※コマンドプロンプトでタブを入力したい場合、「cmd /F:OFF」で起動

  • 引数がスペース/タブを含む場合、「"」デリミタで囲む
  • "」リテラルは、「\"」とする
  • "」直前(デリミタ/リテラル問わず)の「\」リテラルは、「\\」とする

    \」リテラルが複数個の場合は、その分エスケープ(「\」の数が2倍になる)

  • "」直前でない「\」は、そのままリテラルとして扱われる(エスケープ不要)

[エスケープ+デリミタ付加処理の実装例]

/// 
/// コマンドライン引数 1 個をエンコード
/// 
public static string EncodeCommandLineValue(this string value)
{
    if( string.IsNullOrEmpty(value) ) return "";
    var containsSpace = value.IndexOfAny(new[]{' ', '\t'}) != -1;

    // 「\…\"」をエスケープ
    // やってることは、「"」直前の「\」の数を 2倍+1
    value = _commandLineEscapePattern.Replace(value, @"$1\$&");

    // スペース/タブが含まれる場合はデリミタで囲み、末尾が「\」だった場合、エスケープ
    if( containsSpace ){
        value = "\"" + _lastBackSlashPattern.Replace(value, "$1$1") + "\"";
    }
    return value;
}
private static readonly Regex _commandLineEscapePattern = new Regex("(\\\\*)\"");
private static readonly Regex _lastBackSlashPattern = new Regex(@"(\\+)$");

/// 
/// コマンドライン引数複数個をエンコードして、スペースで結合
/// 
public static string EncodeCommandLineValues(this IEnumerable<string> values)
{
    if( values == null ) throw new ArgumentNullException("values");
    return string.Join(" ", values.Select(v => EncodeCommandLineValue(v)));
}

[上記 EncodeCommandLineValue の実行例]
value 戻り値
通常 abc abc
デリミタ対象 a bc "a bc"
エスケープ対象 a\b"c\"d a\b\"c\\\"d
両方 a "b" c "a \"b\" c"
両方(末尾「\」) abc \ "abc \\"

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.5.2

参考URL

0 件のコメント:

コメントを投稿