各文字を調べる関数に基づいて文字列を分割する関数がないことは、私を悩ませます。ある場合は、次のように記述できます。
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);
}
状況によっては空の文字列が生成される場合もありますが、その情報は別の場合に役立つ可能性があるため、この関数では空のエントリを削除しません。
2 つ目は (そしてもっとありふれたことですが)、文字列の先頭と末尾から一致する引用符のペアを削除する小さなヘルパーです。これは、標準の 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 関数のおかげです)。それは通常のコマンドライン解釈の一部だと思います。