Me irrita que não haja função para dividir uma string com base em uma função que examina cada caractere. Se houvesse, você poderia escrever assim:
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));
}
Apesar de ter escrito isso, por que não escrever os métodos de extensão necessários. Ok, você me convenceu ...
Em primeiro lugar, minha própria versão de Split que assume uma função que tem que decidir se o caractere especificado deve dividir a string:
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);
}
Pode render algumas strings vazias dependendo da situação, mas talvez essa informação seja útil em outros casos, então eu não removo as entradas vazias nesta função.
Em segundo lugar (e de forma mais prática), um pequeno auxiliar que irá cortar um par de aspas correspondentes do início e do fim de uma string. É mais complicado do que o método Trim padrão - ele cortará apenas um caractere de cada extremidade e não apenas de uma extremidade:
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;
}
E suponho que você queira alguns testes também. Bem, tudo bem então. Mas isso deve ser absolutamente a última coisa! Primeiro, uma função auxiliar que compara o resultado da divisão com o conteúdo esperado da matriz:
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]);
}
Então posso escrever testes como este:
Test("");
Test("a", "a");
Test(" abc ", "abc");
Test("a b ", "a", "b");
Test("a b \"c d\"", "a", "b", "c d");
Este é o teste para seus requisitos:
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");
Observe que a implementação tem o recurso extra de remover as aspas em torno de um argumento, se isso fizer sentido (graças à função TrimMatchingQuotes). Eu acredito que isso faz parte da interpretação normal da linha de comando.