Архівуйте аргументи командного рядка в c #


75

Коротка версія:

Чи досить обернути аргумент у лапки та уникнути \і "?

Кодова версія

Я хочу передати аргументи командного рядка 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;
}

Це достатньо добре чи для цього існує якась рамкова функція?


5
ти спробував пройти як є? Думаю, якщо його передадуть вам, його можна передати іншій команді. якщо ви потрапили в якісь помилки, тоді ви можете подумати про втечу.
Sanjeevakumar Hiremath

2
@Sanjeevakumar так, наприклад: "C:\Documents and Settings\MyPath \" --kill-all-humans \" except fry"це не було б добре, оскільки я роблю привілейований дзвінок.
hultqvist

1
@Sanjeevakumar Main (string [] args) - це масив нескопійованих рядків, тому, якщо я запустив my.exe "test\"test"arg [0], будеtest"test
hultqvist

1. чи потрібно лише втечу на основі першого коментаря, схоже, що втеча - це не те, що ти хочеш зробити. 2. що таке нескопіровані рядки? коли ви отримуєте такий рядок, як abc"defце, abc"defчому ви хочете втекти від нього зараз? якщо ви додаєте щось на зразок "abc" + "" "" + "def", це має сенс. спостерігати """"тікає"
Sanjeevakumar Hiremath

Так abc"defє правильним, враховуючи введення, однак, якщо я хочу передати його іншому процесу, я повинен уникнути його, перш ніж додавати його до аргументу єдиного рядка. Див. Оновлене запитання для роз’яснення.
hultqvist

Відповіді:


68

Це все складніше, ніж це!

У мене була пов'язана проблема (написання інтерфейсу .exe, який буде викликати фоновий код із усіма переданими параметрами + деякими додатковими), і тому я подивився, як люди це роблять, натрапив на ваше запитання. Спочатку все здавалося добре робити це, як ви пропонуєте arg.Replace (@"\", @"\\").Replace(quote, @"\"+quote).

Однак, коли я дзвоню з аргументами c:\temp a\\b, це передається як c:\tempі a\\b, що призводить до того, що внутрішній виклик викликається"c:\\temp" "a\\\\b" - що є неправильним, оскільки там буде два аргументи c:\\tempі a\\\\b- не те, що ми хотіли! Ми були надмірно завзятими під втечами (windows не unix!).

І тому я докладно прочитав http://msdn.microsoft.com/en-us/library/system.environment.getcommandlineargs.aspx, і там фактично описується, як розглядаються ці справи: зворотні скісні риски трактуються як втеча лише перед подвійним цитата.

У цьому є поворот у тому, як \там обробляють кілька , пояснення може на деякий час запаморочити. Я спробую переформулювати сказане правило невтечі тут: скажімо, у нас є підрядок N \ , за яким слідує ". Під час невиділення, ми замінюємо цей підрядок на int (N / 2), \ і якщо 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") + "\"";

6
+1 за пояснення цього божевілля. Однак не якщо *і +бути всередині групування дужки в наведених вище виразах сірникових? Інакше $1заміною буде лише одна зворотна коса риса.
bobince

На самом деле я думаю , що ці дві заміни можуть бути об'єднані в: "\""+Regex.Replace(s, "(\\\\*)(\\\\$|\")", "$1$1\\$2")+"\"". Однак мій мозок починає тонути зараз, так вдячний, якби ви могли перевірити правильність :-)
bobince


1
Дякую за вашу відповідь! Не могли б ви додати TL; Статичний метод DR, який обробляє все? Мені дуже подобається ваша відповідь, але я повинен її прочитати і зрозуміти кожного разу, коли мені потрібна інформація (бо я занадто дурний, щоб її повністю запам'ятати) ...
vojta

@vojta - мої вибачення, але минуло п’ять років, і я не пам’ятаю подробиці. Перечитавши те, що я написав, я гадаю, що просто потрібно було назвати ці два рядки. Але ви, мабуть, зараз краще розумієте справу, чому б вам не відредагувати відповідь, а для нащадків додати TL-DNR?
Нас Банов,

31

Моя відповідь була схожа на відповідь Наса Банова, але я хотів подвійні лапки лише за необхідності.

Вирізання зайвих непотрібних подвійних лапок

Мій код економить непотрібно, постійно ставлячи навколо нього подвійні лапки, що важливо *, коли ви наближаєтесь до обмеження символів для параметрів.

/// <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 \

Привіт Світ

"Привіт Світ"

\"Здрастуйте\"

\\"Здрастуйте\\\"

\"Привіт Світ

"\\"Привіт Світ"

\"Привіт Світ\

"\\"Привіт Світ\\"

Привіт Світ\\

"Привіт Світ\\\\"


1
Одне незначне виправлення: коли оригінал порожній, вам потрібно повернути пару подвійних лапок ""замість порожнього рядка, щоб командний рядок знав, що аргумент є. Крім цього, це працює чудово!
Джої Адамс,

Там повинна бути помилка ... Вхід: <a>\n <b/>\n</a>. Вихід: <a>\n <b/>\n</a>. Схоже, відсутні зовнішні цитати! Я щось роблю не так? ( \nмається на увазі новий рядок, звичайно, коментарі SO не дуже зручні для нового рядка)
vojta

Я ніколи не думав сперечатися з новим рядком у ньому. Не вдається вставити код сюди, здається .. Я зміню свою відповідь, включивши як оригінал, так і той, який обробляє нові рядки
Метт Вукоманович

7

Я переніс функцію C ++ з аргументів командного рядка "Усі" неправильно .

Це чудово працює, але слід зазначити, що 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();
}

6

Я теж стикався з проблемами з цим. Замість того, щоб розпаковувати аргументи, я взяв повний оригінальний командний рядок і обрізав виконуваний файл. Це мало додаткову перевагу - зберігати пробіли під час дзвінка, навіть якщо це не потрібно / не використовується. Він все ще повинен переслідувати втечі у виконуваному файлі, але це здавалося простішим, ніж аргументи.

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

1
Перетворив ваш код на функцію C:LPWSTR GetArgStrFromCommandLine(LPWSTR c) {if (*c++ != L'"') c = wcspbrk(--c, L" \t\r\n\v\f"); else while (*c && *c++ != L'"') if (*c == L'\\') ++c; return c;}
7vujy0f0hy


2

Я написав вам невеликий зразок, щоб продемонструвати, як використовувати символи переходу в командному рядку.

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 "") і замінити всі лапки всередині значення аргументу на екрановану лапку (тест \ "123).


Ваші приклади працюють, однак @ "\ test" не працює, а @ "test \" розривається з Win32Exception. Останнє досить часто зустрічається в моїй роботі, коли передаю шляхи як аргументи.
hultqvist

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

0

Хороша робота з додавання аргументів, але не втікає. Додано коментар до методу, куди повинна йти втеча послідовності.

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

1
Я боюся, що ваш код обгортає аргументи лише лапками, але він не втікає взагалі. Якби я запустив my.exe "arg1\" \"arg2"даючи один аргумент, arg1" "arg2ваш код генерував би два аргументи, arg1іarg2
hultqvist

Добре, я не тестував проти цього. Я припускаю, що є причина для цього, arg1" "arg2хоча я не уявляю, чому. Ваше право, я все одно мав би втекти туди, я перегляну цю тему, щоб побачити, хто придумав найкращий механізм для цього.
Чак Севедж

Я можу думати про двох. 1: Хтось із поганими намірами намагається обдурити вашу програму на виконання небезпечних команд. 2: Передача аргументуJohn "The Boss" Smith
hultqvist

0

Альтернативний підхід

Якщо ви передаєте складний об'єкт, такий як вкладений JSON, і у вас є контроль над системою, яка отримує аргументи командного рядка, набагато простіше просто закодувати аргументи командного рядка як base64, а потім декодувати їх із системи-одержувача.

Дивіться тут: Кодування / декодування рядка до / з Base64

Приклад використання: мені потрібно було передати об'єкт JSON, який містив рядок XML в одному із властивостей, надмірно складним для втечі. Це вирішило це.


0

Скопіюйте функцію зразка коду з цієї URL-адреси:

http://csharptest.net/529/how-to-correct-escape-command-line-arguments-in-c/index.html

Ви можете отримати командний рядок для виконання, наприклад, таким чином:

String cmdLine = EscapeArguments(Environment.GetCommandLineArgs().Skip(1).ToArray());

Skip(1) пропускає ім'я виконуваного файлу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.