Dividir la cadena que contiene los parámetros de la línea de comandos en la cadena [] en C #


91

Tengo una sola cadena que contiene los parámetros de la línea de comandos que se pasarán a otro ejecutable y necesito extraer la cadena [] que contiene los parámetros individuales de la misma manera que lo haría C # si los comandos se hubieran especificado en la línea de comandos. La cadena [] se utilizará al ejecutar otro punto de entrada de ensamblajes mediante reflexión.

¿Existe una función estándar para esto? ¿O hay un método preferido (¿regex?) Para dividir los parámetros correctamente? Debe manejar cadenas delimitadas '"' que pueden contener espacios correctamente, por lo que no puedo simplemente dividir en ''.

Cadena de ejemplo:

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

Resultado de ejemplo:

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

No necesito una biblioteca de análisis de línea de comandos, solo una forma de obtener el String [] que se debe generar.

Actualización : tuve que cambiar el resultado esperado para que coincida con lo que realmente genera C # (eliminé los "extra" en las cadenas divididas)



5
Cada vez que alguien responde, parece que tiene una objeción basada en material que no está en su publicación. Le sugiero que actualice su publicación con este material. Puede obtener mejores respuestas.
tvanfosson

1
Buena pregunta, buscando lo mismo. Esperaba encontrar a alguien que dijera "hey .net expone eso aquí ..." :) Si me encuentro con eso en algún momento, lo publicaré aquí, aunque tenga como 6 años. ¡Sigue siendo una pregunta válida!
MikeJansen

Creé una versión puramente administrada en una respuesta a continuación, ya que también necesitaba esta función.
ygoe

Respuestas:


75

Además de la buena y pura solución administrada de Earwicker , vale la pena mencionar, en aras de la integridad, que Windows también proporciona la CommandLineToArgvWfunción para dividir una cadena en una matriz de cadenas:

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

Analiza una cadena de línea de comando Unicode y devuelve una matriz de punteros a los argumentos de la línea de comando, junto con un recuento de dichos argumentos, de una manera que es similar a los valores argv y argc estándar en tiempo de ejecución de C.

Puede encontrar un ejemplo de cómo llamar a esta API desde C # y descomprimir la matriz de cadenas resultante en código administrado en " Conversión de cadenas de línea de comandos en Args [] usando la API CommandLineToArgvW () ". A continuación se muestra una versión un poco más simple del mismo 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);
    }
}

1
Esta función requiere que escape la barra invertida final de una ruta entre comillas. "C: \ Archivos de programa \" debe ser "C: \ Archivos de programa \\" para que esto funcione para analizar la cadena correctamente.
Magnus Lindhe

8
También vale la pena señalar que CommandLineArgvW espera que el primer argumento sea el nombre del programa, y ​​la magia de análisis aplicada no es la misma si no se pasa una. Puede falsificarlo con algo como:CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
Scott Wegner

4
En aras de la integridad, MSVCRT no utiliza CommandLineToArgvW () para convertir la línea de comandos a argc / argv. Utiliza su propio código, que es diferente. Por ejemplo, intente llamar a CreateProcess con esta cadena: a "b c" def. En main () obtendría 3 argumentos (como se documenta en MSDN), pero el combo CommandLineToArgvW () / GetCommandLineW () le dará 2.
LRN

7
Dios mío, esto es un desastre. sopa típica de MS. nada se canoniza, y KISS nunca es respetado en el mundo de la EM.
v.oddou

1
Publiqué una versión multiplataforma de la implementación de MSVCRT traducida por Microsoft y una aproximación de alta precisión usando Regex. Sé que esto es viejo, pero bueno, no hay pergaminos corporales.
TylerY86

101

Me molesta que no haya una función para dividir una cadena en función de una función que examine cada carácter. Si lo hubiera, podría escribirlo así:

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

Aunque habiendo escrito eso, ¿por qué no escribir los métodos de extensión necesarios? Está bien, me convenciste ...

En primer lugar, mi propia versión de Split que toma una función que tiene que decidir si el carácter especificado debe dividir la cadena:

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

Puede producir algunas cadenas vacías dependiendo de la situación, pero tal vez esa información sea útil en otros casos, así que no elimino las entradas vacías en esta función.

