Escape argumentos da linha de comando em c #

Versão curta:

É suficiente para envolver o argumento entre aspas e escaping \ e " ?

Versão do código

Eu quero passar os argumentos da linha de comando string[] args para outro processo 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); 

O problema é que eu obtenho os argumentos como um array e preciso mesclá-los em uma única string. Um argumento poderia ser criado para enganar meu programa.

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

De acordo com essa resposta , criei a seguinte function para escaping de um único argumento, mas talvez eu tenha perdido alguma coisa.

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

Isso é bom o suficiente ou existe alguma function de estrutura para isso?

    É mais complicado que isso!

    Eu estava tendo problemas relacionados (escrevendo o front-end .exe que vai chamar o back-end com todos os parâmetros passados ​​+ alguns extras) e então eu olhei como as pessoas fazem isso, corri para a sua pergunta. Inicialmente, tudo parecia bom fazê-lo, como você sugere arg.Replace (@"\", @"\\").Replace(quote, @"\"+quote) .

    No entanto, quando eu chamo com argumentos c:\temp a\\b , isso é passado como c:\temp e a\\b , que leva ao back-end sendo chamado com "c:\\temp" "a\\\\b" – o que é incorreto, porque haverá dois argumentos c:\\temp e a\\\\b – não o que queríamos! Nós temos excesso de zelo nas fugas (o Windows não é unix!).

    E assim, eu leio em detalhes http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx e ele realmente descreve como esses casos são tratados: barras invertidas são tratadas como escape apenas na frente do dobro citar.

    Há uma reviravolta na forma como vários deles são tratados, a explicação pode deixar alguém tonto por um tempo. Vou tentar rewrite a dita regra unescape aqui: digamos que tenhamos uma subseqüência de N \ , seguida de " . Quando sem escape, substituímos essa subseqüência com int (N / 2) \ e se n for ímpar, adicionamos " no fim.

    A codificação para tal decodificação seria assim: para um argumento, encontre cada subcadeia de 0 ou mais \ seguido por " e substitua por duas vezes quantas \ , seguido por \" . O que podemos fazer assim:

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

    Isso é tudo…

    PS. … não . Espere, espere – tem mais! 🙂

    Nós fizemos a codificação corretamente, mas há uma diferença porque você está colocando todos os parâmetros entre aspas duplas (caso haja espaços em algumas delas). Há um problema de limite – caso um parâmetro termine em \ , adicionando " depois que ele quebrará o significado de cotação de fechamento. Exemplo c:\one\ two analisado para c:\one\ e two seguida, serão remontados para "c:\one\" "two" que eu (mis) entendi como um argumento c:\one" two (tentei isso, não estou inventando). Então, o que precisamos, além disso, é verificar se o argumento termina em \ e, em caso afirmativo, o dobro do número de barras invertidas no final, assim:

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

    Minha resposta foi semelhante à resposta de Nas Banov, mas eu queria aspas duplas apenas se necessário.

    Cortando aspas duplas extras desnecessárias

    Meu código salva desnecessariamente colocando aspas duplas em torno dele o tempo todo, o que é importante * quando você está se aproximando do limite de caracteres para os 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; } 

    explicação

    Para escaping das barras invertidas e das aspas duplas corretamente, você pode simplesmente replace quaisquer ocorrências de múltiplas barras invertidas seguidas por uma aspa dupla simples com:

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

    Um extra duas vezes as barras invertidas originais + 1 e a aspa dupla original. ou seja, ‘\’ + backslashes originais + backslashes originais + ‘”‘. Eu usei $ 1 $ 0, já que $ 0 tem as barras invertidas originais e a aspa dupla original, então torna a substituição mais agradável de se ler.

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

    Isso só pode corresponder a uma linha inteira que contenha um espaço em branco.

    Se corresponder, ele adiciona aspas duplas no início e no final.

    Se houve originalmente barras invertidas no final do argumento, elas não terão sido citadas, agora que há uma aspa dupla no final que elas precisam ser. Então, eles são duplicados, o que cita todos eles e evita, sem querer, citar as aspas duplas finais

    Ele faz uma correspondência mínima para a primeira seção, de modo que o último. *? não come em combinar as barras invertidas finais

    Saída

    Então, essas inputs produzem as seguintes saídas

    Olá

    Olá

    \ oi \ 12 \ 3 \

    \ oi \ 12 \ 3 \

    Olá Mundo

    “Olá Mundo”

    \”Olá\”

    \\”Olá\\\”

    \”Olá Mundo

    “\\”Olá Mundo”

    \”Olá Mundo\

    “\\”Olá Mundo\\”

    Olá Mundo\\

    “Olá Mundo\\\\”

    Eu estava correndo em problemas com isso também. Em vez de unparsing args, eu peguei a linha de comando original completa e cortei o executável. Isso tinha o benefício adicional de manter espaços em branco na chamada, mesmo que não fosse necessário / usado. Ainda tem que perseguir escapes no executável, mas isso pareceu mais fácil que os 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); 

    Publiquei um pequeno projeto no GitHub que lida com a maioria dos problemas com codificação / escape de linha de comando:

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

    Há uma class CommandLineEncoder.Utils.cs , bem como Testes de unidade que verificam a funcionalidade Codificação / Decodificação.

    Eu escrevi uma pequena amostra para mostrar como usar chars de escape na linha 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(); } 

    E aqui está um método de teste:

      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 

    O ponto é envolver cada arg com aspas duplas duplas (“” arg “”) e replace todas as aspas dentro do valor arg com aspas escapadas (teste \ “123).

    Eu tenho portado uma function de C ++ a partir de todos os argumentos de linha de comando de citações o artigo de maneira errada .

    Ele funciona bem, mas você deve observar que cmd.exe interpreta a linha de comando de forma diferente. Se ( e somente se , como o autor original do artigo notou), sua linha de comando for interpretada por cmd.exe você também deve escaping dos metacaracteres do shell.

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

    Faz um bom trabalho de adicionar argumentos, mas não escapa. Adicionado comentário no método onde a seqüência de escape deve ir.

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