visual studio pass parser parameter debug c# .net command-line-arguments

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



debug command line arguments c# (9)

¡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 codificación de dicha descodificación 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 codificación 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") + "/"";

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 programa.

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?


AFAIK no hay una función de marco para esto.

Para su caso de uso simple, lo que está haciendo parece ser suficiente, a menos que el programa al que envía los argumentos reconozca que los caracteres son especiales de alguna manera, en cuyo caso también deberá escapar de ellos.


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


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.

/// <summary> /// 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. /// </summary> /// <param name="argument">Supplies the argument to encode.</param> /// <param name="force"> /// Supplies an indication of whether we should quote the argument even if it /// does not contain any characters that would ordinarily require quoting. /// </param> 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(); }


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


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.

/// <summary> /// Encodes an argument for passing into a program /// </summary> /// <param name="original">The value that should be received by the program</param> /// <returns>The value which needs to be passed to the program for the original value /// to come through</returns> 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////"



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<string> 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<string> myArgs = new List<string>(); 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).


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