En segundo lugar (y de forma más mundana), un pequeño ayudante que recortará un par de comillas coincidentes desde el principio y el final de una cadena. Es más exigente que el método de recorte estándar: solo recortará un carácter de cada extremo y no recortará solo un extremo:

    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;
    }

Y supongo que también querrás algunas pruebas. Bueno, esta bien entonces. ¡Pero esto debe ser absolutamente lo último! Primero, una función auxiliar que compara el resultado de la división con el contenido esperado de la 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]);
    }

Entonces puedo escribir pruebas como esta:

        Test("");
        Test("a", "a");
        Test(" abc ", "abc");
        Test("a b ", "a", "b");
        Test("a b \"c d\"", "a", "b", "c d");

Aquí está la prueba para sus 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");

Tenga en cuenta que la implementación tiene la característica adicional de que eliminará las comillas alrededor de un argumento si tiene sentido (gracias a la función TrimMatchingQuotes). Creo que es parte de la interpretación normal de la línea de comandos.


Tuve que desmarcar esto como la respuesta porque no tenía los resultados esperados correctos. La salida real no debe tener "en la matriz final"
Anton

16
¡Vengo a Stack Overflow para alejarme de los requisitos que cambian todo el tiempo! :) Puede usar Reemplazar ("\" "," ") en lugar de TrimMatchingQuotes () para deshacerse de todas las comillas. Pero Windows admite \" para permitir que se pase un carácter de comillas. Mi función Split no puede hacer eso.
Daniel Earwicker

1
Bonito Earwicker :) Anton: Esta es la solución que estaba tratando de describirte en mi publicación anterior, pero Earwicker hizo un trabajo mucho mejor al escribirla;) Y también la extendió mucho;)
Israr Khan

un espacio en blanco no es el único carácter de separación para los argumentos de la línea de comandos, ¿verdad?
Louis Rhys

@Louis Rhys - No estoy seguro. Si eso es una preocupación, es bastante fácil de resolver: use en char.IsWhiteSpacelugar de== ' '
Daniel Earwicker

25

El analizador de la línea de comandos de Windows se comporta como usted dice, dividido en el espacio a menos que haya una cita sin cerrar antes. Recomendaría escribir el analizador usted mismo. Algo como esto tal vez:

    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');
    }

2
Terminé con lo mismo, excepto que usé .Split (new char [] {'\ n'}, StringSplitOptions.RemoveEmptyEntries) en la línea final en caso de que hubiera extra '' s entre los parámetros. Parece estar funcionando.
Anton

3
Supongo que Windows debe tener una forma de escapar de las comillas en los parámetros ... este algoritmo no tiene eso en cuenta.
rmeador

La eliminación de líneas en blanco, la eliminación de las comillas externas y el manejo de las comillas de escape se dejan como un ejercicio para el lector.
Jeffrey L Whitledge

Char.IsWhiteSpace () podría ayudar aquí
Sam Mackrill

Esta solución es buena si los argumentos están separados por un solo espacio, pero falla si los argumentos están separados por varios espacios. Enlace a la solución correcta: stackoverflow.com/a/59131568/3926504
Dilip Nannaware

13

Tomé la respuesta de Jeffrey L Whitledge y la realcé un poco.

Ahora admite comillas simples y dobles. Puede utilizar comillas en los propios parámetros mediante el uso de otras comillas escritas.

También elimina las citas de los argumentos, ya que estos no contribuyen a la información del 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);
    }

7

La solución administrada buena y pura de Earwicker no pudo manejar argumentos como este:

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

Devolvió 3 elementos:

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

Así que aquí hay una solución para admitir la "cita \" escape \ "cita":

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

Probado con 2 casos adicionales:

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

También señaló que la respuesta aceptada por Atif Aziz que usa CommandLineToArgvW también falló. Devolvió 4 elementos:

He whispered to her \ 
I 
love 
you". 

Espero que esto ayude a alguien que busque una solución de este tipo en el futuro.


3
Perdón por la nigromancia, pero esta solución todavía pierde cosas como lo bla.exe aAAA"b\"ASDS\"c"dSADSDque da como resultado el resultado de aAAAb"ASDS"cdSADSDesta solución aAAA"b"ASDS"c"dSADSD. Podría considerar cambiar el TrimMatchingQuotesa ay Regex("(?<!\\\\)\\\"")usarlo así .
Scis

4

