Pergunta

Eu tenho uma única cadeia que contém os parâmetros de linha de comando a serem passados ??para outro executável e eu preciso extrair a string [] que contém os parâmetros individuais da mesma forma que o C # seria se os comandos tinham sido especificada no comando -linha. A string [] será usado quando da execução mais montagens ponto de entrada através de reflexão.

Existe uma função padrão para isso? Ou existe um método preferido (regex?) Para dividir os parâmetros correctamente? Ele deve lidar com '"' strings delimitadas que podem conter espaços corretamente, então eu não posso apenas dividir em ''.

Exemplo string:

string parameterString = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo";

Exemplo resultado:

string[] parameterArray = new string[] { 
  @"/src:C:\tmp\Some Folder\Sub Folder",
  @"/users:abcdefg@hijkl.com",
  @"tasks:SomeTask,Some Other Task",
  @"-someParam",
  @"foo"
};

Eu não preciso de uma biblioteca de análise de linha de comando, apenas uma maneira de obter a String [] que deve ser gerado.

Atualizar : Eu tive que mudar o resultado esperado para corresponder ao que é gerado pelo C # (removeu o extra "é nas cordas dividir)

Foi útil?

Solução

Além do e puro conseguiu solução por Earwicker , pode valer a pena mencionar, por acréscimo, que o Windows também fornece o CommandLineToArgvW função para quebrar-se uma cadeia de caracteres em uma matriz de cadeias:

LPWSTR *CommandLineToArgvW(
    LPCWSTR lpCmdLine, int *pNumArgs);

Analisa uma seqüência de linha de comando Unicode e retorna uma matriz de ponteiros de os argumentos de linha de comando, juntamente com Uma contagem de tais argumentos, de forma que é semelhante ao padrão C argv e argc valores em tempo de execução.

Um exemplo de chamar essa API de C # e desembalar a matriz de cadeia, resultando em código gerenciado pode ser encontrada em “Convertendo Command Line string para Args [] usando CommandLineToArgvW () API.” (http://intellitect.com/converting-command-line-string-to-args-utilizar-commandlinetoargvw-api/) Abaixo está uma versão um pouco mais simples do mesmo código:

[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
    [MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);

public static string[] CommandLineToArgs(string commandLine)
{
    int argc;
    var argv = CommandLineToArgvW(commandLine, out argc);        
    if (argv == IntPtr.Zero)
        throw new System.ComponentModel.Win32Exception();
    try
    {
        var args = new string[argc];
        for (var i = 0; i < args.Length; i++)
        {
            var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
            args[i] = Marshal.PtrToStringUni(p);
        }

        return args;
    }
    finally
    {
        Marshal.FreeHGlobal(argv);
    }
}

Outras dicas

Irrita-me que não há nenhuma função para dividir uma string com base em uma função que examina cada personagem. Se houvesse, você poderia escrevê-lo como este:

    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, a minha própria versão de Split que leva uma função que tem de 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 cadeias vazias, dependendo da situação, mas talvez essa informação será útil em outros casos, para que eu não remover as entradas vazias nesta função.

Em segundo lugar (e mais mundanely) um ajudante pequeno que irá cortar um par correspondente de citações desde o início e final de uma string. É mais exigente do que o método padrão da guarnição - ele só irá cortar um caractere de cada extremidade, e não vai cortar a partir de apenas um fim:

    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 eu suponho que você vai querer 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 de matriz esperados:

    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 eu 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");

Aqui está o teste para suas necessidades:

        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");

Note que a implementação tem a característica adicional que ele irá remover aspas em torno de um argumento se isso faz sentido (graças à função TrimMatchingQuotes). Eu acredito que é parte da interpretação normal de linha de comando.

O analisador de linha de comando do Windows se comporta exatamente como você diz, dividida em espaço a menos que haja uma citação unclosed antes. Eu recomendaria escrevendo o analisador si mesmo. Algo como isso talvez:

    static string[] ParseArguments(string commandLine)
    {
        char[] parmChars = commandLine.ToCharArray();
        bool inQuote = false;
        for (int index = 0; index < parmChars.Length; index++)
        {
            if (parmChars[index] == '"')
                inQuote = !inQuote;
            if (!inQuote && parmChars[index] == ' ')
                parmChars[index] = '\n';
        }
        return (new string(parmChars)).Split('\n');
    }

o resposta de Jeffrey L Whitledge e melhorado um pouco.

Ele agora suporta ambas as aspas simples e duplas. Você pode usar aspas no próprio parâmetros usando outras citações digitados.

Ele também retira as citações dos argumentos uma vez que estes não contribuem para a informação argumento.

    public static string[] SplitArguments(string commandLine)
    {
        var parmChars = commandLine.ToCharArray();
        var inSingleQuote = false;
        var inDoubleQuote = false;
        for (var index = 0; index < parmChars.Length; index++)
        {
            if (parmChars[index] == '"' && !inSingleQuote)
            {
                inDoubleQuote = !inDoubleQuote;
                parmChars[index] = '\n';
            }
            if (parmChars[index] == '\'' && !inDoubleQuote)
            {
                inSingleQuote = !inSingleQuote;
                parmChars[index] = '\n';
            }
            if (!inSingleQuote && !inDoubleQuote && parmChars[index] == ' ')
                parmChars[index] = '\n';
        }
        return (new string(parmChars)).Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
    }

A boa e puro gerenciado solução por Earwicker não conseguiu argumentos punho como este:

Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");

Ele retornou 3 elementos:

"He whispered to her \"I
love
you\"."

Então aqui é uma solução para apoiar o "citou \" escape \ "quote":

public static IEnumerable<string> SplitCommandLine(string commandLine)
{
    bool inQuotes = false;
    bool isEscaping = false;

    return commandLine.Split(c => {
        if (c == '\\' && !isEscaping) { isEscaping = true; return false; }

        if (c == '\"' && !isEscaping)
            inQuotes = !inQuotes;

        isEscaping = false;

        return !inQuotes && Char.IsWhiteSpace(c)/*c == ' '*/;
        })
        .Select(arg => arg.Trim().TrimMatchingQuotes('\"').Replace("\\\"", "\""))
        .Where(arg => !string.IsNullOrEmpty(arg));
}

Testado com 2 casos adicionais:

Test("\"C:\\Program Files\"", "C:\\Program Files");
Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");

Também observou que o aceito resposta por Atif Aziz que usos CommandLineToArgvW também falhou. Ele retornou 4 elementos:

He whispered to her \ 
I 
love 
you". 

Espero que isso ajude alguém à procura de uma solução deste tipo no futuro.

Eu como iteradores, e hoje em dia LINQ faz IEnumerable<String> tão facilmente utilizável como matrizes de corda, por isso a minha opinião seguindo o espírito de Jeffrey L Whitledge é (como um método de extensão para string):

public static IEnumerable<string> ParseArguments(this string commandLine)
{
    if (string.IsNullOrWhiteSpace(commandLine))
        yield break;

    var sb = new StringBuilder();
    bool inQuote = false;
    foreach (char c in commandLine) {
        if (c == '"' && !inQuote) {
            inQuote = true;
            continue;
        }

        if (c != '"' && !(char.IsWhiteSpace(c) && !inQuote)) {
            sb.Append(c);
            continue;
        }

        if (sb.Length > 0) {
            var result = sb.ToString();
            sb.Clear();
            inQuote = false;
            yield return result;
        }
    }

    if (sb.Length > 0)
        yield return sb.ToString();
}

Este O Projeto de Código artigo é o que eu usei no passado. É um bom bocado de código, mas ele poderia funcionar.

Este MSDN artigo é a única coisa que eu poderia achar que explica como comandar C # parses argumentos da linha.

Na sua pergunta você pediu um regex, e eu sou um grande fã e usuário deles, então quando eu precisava fazer esta mesma divisão argumento como você, eu escrevi o meu próprio regex após pesquisando em torno e não encontrar uma solução simples . I como soluções de curto, por isso fiz um e aqui está:

            var re = @"\G(""((""""|[^""])+)""|(\S+)) *";
            var ms = Regex.Matches(CmdLine, re);
            var list = ms.Cast<Match>()
                         .Select(m => Regex.Replace(
                             m.Groups[2].Success
                                 ? m.Groups[2].Value
                                 : m.Groups[4].Value, @"""""", @"""")).ToArray();

Ele lida com espaços em branco e citações entre aspas e converte fechados "" para". Sinta-se livre para usar o código!

A solução puramente gerenciada pode ser útil. Há muitos comentários "problema" para a função de WINAPI e ele não está disponível em outras plataformas. Aqui está o meu código que tem um comportamento bem definido (que você pode mudar se quiser).

Deve fazer o mesmo que o .NET / Windows fazer quando fornecendo esse parâmetro string[] args, e eu em comparação com uma série de valores "interessantes".

Esta é uma implementação clássico estado-máquina que leva cada personagem único da cadeia de entrada e interpreta-o para o estado atual, a saída de produção e um novo estado. O estado é definido nas variáveis ??escape, inQuote, hadQuote e prevCh, ea saída é coletado em currentArg e args.

Algumas das especialidades que eu descobri através de experimentos em um verdadeiro comando prompt de (Windows 7):. \\ produz \, \" produz ", "" dentro de uma faixa citada produz "

O personagem ^ parece ser mágico, também: ele sempre desaparece quando não duplicando-a. Caso contrário, ele não tem efeito sobre a linha de comando real. Minha implementação não suporta este, como eu não ter encontrado um padrão nesse comportamento. Talvez alguém sabe mais sobre ele.

Algo que não se encaixa nesse padrão é o seguinte comando:

cmd /c "argdump.exe "a b c""

O comando cmd parece pegar as cotações externas e levar o resto textualmente. Deve haver algum molho de magia especial nesta matéria.

Eu fiz nenhuma referência sobre o meu método, mas considerá-lo razoavelmente rápido. Ele não usa Regex e não faz qualquer concatenação mas em vez disso usa um StringBuilder para recolher os personagens para um argumento e coloca-los em uma lista.

/// <summary>
/// Reads command line arguments from a single string.
/// </summary>
/// <param name="argsString">The string that contains the entire command line.</param>
/// <returns>An array of the parsed arguments.</returns>
public string[] ReadArgs(string argsString)
{
    // Collects the split argument strings
    List<string> args = new List<string>();
    // Builds the current argument
    var currentArg = new StringBuilder();
    // Indicates whether the last character was a backslash escape character
    bool escape = false;
    // Indicates whether we're in a quoted range
    bool inQuote = false;
    // Indicates whether there were quotes in the current arguments
    bool hadQuote = false;
    // Remembers the previous character
    char prevCh = '\0';
    // Iterate all characters from the input string
    for (int i = 0; i < argsString.Length; i++)
    {
        char ch = argsString[i];
        if (ch == '\\' && !escape)
        {
            // Beginning of a backslash-escape sequence
            escape = true;
        }
        else if (ch == '\\' && escape)
        {
            // Double backslash, keep one
            currentArg.Append(ch);
            escape = false;
        }
        else if (ch == '"' && !escape)
        {
            // Toggle quoted range
            inQuote = !inQuote;
            hadQuote = true;
            if (inQuote && prevCh == '"')
            {
                // Doubled quote within a quoted range is like escaping
                currentArg.Append(ch);
            }
        }
        else if (ch == '"' && escape)
        {
            // Backslash-escaped quote, keep it
            currentArg.Append(ch);
            escape = false;
        }
        else if (char.IsWhiteSpace(ch) && !inQuote)
        {
            if (escape)
            {
                // Add pending escape char
                currentArg.Append('\\');
                escape = false;
            }
            // Accept empty arguments only if they are quoted
            if (currentArg.Length > 0 || hadQuote)
            {
                args.Add(currentArg.ToString());
            }
            // Reset for next argument
            currentArg.Clear();
            hadQuote = false;
        }
        else
        {
            if (escape)
            {
                // Add pending escape char
                currentArg.Append('\\');
                escape = false;
            }
            // Copy character from input, no special meaning
            currentArg.Append(ch);
        }
        prevCh = ch;
    }
    // Save last argument
    if (currentArg.Length > 0 || hadQuote)
    {
        args.Add(currentArg.ToString());
    }
    return args.ToArray();
}

Use:

public static string[] SplitArguments(string args) {
    char[] parmChars = args.ToCharArray();
    bool inSingleQuote = false;
    bool inDoubleQuote = false;
    bool escaped = false;
    bool lastSplitted = false;
    bool justSplitted = false;
    bool lastQuoted = false;
    bool justQuoted = false;

    int i, j;

    for(i=0, j=0; i<parmChars.Length; i++, j++) {
        parmChars[j] = parmChars[i];

        if(!escaped) {
            if(parmChars[i] == '^') {
                escaped = true;
                j--;
            } else if(parmChars[i] == '"' && !inSingleQuote) {
                inDoubleQuote = !inDoubleQuote;
                parmChars[j] = '\n';
                justSplitted = true;
                justQuoted = true;
            } else if(parmChars[i] == '\'' && !inDoubleQuote) {
                inSingleQuote = !inSingleQuote;
                parmChars[j] = '\n';
                justSplitted = true;
                justQuoted = true;
            } else if(!inSingleQuote && !inDoubleQuote && parmChars[i] == ' ') {
                parmChars[j] = '\n';
                justSplitted = true;
            }

            if(justSplitted && lastSplitted && (!lastQuoted || !justQuoted))
                j--;

            lastSplitted = justSplitted;
            justSplitted = false;

            lastQuoted = justQuoted;
            justQuoted = false;
        } else {
            escaped = false;
        }
    }

    if(lastQuoted)
        j--;

    return (new string(parmChars, 0, j)).Split(new[] { '\n' });
}

Com base vapor em resposta s the Alley ', este também suporta ^ escapes.

Exemplos:

  • este é um teste
    • este
    • é
    • a
    • test
  • este "é um" test
    • este
    • é um
    • test
  • este ^ "é a ^" test
    • este
    • um "
    • test
  • "" este "é um ^^ teste"
    • este
    • é a ^ test

Ele também suporta múltiplos espaços (quebras de argumentos apenas uma vez por bloco de espaços).

Oh diabo. É tudo ... Eugh. Mas isso é oficial legítimo. de Microsoft em C # .NET para core, talvez somente para Windows, talvez multi-plataforma, mas MIT licenciado.

Selecionar boatos, declarações de método e comentários notáveis;

internal static unsafe string[] InternalCreateCommandLine(bool includeArg0)
private static unsafe int SegmentCommandLine(char * pCmdLine, string[] argArray, bool includeArg0)
private static unsafe int ScanArgument0(ref char* psrc, char[] arg)
private static unsafe int ScanArgument(ref char* psrc, ref bool inquote, char[] arg)

-

// First, parse the program name (argv[0]). Argv[0] is parsed under special rules. Anything up to 
// the first whitespace outside a quoted subtring is accepted. Backslashes are treated as normal 
// characters.

-

// Rules: 2N backslashes + " ==> N backslashes and begin/end quote
//      2N+1 backslashes + " ==> N backslashes + literal "
//         N backslashes     ==> N backslashes

Este é o código portado para .NET Core a partir de .NET Framework a partir do que eu assumo é tanto a biblioteca MSVC C ou CommandLineToArgvW.

Aqui está minha tentativa half-hearted em lidar com algumas das travessuras com expressões regulares, e ignorando o argumento de zero bit. É um pouco wizardy pouco.

private static readonly Regex RxWinArgs
  = new Regex("([^\\s\"]+\"|((?<=\\s|^)(?!\"\"(?!\"))\")+)(\"\"|.*?)*\"[^\\s\"]*|[^\\s]+",
    RegexOptions.Compiled
    | RegexOptions.Singleline
    | RegexOptions.ExplicitCapture
    | RegexOptions.CultureInvariant);

internal static IEnumerable<string> ParseArgumentsWindows(string args) {
  var match = RxWinArgs.Match(args);

  while (match.Success) {
    yield return match.Value;
    match = match.NextMatch();
  }
}

Testado um pouco justo na saída gerada maluco. É de saída coincide com uma porcentagem justa do que os macacos digitado e correu através CommandLineToArgvW.

Atualmente, este é o código que eu tenho:

    private String[] SplitCommandLineArgument(String argumentString)
    {
        StringBuilder translatedArguments = new StringBuilder(argumentString);
        bool escaped = false;
        for (int i = 0; i < translatedArguments.Length; i++)
        {
            if (translatedArguments[i] == '"')
            {
                escaped = !escaped;
            }
            if (translatedArguments[i] == ' ' && !escaped)
            {
                translatedArguments[i] = '\n';
            }
        }

        string[] toReturn = translatedArguments.ToString().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
        for(int i = 0; i < toReturn.Length; i++)
        {
            toReturn[i] = RemoveMatchingQuotes(toReturn[i]);
        }
        return toReturn;
    }

    public static string RemoveMatchingQuotes(string stringToTrim)
    {
        int firstQuoteIndex = stringToTrim.IndexOf('"');
        int lastQuoteIndex = stringToTrim.LastIndexOf('"');
        while (firstQuoteIndex != lastQuoteIndex)
        {
            stringToTrim = stringToTrim.Remove(firstQuoteIndex, 1);
            stringToTrim = stringToTrim.Remove(lastQuoteIndex - 1, 1); //-1 because we've shifted the indicies left by one
            firstQuoteIndex = stringToTrim.IndexOf('"');
            lastQuoteIndex = stringToTrim.LastIndexOf('"');
        }
        return stringToTrim;
    }

Não funciona com citações escaparam, mas funciona para os casos que eu vim acima contra até agora.

Esta é uma resposta ao código de Anton, que não funcionam com citações escaparam. Eu modifiquei 3 lugares.

  1. O construtor StringBuilder em SplitCommandLineArguments , substituindo qualquer \ " com \ r
  2. No for-loop em SplitCommandLineArguments , agora eu substituir o \ r personagem de volta a \ " .
  3. Mudou o SplitCommandLineArgument método de privada para public static .

public static string[] SplitCommandLineArgument( String argumentString )
{
    StringBuilder translatedArguments = new StringBuilder( argumentString ).Replace( "\\\"", "\r" );
    bool InsideQuote = false;
    for ( int i = 0; i < translatedArguments.Length; i++ )
    {
        if ( translatedArguments[i] == '"' )
        {
            InsideQuote = !InsideQuote;
        }
        if ( translatedArguments[i] == ' ' && !InsideQuote )
        {
            translatedArguments[i] = '\n';
        }
    }

    string[] toReturn = translatedArguments.ToString().Split( new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries );
    for ( int i = 0; i < toReturn.Length; i++ )
    {
        toReturn[i] = RemoveMatchingQuotes( toReturn[i] );
        toReturn[i] = toReturn[i].Replace( "\r", "\"" );
    }
    return toReturn;
}

public static string RemoveMatchingQuotes( string stringToTrim )
{
    int firstQuoteIndex = stringToTrim.IndexOf( '"' );
    int lastQuoteIndex = stringToTrim.LastIndexOf( '"' );
    while ( firstQuoteIndex != lastQuoteIndex )
    {
        stringToTrim = stringToTrim.Remove( firstQuoteIndex, 1 );
        stringToTrim = stringToTrim.Remove( lastQuoteIndex - 1, 1 ); //-1 because we've shifted the indicies left by one
        firstQuoteIndex = stringToTrim.IndexOf( '"' );
        lastQuoteIndex = stringToTrim.LastIndexOf( '"' );
    }
    return stringToTrim;
}

Eu não acho que há aspas simples ou ^ cotações para aplicações C #. A função a seguir está funcionando bem para mim:

public static IEnumerable<String> SplitArguments(string commandLine)
{
    Char quoteChar = '"';
    Char escapeChar = '\\';
    Boolean insideQuote = false;
    Boolean insideEscape = false;

    StringBuilder currentArg = new StringBuilder();

    // needed to keep "" as argument but drop whitespaces between arguments
    Int32 currentArgCharCount = 0;                  

    for (Int32 i = 0; i < commandLine.Length; i++)
    {
        Char c = commandLine[i];
        if (c == quoteChar)
        {
            currentArgCharCount++;

            if (insideEscape)
            {
                currentArg.Append(c);       // found \" -> add " to arg
                insideEscape = false;
            }
            else if (insideQuote)
            {
                insideQuote = false;        // quote ended
            }
            else
            {
                insideQuote = true;         // quote started
            }
        }
        else if (c == escapeChar)
        {
            currentArgCharCount++;

            if (insideEscape)   // found \\ -> add \\ (only \" will be ")
                currentArg.Append(escapeChar + escapeChar);       

            insideEscape = !insideEscape;
        }
        else if (Char.IsWhiteSpace(c))
        {
            if (insideQuote)
            {
                currentArgCharCount++;
                currentArg.Append(c);       // append whitespace inside quote
            }
            else
            {
                if (currentArgCharCount > 0)
                    yield return currentArg.ToString();

                currentArgCharCount = 0;
                currentArg.Clear();
            }
        }
        else
        {
            currentArgCharCount++;
            if (insideEscape)
            {
                // found non-escaping backslash -> add \ (only \" will be ")
                currentArg.Append(escapeChar);                       
                currentArgCharCount = 0;
                insideEscape = false;
            }
            currentArg.Append(c);
        }
    }

    if (currentArgCharCount > 0)
        yield return currentArg.ToString();
}

Você pode ter um olhar para o código que eu tenho ontem publicação:

[C #] Caminho e argumentos cordas

Ele divide um nome de arquivo + argumentos para string []. caminhos curtos, variáveis ??de ambiente e extensões de arquivos que faltam são tratadas.

(Inicialmente era para UninstallString no Registro).

Tente este código:

    string[] str_para_linha_comando(string str, out int argumentos)
    {
        string[] linhaComando = new string[32];
        bool entre_aspas = false;
        int posicao_ponteiro = 0;
        int argc = 0;
        int inicio = 0;
        int fim = 0;
        string sub;

        for(int i = 0; i < str.Length;)
        {
            if (entre_aspas)
            {
                // Está entre aspas
                sub = str.Substring(inicio+1, fim - (inicio+1));
                linhaComando[argc - 1] = sub;

                posicao_ponteiro += ((fim - posicao_ponteiro)+1);
                entre_aspas = false;
                i = posicao_ponteiro;
            }
            else
            {
            tratar_aspas:
                if (str.ElementAt(i) == '\"')
                {
                    inicio = i;
                    fim = str.IndexOf('\"', inicio + 1);
                    entre_aspas = true;
                    argc++;
                }
                else
                {
                    // Se não for aspas, então ler até achar o primeiro espaço em branco
                    if (str.ElementAt(i) == ' ')
                    {
                        if (str.ElementAt(i + 1) == '\"')
                        {
                            i++;
                            goto tratar_aspas;
                        }

                        // Pular os espaços em branco adiconais
                        while(str.ElementAt(i) == ' ') i++;

                        argc++;
                        inicio = i;
                        fim = str.IndexOf(' ', inicio);
                        if (fim == -1) fim = str.Length;
                        sub = str.Substring(inicio, fim - inicio);
                        linhaComando[argc - 1] = sub;
                        posicao_ponteiro += (fim - posicao_ponteiro);

                        i = posicao_ponteiro;
                        if (posicao_ponteiro == str.Length) break;
                    }
                    else
                    {
                        argc++;
                        inicio = i;
                        fim = str.IndexOf(' ', inicio);
                        if (fim == -1) fim = str.Length;

                        sub = str.Substring(inicio, fim - inicio);
                        linhaComando[argc - 1] = sub;
                        posicao_ponteiro += fim - posicao_ponteiro;
                        i = posicao_ponteiro;
                        if (posicao_ponteiro == str.Length) break;
                    }
                }
            }
        }

        argumentos = argc;

        return linhaComando;
    }

Está escrito em Português.

Aqui está um um forro que começa o trabalho feito (ver a linha que faz todo o trabalho dentro das BurstCmdLineArgs (...) método).

Não é o que eu chamaria a linha mais legível do código, mas você pode quebrá-lo por causa de legibilidade. É simples de propósito e não funciona bem para todos os casos de argumentos (como argumentos de nome de arquivo que contêm o delimitador de separação cadeia de caracteres neles).

Esta solução tem funcionado bem em minhas soluções que o utilizam. Como eu disse, ele começa o trabalho feito sem ninho de código para lidar com todos os possíveis formato argumento n-fatorial de um rato.

using System;
using System.Collections.Generic;
using System.Linq;

namespace CmdArgProcessor
{
    class Program
    {
        static void Main(string[] args)
        {
            // test switches and switches with values
            // -test1 1 -test2 2 -test3 -test4 -test5 5

            string dummyString = string.Empty;

            var argDict = BurstCmdLineArgs(args);

            Console.WriteLine("Value for switch = -test1: {0}", argDict["test1"]);
            Console.WriteLine("Value for switch = -test2: {0}", argDict["test2"]);
            Console.WriteLine("Switch -test3 is present? {0}", argDict.TryGetValue("test3", out dummyString));
            Console.WriteLine("Switch -test4 is present? {0}", argDict.TryGetValue("test4", out dummyString));
            Console.WriteLine("Value for switch = -test5: {0}", argDict["test5"]);

            // Console output:
            //
            // Value for switch = -test1: 1
            // Value for switch = -test2: 2
            // Switch -test3 is present? True
            // Switch -test4 is present? True
            // Value for switch = -test5: 5
        }

        public static Dictionary<string, string> BurstCmdLineArgs(string[] args)
        {
            var argDict = new Dictionary<string, string>();

            // Flatten the args in to a single string separated by a space.
            // Then split the args on the dash delimiter of a cmd line "switch".
            // E.g. -mySwitch myValue
            //  or -JustMySwitch (no value)
            //  where: all values must follow a switch.
            // Then loop through each string returned by the split operation.
            // If the string can be split again by a space character,
            // then the second string is a value to be paired with a switch,
            // otherwise, only the switch is added as a key with an empty string as the value.
            // Use dictionary indexer to retrieve values for cmd line switches.
            // Use Dictionary::ContainsKey(...) where only a switch is recorded as the key.
            string.Join(" ", args).Split('-').ToList().ForEach(s => argDict.Add(s.Split()[0], (s.Split().Count() > 1 ? s.Split()[1] : "")));

            return argDict;
        }
    }
}

Não foi possível encontrar qualquer coisa que eu gostei aqui. Eu odeio estragar a pilha com a magia de rendimento para uma pequena linha de comando (se fosse um fluxo de um terabyte, seria outra história).

Aqui é a minha opinião, ele suporta Citação escapes com aspas duplas como estes:

param = "a 15" tela "não é ruim" param2 = 'a 15" isn''t ruim tela' param3 = "" param4 = / param5

resultado:

param = "a 15" tela não é ruim "

param2 = 'uma tela de 15" não é ruim'

param3 = ""

param4 =

/ param5

public static string[] SplitArguments(string commandLine)
{
    List<string> args         = new List<string>();
    List<char>   currentArg   = new List<char>();
    char?        quoteSection = null; // Keeps track of a quoted section (and the type of quote that was used to open it)
    char[]       quoteChars   = new[] {'\'', '\"'};
    char         previous     = ' '; // Used for escaping double quotes

    for (var index = 0; index < commandLine.Length; index++)
    {
        char c = commandLine[index];
        if (quoteChars.Contains(c))
        {
            if (previous == c) // Escape sequence detected
            {
                previous = ' '; // Prevent re-escaping
                if (!quoteSection.HasValue)
                {
                    quoteSection = c; // oops, we ended the quoted section prematurely
                    continue;         // don't add the 2nd quote (un-escape)
                }

                if (quoteSection.Value == c)
                    quoteSection = null; // appears to be an empty string (not an escape sequence)
            }
            else if (quoteSection.HasValue)
            {
                if (quoteSection == c)
                    quoteSection = null; // End quoted section
            }
            else
                quoteSection = c; // Start quoted section
        }
        else if (char.IsWhiteSpace(c))
        {
            if (!quoteSection.HasValue)
            {
                args.Add(new string(currentArg.ToArray()));
                currentArg.Clear();
                previous = c;
                continue;
            }
        }

        currentArg.Add(c);
        previous = c;
    }

    if (currentArg.Count > 0)
        args.Add(new string(currentArg.ToArray()));

    return args.ToArray();
}

Eu não tenho certeza se eu entendi você, mas é o problema que o caractere usado como divisor, é também pode ser encontrada dentro do texto? (Exceto por que ele escapou com duplo "?)

Se assim for, eu criaria um ciclo for, e substituir todas as instâncias onde < "> está presente com <|> (ou outro 'seguro' de caráter, mas certifique-se que ele só substitui <">, e não <" ">

Depois de reiterar a corda, eu faria como anteriormente publicado, dividir a string, mas agora sobre o caráter <|>.

Sim, o objeto string tem um construído em função chamada Split() que leva um único parâmetro especificando o personagem para olhar para como um delimitador, e retorna uma matriz de strings (String []) com os valores individuais na mesma.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top