Escape аргументы командной строки в С#
Краткая версия:
Достаточно ли обернуть аргумент в кавычки и выйти \
и "
?
Версия кода
Я хочу передать аргументы командной строки string[] args
другому процессу с помощью 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);
Проблема в том, что я получаю аргументы как массив и должен объединять их в одну строку. Аргументы могут быть созданы, чтобы обмануть мою программу.
my.exe "C:\Documents and Settings\MyPath \" --kill-all-humans \" except fry"
В соответствии с этим ответом я создал следующую функцию, чтобы избежать одного аргумента, но я мог что-то пропустить.
private static string EscapeCommandLineArguments(string[] args)
{
string arguments = "";
foreach (string arg in args)
{
arguments += " \"" +
arg.Replace ("\\", "\\\\").Replace("\"", "\\\"") +
"\"";
}
return arguments;
}
Хорошо ли это или есть какая-либо фрейм-функция для этого?
Ответы
Ответ 1
Это сложнее, чем это!
У меня была связанная с этим проблема (написав front-end.exe, который вызовет back-end со всеми переданными параметрами + некоторые дополнительные), и поэтому я посмотрел, как люди это делают, столкнулся с вашим вопросом. Первоначально все казалось хорошим, если вы предложили arg.Replace (@"\", @"\\").Replace(quote, @"\"+quote)
.
Однако, когда я вызываю с аргументами c:\temp a\\b
, это передается как c:\temp
и a\\b
, что приводит к тому, что back-end вызывается с "c:\\temp" "a\\\\b"
- это неверно, потому что там будут два аргумента c:\\temp
и a\\\\b
- не то, что мы хотели! Мы были чрезмерно усердны в побегах (окна не unix!).
Итак, я подробно читаю http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx и на самом деле описывает там, как обрабатываются эти случаи: обратные косые черты рассматриваются как escape только перед двойной кавычкой.
В нем есть трюк, когда обрабатывается несколько \
, объяснение может оставить какое-то головокружение на некоторое время. Я попытаюсь повторно сформулировать это правило unescape: скажем, у нас есть подстрока N \
, за которой следует "
. Когда unescaping мы заменим эту подстроку на int (N/2) \
, а iff N нечетно, мы добавим "
в конец.
Кодировка для такого декодирования будет выглядеть следующим образом: для аргумента найдите каждую подстроку 0 или более \
, за которой следует "
, и замените ее на два раза больше \
, а затем \"
. Что мы можем сделать так:
s = Regex.Replace(arg, @"(\\*)" + "\"", @"$1$1\" + "\"");
Что все...
PS.... не. Подождите, подождите - больше!:)
Мы правильно кодировали, но есть твист, потому что вы включаете все параметры в двойные кавычки (в случае, если в некоторых из них есть пробелы). Существует граничная проблема - в случае, если параметр заканчивается на \
, добавив "
после того, как он нарушит значение закрывающей цитаты. Пример c:\one\ two
проанализирован на c:\one\
и two
, после чего будет собрана в "c:\one\" "two"
, которая будет меня (неверно) понята как один аргумент c:\one" two
(я пробовал это, я не собираюсь это делать). Так что нам нужно дополнительно проверить, заканчивается ли аргумент на \
, и если да, удвоьте количество обратных косых черт в конце, например:
s = "\"" + Regex.Replace(s, @"(\\+)$", @"$1$1") + "\"";
Ответ 2
Мой ответ был похож на ответ Наса Банова, но я хотел получить двойные кавычки только при необходимости.
Вырезание лишних лишних двойных кавычек
Мой код экономит ненужное размещение двойных кавычек вокруг него все время, которое важно *, когда вы приближаетесь к пределу символа для параметров.
/// <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;
}
Объяснение
Чтобы избежать обратных косых черт и двойных кавычек, вы можете просто заменить любые экземпляры нескольких обратных косых черт, за которыми следует одна двойная кавычка, с помощью:
string value = Regex.Replace(original, @"(\\*)" + "\"", @"\$1$0");
Дополнительно в два раза больше исходной обратной косой черты + 1 и оригинальной двойной кавычки. то есть '\' + originalbackslashes + originalbackslashes + '"'. Я использовал $1 $0, так как $0 имеет исходную обратную косую черту и оригинальную двойную кавычку, поэтому делает замену более приятной для чтения.
value = Regex.Replace(value, @"^(.*\s.*?)(\\*)$", "\"$1$2$2\"");
Это может только соответствовать всей строке, содержащей пробелы.
Если он совпадает, он добавляет двойные кавычки к началу и концу.
Если в конце аргумента были исходные обратные косые черты, они не будут указаны, теперь, когда в конце есть двойная кавычка, они должны быть. Таким образом, они дублируются, что их цитирует, и предотвращает непреднамеренное цитирование заключительной двойной кавычки
Он выполняет минимальное совпадение для первого раздела, так что последний. *? не питается в соответствии с окончательной обратной косой чертой
Выход
Таким образом, эти входы создают следующие выходы
Привет
привет
\ привет \12\3\
<Ь > \Привет\12\3\
привет мир
"привет мир"
\ "Привет \"
\\ "привет \\\"
\ "привет\мир
"\\" hello\world "
\ "hello \\\ world\
"\\" привет \\\ мир \\"
привет мир \\
"hello world \\\\"
Ответ 3
У меня тоже возникали проблемы с этим. Вместо unparsing args я пошел с полной версией командной строки и обрезкой исполняемого файла. Это имело дополнительную выгоду от сохранения пробелов в вызове, даже если оно не требуется/используется. Он по-прежнему должен преследовать escape-последовательности в исполняемом файле, но это оказалось проще, чем аргументы.
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);
Ответ 4
Я портировал С++-функцию из Все цитируют аргументы командной строки неправильным образом.
Он отлично работает, но вы должны заметить, что cmd.exe
интерпретирует командную строку по-разному. Если ( и только если, как отмечал автор оригинала статьи), ваша командная строка будет интерпретироваться cmd.exe
, вы также должны избежать метасимволов оболочки.
/// <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();
}
Ответ 5
Я опубликовал небольшой проект на GitHub, который обрабатывает большинство проблем с кодировкой/экранированием командной строки:
https://github.com/ericpopivker/Command-Line-Encoder
Существует класс CommandLineEncoder.Utils.cs, а также тесты модулей, которые проверяют функциональность кодирования/декодирования.
Ответ 6
Я написал вам небольшой пример, чтобы показать вам, как использовать escape-символы в командной строке.
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();
}
И вот тестовый метод:
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
Дело в том, чтобы обернуть каждый аргумент с двойными двойными кавычками ( "arg" ") и заменить все кавычки внутри значения arg на экранированную цитату (test\" 123).
Ответ 7
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;
}
Ответ 8
Хорошая работа по добавлению аргументов, но не утихает. Добавлен комментарий в методе, в котором должна выполняться 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();
}
Ответ 9
Альтернативный подход
Если вы передаете сложный объект, такой как вложенный JSON, и у вас есть контроль над системой, которая получает аргументы командной строки, гораздо проще просто кодировать командную строку arg/s как base64, а затем декодировать их из принимающей системы.
Смотрите здесь: Encode/Decode String в/из Base64
Пример использования: мне нужно было передать объект JSON, содержащий строку XML, в одно из свойств, которое было слишком сложным для экранирования.Это решило это.
Ответ 10
AFAIK для этой функции не существует.
В вашем простом случае то, что вы делаете, выглядит достаточным, если программа, в которую вы отправляете аргументы, не удостаивает каких-либо символов каким-либо образом, и в этом случае вам также нужно будет избежать этих ошибок.