2
Útil, pero esto solo le dará los argumentos de la línea de comando enviados al proceso actual. El requisito era obtener una cadena [] de una cadena "de la misma manera que lo haría C # si los comandos se hubieran especificado en la línea de comandos". Sin embargo, creo que podríamos usar un descompilador para ver cómo MS implementó esto ...
rohancragg

Como Jon Galloway también encontró ( weblogs.asp.net/jgalloway/archive/2006/09/13/… ) un descompilador no ayuda mucho, lo que nos lleva de vuelta a la respuesta de Atif ( stackoverflow.com/questions/298830/… )
rohancragg

4

Me gustan los iteradores, y hoy en día LINQ hace IEnumerable<String>que sea tan fácil de usar como matrices de cadenas, por lo que mi opinión siguiendo el espíritu de la respuesta de Jeffrey L Whitledge es (como un método de extensión 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();
}

3

En su pregunta, solicitó una expresión regular, y soy un gran admirador y usuario de ellas, así que cuando necesitaba dividir este mismo argumento que usted, escribí mi propia expresión regular después de buscar en Google y no encontrar una solución simple. Me gustan las soluciones cortas, así que hice una y aquí 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();

Maneja espacios en blanco y comillas dentro de comillas, y convierte "" a "adjunto. ¡Siéntete libre de usar el código!


3

Oh diablos. Es todo ... Eugh. Pero esto es oficial legítimo. De Microsoft en C # para .NET Core, tal vez solo Windows, tal vez multiplataforma, pero con licencia MIT.

Seleccione tidbits, declaraciones de métodos y comentarios notables;

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 es un código portado a .NET Core desde .NET Framework de lo que supongo que es la biblioteca MSVC C o CommandLineToArgvW.

Aquí está mi intento poco entusiasta de manejar algunas de las travesuras con expresiones regulares e ignorar el argumento cero bit. Es un poco mágico.

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();
  }
}

Lo probé un poco en resultados generados extravagantes. Su salida coincide con un buen porcentaje de lo que los monos escribieron y corrieron CommandLineToArgvW.



1
Sí, parece que la versión de C # está muerta. github.com/dotnet/runtime/blob/master/src/coreclr/src/utilcode/…
TylerY86

1
Avivamiento por tiempo limitado. pastebin.com/ajhrBS4t
TylerY86

2

Este artículo de The Code Project es lo que he usado en el pasado. Es un buen fragmento de código, pero podría funcionar.

Este artículo de MSDN es lo único que pude encontrar que explica cómo C # analiza los argumentos de la línea de comandos.


Intenté reflejar en la biblioteca de C #, pero va a una llamada nativa de C ++ para la que no tengo el código y no veo ninguna forma de llamar sin invocarlo. Tampoco quiero una biblioteca de análisis de línea de comandos, solo quiero la cadena [].
Anton

Reflejar .NET tampoco me llevó a ninguna parte. Al examinar el código fuente de Mono , se sugirió que la división de este argumento no la realiza CLR, sino que ya proviene del sistema operativo. Piense en los parámetros argc, argv de la función principal de C. Por lo tanto, no hay nada que reutilizar más que la API del sistema operativo.
ygoe

1

Una solución puramente administrada puede resultar útil. Hay demasiados comentarios de "problemas" para la función WINAPI y no está disponible en otras plataformas. Aquí está mi código que tiene un comportamiento bien definido (que puede cambiar si lo desea).

Debería hacer lo mismo que .NET / Windows al proporcionar ese string[] argsparámetro, y lo he comparado con varios valores "interesantes".

Esta es una implementación clásica de máquina de estado que toma cada carácter de la cadena de entrada y lo interpreta para el estado actual, produciendo una salida y un nuevo estado. El estado se define en las variables escape, inQuote, hadQuotey prevCh, y la salida es recogido en currentArgy args.

Algunas de las especialidades que he descubierto mediante experimentos en un símbolo del sistema real (Windows 7): \\produce \, \"produce ", ""dentro de un rango citado produce ".

El ^personaje también parece ser mágico: siempre desaparece cuando no se dobla. De lo contrario, no tiene ningún efecto en una línea de comando real. Mi implementación no admite esto, ya que no he encontrado un patrón en este comportamiento. Quizás alguien sepa más sobre eso.

Algo que no encaja en este patrón es el siguiente comando:

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

