令我烦恼的是,没有基于检查每个字符的函数来分割字符串的函数。如果有的话,可以这样写:
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方法更挑剔-它只会从每一端修剪一个字符,而不会从一个末端修剪一个字符:
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函数)。我相信这是正常命令行解释的一部分。