Escape los argumentos de línea de comando en c #

Version corta:

¿Es suficiente para envolver el argumento entre comillas y escapar \ y " ?

Versión del código

Quiero pasar los argumentos de la línea de comando string[] args a otro proceso usando ProcessInfo.Arguments.

 ProcessStartInfo info = new ProcessStartInfo(); info.FileName = Application.ExecutablePath; info.UseShellExecute = true; info.Verb = "runas"; // Provides Run as Administrator info.Arguments = EscapeCommandLineArguments(args); Process.Start(info); 

El problema es que obtengo los argumentos como una matriz y debo fusionarlos en una sola cadena. Se podrían diseñar argumentos para engañar a mi progtwig.

 my.exe "C:\Documents and Settings\MyPath \" --kill-all-humans \" except fry" 

De acuerdo con esta respuesta , he creado la siguiente función para escapar de un solo argumento, pero podría haberme perdido algo.

 private static string EscapeCommandLineArguments(string[] args) { string arguments = ""; foreach (string arg in args) { arguments += " \"" + arg.Replace ("\\", "\\\\").Replace("\"", "\\\"") + "\""; } return arguments; } 

¿Es esto lo suficientemente bueno o hay alguna función de marco para esto?

    ¡Es más complicado que eso!

    Estaba teniendo un problema relacionado (escribir .exe de interfaz que llamará al servidor con todos los parámetros pasados ​​+ algunos adicionales) y entonces miré cómo la gente hace eso, se encontró con tu pregunta. Inicialmente, todo parecía arg.Replace (@"\", @"\\").Replace(quote, @"\"+quote) bien haciéndolo mientras arg.Replace (@"\", @"\\").Replace(quote, @"\"+quote) .

    Sin embargo, cuando llamo con argumentos c:\temp a\\b , esto pasa como c:\temp y a\\b , lo que lleva a que se llame al back-end con "c:\\temp" "a\\\\b" – que es incorrecto, porque allí serán dos argumentos c:\\temp y a\\\\b – ¡no lo que queríamos! Hemos sido excesivamente celosos en escapes (windows no es unix!).

    Y entonces leo en detalle http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx y de hecho describe cómo se manejan esos casos: las barras diagonales se tratan como escape solo delante del doble citar.

    Hay un giro en la forma en que se manejan los múltiples allí, la explicación puede dejar mareado por un tiempo. Trataré de volver a formular la regla de unescape aquí: digamos que tenemos una subcadena de N \ , seguida de " . Al separar, reemplazamos esa subcadena con int (N / 2) \ y si n era impar, agregamos " al final.

    La encoding de dicha desencoding sería así: para un argumento, busque cada subcadena de 0 o más \ seguida de " y reemplácela por dos veces como tantos”, seguido por \" . Que podemos hacer así:

     s = Regex.Replace(arg, @"(\\*)" + "\"", @"$1$1\" + "\""); 

    Eso es todo…

    PD. … no Espera, espera, hay más! 🙂

    Hicimos la encoding correctamente pero hay un giro porque está encerrando todos los parámetros entre comillas dobles (en caso de que haya espacios en algunos de ellos). Hay un problema de límite, en caso de que un parámetro termine en \ , agregando " después de que se rompa el significado de la cita de cierre. Ejemplo c:\one\ two analizado a c:\one\ y two luego serán reensamblados a "c:\one\" "two" que me (mal) entenderá como un argumento c:\one" two (lo intenté, no lo estoy inventando). Entonces, lo que necesitamos además es verificar si el argumento termina en \ y, de ser así, duplicar el número de barras diagonales inversas al final, de esta forma:

     s = "\"" + Regex.Replace(s, @"(\\+)$", @"$1$1") + "\""; 

    Mi respuesta fue similar a la respuesta de Nas Banov, pero quería comillas dobles solo si es necesario.

    Recortando comillas dobles innecesarias

    Mi código ahorra poniendo comillas dobles innecesariamente todo el tiempo, lo cual es importante * cuando te estás acercando al límite de caracteres para los parámetros.

     ///  /// Encodes an argument for passing into a program ///  /// The value that should be received by the program /// The value which needs to be passed to the program for the original value /// to come through public static string EncodeParameterArgument(string original) { if( string.IsNullOrEmpty(original)) return original; string value = Regex.Replace(original, @"(\\*)" + "\"", @"$1\$0"); value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\""); return value; } // This is an EDIT // Note that this version does the same but handles new lines in the arugments public static string EncodeParameterArgumentMultiLine(string original) { if (string.IsNullOrEmpty(original)) return original; string value = Regex.Replace(original, @"(\\*)" + "\"", @"$1\$0"); value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"", RegexOptions.Singleline); return value; } 

    explicación

    Para escapar de las barras diagonales inversas y de las comillas dobles correctamente, puede reemplazar las instancias de barras invertidas múltiples seguidas de una comilla doble simple con:

     string value = Regex.Replace(original, @"(\\*)" + "\"", @"\$1$0"); 

    Un doble extra de las barras invertidas originales + 1 y la comilla doble original. es decir, ‘\’ + originalbackslashes + originalbackslashes + ‘”‘. Usé $ 1 $ 0 ya que $ 0 tiene las barras invertidas originales y la comilla doble original, por lo que hace que el reemplazo sea más agradable de leer.

     value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\""); 

    Esto solo puede coincidir con una línea completa que contenga un espacio en blanco.

    Si coincide, agrega comillas dobles al principio y al final.

    Si originalmente hubo barras invertidas al final del argumento, no se habrán citado, ahora que hay una comilla doble al final deben ser. Por lo tanto, están duplicados, lo que los cita a todos y evita que se cite involuntariamente la última comilla doble

    Hace una coincidencia mínima para la primera sección para que la última. *? no come en coincidir con las últimas barras invertidas

    Salida

    Entonces estas entradas producen las siguientes salidas

    Hola

    Hola

    \ hello \ 12 \ 3 \

    \ hello \ 12 \ 3 \

    Hola Mundo

    “Hola Mundo”

    \”Hola\”

    \\”Hola\\\”

    \”Hola Mundo

    “\\”Hola Mundo”

    \”Hola Mundo\

    “\\”Hola Mundo\\”

    Hola Mundo\\

    “Hola Mundo\\\\”

    Me encontré con problemas con esto, también. En lugar de analizar args, opté por tomar la línea de comando original completa y recortar el ejecutable. Esto tiene el beneficio adicional de mantener el espacio en blanco en la llamada, incluso si no es necesario / usado. Todavía tiene que buscar escapes en el ejecutable, pero eso parecía más fácil que los args.

     var commandLine = Environment.CommandLine; var argumentsString = ""; if(args.Length > 0) { // Re-escaping args to be the exact same as they were passed is hard and misses whitespace. // Use the original command line and trim off the executable to get the args. var argIndex = -1; if(commandLine[0] == '"') { //Double-quotes mean we need to dig to find the closing double-quote. var backslashPending = false; var secondDoublequoteIndex = -1; for(var i = 1; i < commandLine.Length; i++) { if(backslashPending) { backslashPending = false; continue; } if(commandLine[i] == '\\') { backslashPending = true; continue; } if(commandLine[i] == '"') { secondDoublequoteIndex = i + 1; break; } } argIndex = secondDoublequoteIndex; } else { // No double-quotes, so args begin after first whitespace. argIndex = commandLine.IndexOf(" ", System.StringComparison.Ordinal); } if(argIndex != -1) { argumentsString = commandLine.Substring(argIndex + 1); } } Console.WriteLine("argumentsString: " + argumentsString); 

    Publiqué un pequeño proyecto en GitHub que maneja la mayoría de los problemas con la encoding / escape de línea de comando:

    https://github.com/ericpopivker/Command-Line-Encoder

    Hay una clase CommandLineEncoder.Utils.cs , así como pruebas unitarias que verifican la funcionalidad de Codificación / Desencoding.

    Te escribí una pequeña muestra para mostrarte cómo usar los caracteres de escape en la línea de comando.

     public static string BuildCommandLineArgs(List argsList) { System.Text.StringBuilder sb = new System.Text.StringBuilder(); foreach (string arg in argsList) { sb.Append("\"\"" + arg.Replace("\"", @"\" + "\"") + "\"\" "); } if (sb.Length > 0) { sb = sb.Remove(sb.Length - 1, 1); } return sb.ToString(); } 

    Y aquí hay un método de prueba:

      List myArgs = new List(); myArgs.Add("test\"123"); // test"123 myArgs.Add("test\"\"123\"\"234"); // test""123""234 myArgs.Add("test123\"\"\"234"); // test123"""234 string cmargs = BuildCommandLineArgs(myArgs); // result: ""test\"123"" ""test\"\"123\"\"234"" ""test123\"\"\"234"" // when you pass this result to your app, you will get this args list: // test"123 // test""123""234 // test123"""234 

    El punto es para envolver cada arg con comillas dobles-dobles (“” arg “”) y para reemplazar todas las comillas dentro del valor arg con comillas escapadas (prueba \ “123).

    He transferido una función de C ++ desde los argumentos de la línea de comando de Todos los comentarios al artículo de forma incorrecta .

    Funciona bien, pero debe tener en cuenta que cmd.exe interpreta la línea de comandos de manera diferente. Si ( y solo si , como el autor original del artículo anotado) tu línea de comando será interpretada por cmd.exe , también deberías escapar de los metacaracteres del intérprete de comandos.

     ///  /// This routine appends the given argument to a command line such that /// CommandLineToArgvW will return the argument string unchanged. Arguments /// in a command line should be separated by spaces; this function does /// not add these spaces. ///  /// Supplies the argument to encode. ///  /// Supplies an indication of whether we should quote the argument even if it /// does not contain any characters that would ordinarily require quoting. ///  private static string EncodeParameterArgument(string argument, bool force = false) { if (argument == null) throw new ArgumentNullException(nameof(argument)); // Unless we're told otherwise, don't quote unless we actually // need to do so --- hopefully avoid problems if programs won't // parse quotes properly if (force == false && argument.Length > 0 && argument.IndexOfAny(" \t\n\v\"".ToCharArray()) == -1) { return argument; } var quoted = new StringBuilder(); quoted.Append('"'); var numberBackslashes = 0; foreach (var chr in argument) { switch (chr) { case '\\': numberBackslashes++; continue; case '"': // Escape all backslashes and the following // double quotation mark. quoted.Append('\\', numberBackslashes*2 + 1); quoted.Append(chr); break; default: // Backslashes aren't special here. quoted.Append('\\', numberBackslashes); quoted.Append(chr); break; } numberBackslashes = 0; } // Escape all backslashes, but let the terminating // double quotation mark we add below be interpreted // as a metacharacter. quoted.Append('\\', numberBackslashes*2); quoted.Append('"'); return quoted.ToString(); } 
     static string BuildCommandLineFromArgs(params string[] args) { if (args == null) return null; string result = ""; if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX) { foreach (string arg in args) { result += (result.Length > 0 ? " " : "") + arg .Replace(@" ", @"\ ") .Replace("\t", "\\\t") .Replace(@"\", @"\\") .Replace(@"""", @"\""") .Replace(@"< ", @"\<") .Replace(@">", @"\>") .Replace(@"|", @"\|") .Replace(@"@", @"\@") .Replace(@"&", @"\&"); } } else //Windows family { bool enclosedInApo, wasApo; string subResult; foreach (string arg in args) { enclosedInApo = arg.LastIndexOfAny( new char[] { ' ', '\t', '|', '@', '^', '< ', '>', '&'}) >= 0; wasApo = enclosedInApo; subResult = ""; for (int i = arg.Length - 1; i >= 0; i--) { switch (arg[i]) { case '"': subResult = @"\""" + subResult; wasApo = true; break; case '\\': subResult = (wasApo ? @"\\" : @"\") + subResult; break; default: subResult = arg[i] + subResult; wasApo = false; break; } } result += (result.Length > 0 ? " " : "") + (enclosedInApo ? "\"" + subResult + "\"" : subResult); } } return result; } 

    Hace un buen trabajo al agregar argumentos, pero no escapa. Se agregó un comentario en el método donde debería ir la secuencia de escape.

     public static string ApplicationArguments() { List args = Environment.GetCommandLineArgs().ToList(); args.RemoveAt(0); // remove executable StringBuilder sb = new StringBuilder(); foreach (string s in args) { // todo: add escape double quotes here sb.Append(string.Format("\"{0}\" ", s)); // wrap all args in quotes } return sb.ToString().Trim(); }