El cmdcomando parece captar las comillas externas y tomar el resto literalmente. Debe haber una salsa mágica especial en esto.

No he realizado evaluaciones comparativas en mi método, pero considérelo razonablemente rápido. No usa Regexni hace ninguna concatenación de cadenas, sino que usa a StringBuilderpara recopilar los caracteres de un argumento y los coloca en una 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();
}

1

Utilizar:

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' });
}

Basado en la respuesta de Vapor in the Alley , este también admite ^ escapes.

Ejemplos:

  • esto es una prueba
    • esta
    • es
    • una
    • prueba
  • esto es una prueba
    • esta
    • es un
    • prueba
  • esta ^ "es una ^" prueba
    • esta
    • "es
    • una"
    • prueba
  • esta "" "es una ^^ prueba"
    • esta
    • es una ^ prueba

También admite múltiples espacios (rompe argumentos solo una vez por bloque de espacios).


El último de los tres interfiere de alguna manera con Markdown y no se representa según lo previsto.
Peter Mortensen

Se corrigió con un espacio de ancho cero.
Fabio Iotti

1

Debido a que quería el mismo comportamiento que OP (dividir una cadena exactamente igual que lo haría Windows cmd), escribí un montón de casos de prueba y probé las respuestas aquí publicadas:

    Test( 0, m, "One",                    new[] { "One" });
    Test( 1, m, "One ",                   new[] { "One" });
    Test( 2, m, " One",                   new[] { "One" });
    Test( 3, m, " One ",                  new[] { "One" });
    Test( 4, m, "One Two",                new[] { "One", "Two" });
    Test( 5, m, "One  Two",               new[] { "One", "Two" });
    Test( 6, m, "One   Two",              new[] { "One", "Two" });
    Test( 7, m, "\"One Two\"",            new[] { "One Two" });
    Test( 8, m, "One \"Two Three\"",      new[] { "One", "Two Three" });
    Test( 9, m, "One \"Two Three\" Four", new[] { "One", "Two Three", "Four" });
    Test(10, m, "One=\"Two Three\" Four", new[] { "One=Two Three", "Four" });
    Test(11, m, "One\"Two Three\" Four",  new[] { "OneTwo Three", "Four" });
    Test(12, m, "One\"Two Three   Four",  new[] { "OneTwo Three   Four" });
    Test(13, m, "\"One Two\"",            new[] { "One Two" });
    Test(14, m, "One\" \"Two",            new[] { "One Two" });
    Test(15, m, "\"One\"  \"Two\"",       new[] { "One", "Two" });
    Test(16, m, "One\\\"  Two",           new[] { "One\"", "Two" });
    Test(17, m, "\\\"One\\\"  Two",       new[] { "\"One\"", "Two" });
    Test(18, m, "One\"",                  new[] { "One" });
    Test(19, m, "\"One",                  new[] { "One" });
    Test(20, m, "One \"\"",               new[] { "One", "" });
    Test(21, m, "One \"",                 new[] { "One", "" });
    Test(22, m, "1 A=\"B C\"=D 2",        new[] { "1", "A=B C=D", "2" });
    Test(23, m, "1 A=\"B \\\" C\"=D 2",   new[] { "1", "A=B \" C=D", "2" });
    Test(24, m, "1 \\A 2",                new[] { "1", "\\A", "2" });
    Test(25, m, "1 \\\" 2",               new[] { "1", "\"", "2" });
    Test(26, m, "1 \\\\\" 2",             new[] { "1", "\\\"", "2" });
    Test(27, m, "\"",                     new[] { "" });
    Test(28, m, "\\\"",                   new[] { "\"" });
    Test(29, m, "'A B'",                  new[] { "'A", "B'" });
    Test(30, m, "^",                      new[] { "^" });
    Test(31, m, "^A",                     new[] { "A" });
    Test(32, m, "^^",                     new[] { "^" });
    Test(33, m, "\\^^",                   new[] { "\\^" });
    Test(34, m, "^\\\\", new[] { "\\\\" });
    Test(35, m, "^\"A B\"", new[] { "A B" });

    // Test cases Anton

    Test(36, m, @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo", new[] { @"/src:C:\tmp\Some Folder\Sub Folder", @"/users:abcdefg@hijkl.com", @"tasks:SomeTask,Some Other Task", @"-someParam", @"foo" });

    // Test cases Daniel Earwicker 

    Test(37, m, "", new string[] { });
    Test(38, m, "a", new[] { "a" });
    Test(39, m, " abc ", new[] { "abc" });
    Test(40, m, "a b ", new[] { "a", "b" });
    Test(41, m, "a b \"c d\"", new[] { "a", "b", "c d" });

    // Test cases Fabio Iotti 

    Test(42, m, "this is a test ", new[] { "this", "is", "a", "test" });
    Test(43, m, "this \"is a\" test", new[] { "this", "is a", "test" });

    // Test cases Kevin Thach

    Test(44, m, "\"C:\\Program Files\"", new[] { "C:\\Program Files" });
    Test(45, m, "\"He whispered to her \\\"I love you\\\".\"", new[] { "He whispered to her \"I love you\"." });

el valor "esperado" proviene de probarlo directamente con cmd.exe en mi máquina (Win10 x64) y un programa de impresión simple:

static void Main(string[] args) => Console.Out.WriteLine($"Count := {args.Length}\n{string.Join("\n", args.Select((v,i) => $"[{i}] => '{v}'"))}");

Estos son los resultados:


Solution                      | Failed Tests
------------------------------|------------------------------------- 
Atif Aziz (749653)            | 2, 3, 10, 11, 12, 14, 16, 17, 18, 26, 28, 31, 32, 33, 34, 35, 36, 37, 39, 45
Jeffrey L Whitledge (298968)  | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Daniel Earwicker (298990)     | 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 45
Anton (299795)                | 12, 16, 17, 18, 19, 21, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
CS. (467313)                  | 12, 18, 19, 21, 27, 31, 32, 33, 34, 35
Vapour in the Alley (2132004) | 10, 11, 12, 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 45
Monoman (7774211)             | 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
Thomas Petersson (19091999)   | 2, 3, 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 39, 45
Fabio Iotti (19725880)        | 1, 2, 3, 7, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 22, 23, 25, 26, 28, 29, 30, 35, 36, 37, 39, 40, 42, 44, 45
ygoe (23961658)               | 26, 31, 32, 33, 34, 35
Kevin Thach (24829691)        | 10, 11, 12, 14, 18, 19, 20, 21, 22, 23, 26, 27, 31, 32, 33, 34, 35, 36
Lucas De Jesus (31621370)     | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45
HarryP (48008872)             | 24, 26, 31, 32, 33, 34, 35
TylerY86 (53290784)           | 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 41, 43, 44, 45
Louis Somers (55903304)       | 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 39, 41, 43, 44, 45
user2126375 (58233585)        | 5, 6, 15, 16, 17, 31, 32, 33, 34, 35
DilipNannaware (59131568)     | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Mikescher (this)              | -

Debido a que ninguna respuesta parecía correcta (al menos según mi caso de uso) aquí está mi solución, actualmente pasa todos los casos de prueba (pero si alguien tiene casos de esquina adicionales (fallidos), comente):

public static IEnumerable<string> SplitArgs(string commandLine)
{
    var result = new StringBuilder();

    var quoted = false;
    var escaped = false;
    var started = false;
    var allowcaret = false;
    for (int i = 0; i < commandLine.Length; i++)
    {
        var chr = commandLine[i];

        if (chr == '^' && !quoted)
        {
            if (allowcaret)
            {
                result.Append(chr);
                started = true;
                escaped = false;
                allowcaret = false;
            }
            else if (i + 1 < commandLine.Length && commandLine[i + 1] == '^')
            {
                allowcaret = true;
            }
            else if (i + 1 == commandLine.Length)
            {
                result.Append(chr);
                started = true;
                escaped = false;
            }
        }
        else if (escaped)
        {
            result.Append(chr);
            started = true;
            escaped = false;
        }
        else if (chr == '"')
        {
            quoted = !quoted;
            started = true;
        }
        else if (chr == '\\' && i + 1 < commandLine.Length && commandLine[i + 1] == '"')
        {
            escaped = true;
        }
        else if (chr == ' ' && !quoted)
        {
            if (started) yield return result.ToString();
            result.Clear();
            started = false;
        }
        else
        {
            result.Append(chr);
            started = true;
        }
    }

    if (started) yield return result.ToString();
}

El código que utilicé para generar los resultados de la prueba se puede encontrar aquí


0

Actualmente, este es el código que tengo:

    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;
    }

