各文字を調べる関数に基づいて文字列を分割する関数がないことは私を困らせます。もしあれば、次のように書くことができます:
public static IEnumerable<string> SplitCommandLine(string commandLine)
{
bool inQuotes = false;
return commandLine.Split(c =>
{
if (c == '\"')
inQuotes = !inQuotes;
return !inQuotes && c == ' ';
})
.Select(arg => arg.Trim().TrimMatchingQuotes('\"'))
.Where(arg => !string.IsNullOrEmpty(arg));
}
それを書いても、必要な拡張メソッドを書いてみませんか。わかりました、あなたは私に話しかけました...
最初に、指定した文字で文字列を分割するかどうかを決定する必要がある関数を使用する独自のバージョンのSplit:
public static IEnumerable<string> Split(this string str,
Func<char, bool> controller)
{
int nextPiece = 0;
for (int c = 0; c < str.Length; c++)
{
if (controller(str[c]))
{
yield return str.Substring(nextPiece, c - nextPiece);
nextPiece = c + 1;
}
}
yield return str.Substring(nextPiece);
}
状況によっては空の文字列が生成される場合がありますが、その情報は他の場合にも役立つため、この関数の空のエントリは削除しません。
第二に(そしてより平凡に)文字列の最初と最後から一致する引用符のペアを削除する小さなヘルパー。標準のTrimメソッドよりも手間がかかります。両端から1文字だけをトリミングし、片端だけをトリミングすることはありません。
public static string TrimMatchingQuotes(this string input, char quote)
{
if ((input.Length >= 2) &&
(input[0] == quote) && (input[input.Length - 1] == quote))
return input.Substring(1, input.Length - 2);
return input;
}
また、いくつかのテストも必要になると思います。さて、それでは大丈夫です。しかし、これは絶対に最後のものでなければなりません!最初に、分割の結果を予想される配列の内容と比較するヘルパー関数:
public static void Test(string cmdLine, params string[] args)
{
string[] split = SplitCommandLine(cmdLine).ToArray();
Debug.Assert(split.Length == args.Length);
for (int n = 0; n < split.Length; n++)
Debug.Assert(split[n] == args[n]);
}
それから私はこのようなテストを書くことができます:
Test("");
Test("a", "a");
Test(" abc ", "abc");
Test("a b ", "a", "b");
Test("a b \"c d\"", "a", "b", "c d");
要件のテストは次のとおりです。
Test(@"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam",
@"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""abcdefg@hijkl.com""", @"tasks:""SomeTask,Some Other Task""", @"-someParam");
実装には、それが理にかなっている場合に引数の前後の引用符を削除するという追加の機能があることに注意してください(TrimMatchingQuotes関数のおかげです)。これは通常のコマンドライン解釈の一部だと思います。