Архівуйте аргументи командного рядка в 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.