No funciona con comillas de escape, pero funciona para los casos con los que me he encontrado hasta ahora.


0

Esta es una respuesta al código de Anton, que no funciona con comillas de escape. Modifiqué 3 lugares.

  1. El constructor de StringBuilder en SplitCommandLineArguments , reemplazando cualquier \ " con \ r
  2. En el bucle for en SplitCommandLineArguments , ahora reemplazo el carácter \ r por \ " .
  3. Se cambió el método SplitCommandLineArgument de privado a público estático .

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;
}

Estoy abordando este mismo problema, habrías pensado que en esta época existiría una solución simple para las cadenas de argumentos de la línea de comandos de prueba unitaria. De todo lo que quiero estar seguro es del comportamiento que resultará de una cadena de argumento de línea de comando dada. Me rindo por ahora y crearé pruebas unitarias para la cadena [] pero puedo agregar algunas pruebas de integración para cubrir esto.
Charlie Barker

0

No creo que haya comillas simples o ^ comillas para aplicaciones C #. La siguiente función funciona bien para mí:

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();
}

0

Puedes echar un vistazo al código que publiqué ayer:

[C #] Ruta y cadenas de argumentos

Divide un nombre de archivo + argumentos en una cadena []. Se manejan rutas cortas, variables de entorno y extensiones de archivo faltantes.

(Inicialmente fue para UninstallString en el Registro).


0

Prueba 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 en portugués.


más bien la documentación es portuguesa
Enamul Hassan

@EnamulHassan Yo diría que el código también está en portugués, por ejemplo posicao_ponteiro += ((fim - posicao_ponteiro)+1);.
MEMark

0

Aquí hay una línea que hace el trabajo (vea la línea que hace todo el trabajo dentro del método BurstCmdLineArgs (...)).

No es lo que yo llamaría la línea de código más legible, pero puede dividirla en aras de la legibilidad. Es simple a propósito y no funciona bien para todos los casos de argumentos (como los argumentos de nombre de archivo que contienen el delimitador de caracteres de cadena dividida).

Esta solución ha funcionado bien en mis soluciones que la utilizan. Como dije, hace el trabajo sin un nido de código para manejar cada posible formato de argumento n-factorial.

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;
        }
    }
}

