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:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam foo";
Resultado de ejemplo:
string[] parameterArray = new string[] {
@"/src:C:\tmp\Some Folder\Sub Folder",
@"/users:[email protected]",
@"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)
fuente
Respuestas:
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
CommandLineToArgvW
función para dividir una cadena en una matriz de cadenas: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); } }
fuente
CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
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:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam", @"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""[email protected]""", @"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.
fuente
char.IsWhiteSpace
lugar de== ' '
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'); }
fuente
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); }
fuente
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.
fuente
bla.exe aAAA"b\"ASDS\"c"dSADSD
que da como resultado el resultado deaAAAb"ASDS"cdSADSD
esta soluciónaAAA"b"ASDS"c"dSADSD
. Podría considerar cambiar elTrimMatchingQuotes
a ayRegex("(?<!\\\\)\\\"")
usarlo así .Environment.GetCommandLineArgs ()
fuente
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 parastring
):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(); }
fuente
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!
fuente
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
.fuente
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.
fuente
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[] args
pará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
,hadQuote
yprevCh
, y la salida es recogido encurrentArg
yargs
.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
cmd
comando 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
Regex
ni hace ninguna concatenación de cadenas, sino que usa aStringBuilder
para 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(); }
fuente
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:
También admite múltiples espacios (rompe argumentos solo una vez por bloque de espacios).
fuente
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:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam foo", new[] { @"/src:C:\tmp\Some Folder\Sub Folder", @"/users:[email protected]", @"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í
fuente
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.
fuente
Esta es una respuesta al código de Anton, que no funciona con comillas de escape. Modifiqué 3 lugares.
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; }
fuente
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(); }
fuente
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).
fuente
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.
fuente
posicao_ponteiro += ((fim - posicao_ponteiro)+1);
.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; } } }
fuente
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:
resultado:
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(); }
fuente
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(); } }
fuente
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'); }
fuente
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:""[email protected]"" 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:"[email protected]" // tasks:"SomeTask,Some Other Task" // -someParam // foo
fuente
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
for
bucle 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 <|>.
fuente
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.fuente