0

No pude encontrar nada que me gustara aquí. Odio estropear la pila con magia de rendimiento para una pequeña línea de comandos (si fuera un flujo de un terabyte, sería otra historia).

Aquí está mi opinión, admite escapes de citas con comillas dobles como estas:

param = "una pantalla de 15" "no está mal" param2 = 'una pantalla de 15 "no está mal' param3 =" "param4 = / param5

resultado:

param = "una pantalla de 15" no está mal "

param2 = 'una pantalla de 15 "no está mal'

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();
}

0

He implementado la máquina de estado para tener los mismos resultados del analizador como si los argumentos se pasaran a la aplicación .NET y se procesaran en el static void Main(string[] args)método.

    public static IList<string> ParseCommandLineArgsString(string commandLineArgsString)
    {
        List<string> args = new List<string>();

        commandLineArgsString = commandLineArgsString.Trim();
        if (commandLineArgsString.Length == 0)
            return args;

        int index = 0;
        while (index != commandLineArgsString.Length)
        {
            args.Add(ReadOneArgFromCommandLineArgsString(commandLineArgsString, ref index));
        }

        return args;
    }

    private static string ReadOneArgFromCommandLineArgsString(string line, ref int index)
    {
        if (index >= line.Length)
            return string.Empty;

        var sb = new StringBuilder(512);
        int state = 0;
        while (true)
        {
            char c = line[index];
            index++;
            switch (state)
            {
                case 0: //string outside quotation marks
                    if (c == '\\') //possible escaping character for quotation mark otherwise normal character
                    {
                        state = 1;
                    }
                    else if (c == '"') //opening quotation mark for string between quotation marks
                    {
                        state = 2;
                    }
                    else if (c == ' ') //closing arg
                    {
                        return sb.ToString();
                    }
                    else
                    {
                        sb.Append(c);
                    }

                    break;
                case 1: //possible escaping \ for quotation mark or normal character
                    if (c == '"') //If escaping quotation mark only quotation mark is added into result
                    {
                        state = 0;
                        sb.Append(c);
                    }
                    else // \ works as not-special character
                    {
                        state = 0;
                        sb.Append('\\');
                        index--;
                    }

                    break;
                case 2: //string between quotation marks
                    if (c == '"') //quotation mark in string between quotation marks can be escape mark for following quotation mark or can be ending quotation mark for string between quotation marks
                    {
                        state = 3;
                    }
                    else if (c == '\\') //escaping \ for possible following quotation mark otherwise normal character
                    {
                        state = 4;
                    }
                    else //text in quotation marks
                    {
                        sb.Append(c);
                    }

                    break;
                case 3: //quotation mark in string between quotation marks
                    if (c == '"') //Quotation mark after quotation mark - that means that this one is escaped and can added into result and we will stay in string between quotation marks state
                    {
                        state = 2;
                        sb.Append(c);
                    }
                    else //we had two consecutive quotation marks - this means empty string but the following chars (until space) will be part of same arg result as well
                    {
                        state = 0;
                        index--;
                    }

                    break;
                case 4: //possible escaping \ for quotation mark or normal character in string between quotation marks
                    if (c == '"') //If escaping quotation mark only quotation mark added into result
                    {
                        state = 2;
                        sb.Append(c);
                    }
                    else
                    {
                        state = 2;
                        sb.Append('\\');
                        index--;
                    }

                    break;
            }

            if (index == line.Length)
                return sb.ToString();
        }
    }

0

Aquí está la solución que trata los espacios (espacios únicos o múltiples) como separador de parámetros de línea de comando y devuelve los argumentos reales de la línea de comando:

static string[] ParseMultiSpacedArguments(string commandLine)
{
    var isLastCharSpace = false;
    char[] parmChars = commandLine.ToCharArray();
    bool inQuote = false;
    for (int index = 0; index < parmChars.Length; index++)
    {
        if (parmChars[index] == '"')
            inQuote = !inQuote;
        if (!inQuote && parmChars[index] == ' ' && !isLastCharSpace)
            parmChars[index] = '\n';

        isLastCharSpace = parmChars[index] == '\n' || parmChars[index] == ' ';
    }

    return (new string(parmChars)).Split('\n');
}

0

Hay un paquete NuGet que contiene exactamente la funcionalidad que necesita:

Microsoft.CodeAnalysis.Common contiene la clase CommandLineParser con el método SplitCommandLineIntoArguments .

Lo usas así:

using Microsoft.CodeAnalysis;
// [...]
var cli = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo";
var cliArgs = CommandLineParser.SplitCommandLineIntoArguments(cli, true);

Console.WriteLine(string.Join('\n', cliArgs));
// prints out:
// /src:"C:\tmp\Some Folder\Sub Folder"
// /users:"abcdefg@hijkl.com"
// tasks:"SomeTask,Some Other Task"
// -someParam
// foo

-2

No estoy seguro de haberlo entendido, pero ¿el problema de que el carácter utilizado como divisor también se encuentra dentro del texto? (¿Excepto que se escapa con doble "?)

Si es así, crearía un forbucle y reemplazaría todas las instancias donde <"> esté presente con <|> (u otro carácter" seguro ", pero asegúrese de que solo reemplace <"> y no <"">

Después de iterar la cadena, haría lo que se publicó anteriormente, dividiría la cadena, pero ahora en el carácter <|>.


El doble "" es porque es un literal de cadena @ "..", el doble "dentro de la cadena @" .. "es equivalente a un \ escaped" en una cadena normal
Anton

"la única restricción (creo) es que las cadenas están delimitadas por espacios, a menos que el espacio u ocurra dentro de un" ... "bloque" -> Podría estar disparando a un pájaro con una bazuca, pero ponga un booleano que sea "verdadero" cuando está dentro de una cita, y si se detecta un espacio dentro mientras "verdadero", continúe, de lo contrario <> = <|>
Israr Khan

-6

Sí, el objeto de cadena tiene una función incorporada llamada Split()que toma un único parámetro que especifica el carácter a buscar como delimitador y devuelve una matriz de cadenas (cadena []) con los valores individuales en ella.


1
Esto dividiría la porción src: "C: \ tmp \ Some Folder \ Sub Folder" incorrectamente.
Anton

¿Qué pasa con las comillas dentro de la cadena que desactivan temporalmente la división en espacios?
Daniel Earwicker
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.