Разделить строку, содержащую параметры командной строки, на строку [] в C #


91

У меня есть единственная строка, содержащая параметры командной строки для передачи в другой исполняемый файл, и мне нужно извлечь строку [], содержащую отдельные параметры, так же, как это сделал бы C #, если бы команды были указаны в командной строке. Строка [] будет использоваться при выполнении точки входа другой сборки через отражение.

Есть ли для этого стандартная функция? Или есть предпочтительный метод (регулярное выражение?) Для правильного разделения параметров? Он должен обрабатывать строки с разделителями '"', которые могут правильно содержать пробелы, поэтому я не могу просто разделить на '' '.

Пример строки:

string parameterString = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo";

Пример результата:

string[] parameterArray = new string[] { 
  @"/src:C:\tmp\Some Folder\Sub Folder",
  @"/users:abcdefg@hijkl.com",
  @"tasks:SomeTask,Some Other Task",
  @"-someParam",
  @"foo"
};

Мне не нужна библиотека синтаксического анализа командной строки, просто способ получить String [], который должен быть сгенерирован.

Обновление : мне пришлось изменить ожидаемый результат, чтобы он соответствовал тому, что на самом деле генерируется C # (удалил лишние символы в разделенных строках)



5
Каждый раз, когда кто-то отвечает, у вас появляется возражение, основанное на материалах, которых нет в вашем сообщении. Предлагаю вам обновить свой пост этим материалом. Вы можете получить более точные ответы.
— tvanfosson

1
Хороший вопрос, ищу то же самое. Надеялся найти кого-нибудь, кто скажет: «Привет .net раскрывает это здесь ...» :) Если я наткнусь на это в какой-то момент, я отправлю это здесь, даже если ему уже 6 лет. Все еще актуальный вопрос!
— MikeJansen

В ответе ниже я создал полностью управляемую версию, так как мне тоже нужна эта функция.
— ygoe

Ответы:


75

В дополнение к хорошему и чисто управляемому решению от Earwicker , для полноты картины , возможно, стоит упомянуть, что Windows также предоставляет CommandLineToArgvWфункцию для разбиения строки на массив строк:

LPWSTR *CommandLineToArgvW(
    LPCWSTR lpCmdLine, int *pNumArgs);

Анализирует строку командной строки Unicode и возвращает массив указателей на аргументы командной строки вместе со счетчиком таких аргументов способом, аналогичным стандартным значениям argv и argc времени выполнения C.

Пример вызова этого API из C # и распаковки результирующего массива строк в управляемом коде можно найти по адресу « Преобразование строки командной строки в Args [] с использованием API CommandLineToArgvW () ». Ниже представлена ​​чуть более простая версия того же кода:

[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
    [MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);

public static string[] CommandLineToArgs(string commandLine)
{
    int argc;
    var argv = CommandLineToArgvW(commandLine, out argc);        
    if (argv == IntPtr.Zero)
        throw new System.ComponentModel.Win32Exception();
    try
    {
        var args = new string[argc];
        for (var i = 0; i < args.Length; i++)
        {
            var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
            args[i] = Marshal.PtrToStringUni(p);
        }

        return args;
    }
    finally
    {
        Marshal.FreeHGlobal(argv);
    }
}

1
Эта функция требует, чтобы вы избегали обратной косой черты пути внутри кавычек. «C: \ Program Files \» должен быть «C: \ Program Files \\», чтобы это работало и правильно анализировало строку.
— Магнус Линде,

8
Также стоит отметить, что CommandLineArgvW ожидает, что первым аргументом будет имя программы, а применяемая магия синтаксического анализа не совсем такая же, если она не передана. Вы можете подделать это примерно так:CommandLineToArgs("foo.exe " + commandLine).Skip(1).ToArray();
— Скотт Вегнер

4
Для полноты картины MSVCRT не использует CommandLineToArgvW () для преобразования командной строки в argc / argv. Он использует свой собственный код, который отличается. Например, попробуйте вызвать CreateProcess с этой строкой: a "b c" def. В main () вы получите 3 аргумента (как описано в MSDN), но комбинация CommandLineToArgvW () / GetCommandLineW () даст вам 2.
— LRN

7
Боже мой, это такой беспорядок. типичный суп из РС. ничто не канонизировано, и KISS никогда не уважают в мире MS.
— v.oddou 02

1
Я опубликовал кроссплатформенную версию реализации MSVCRT, переведенную Microsoft, и высокоточное приближение с использованием Regex. Я знаю, что это старое, но эй - никаких свитков тела.
— TylerY86 07

101

Меня раздражает, что нет функции для разделения строки на основе функции, которая проверяет каждый символ. Если бы был, то можно было бы написать так:

    public static IEnumerable<string> SplitCommandLine(string commandLine)
    {
        bool inQuotes = false;

        return commandLine.Split(c =>
                                 {
                                     if (c == '\"')
                                         inQuotes = !inQuotes;

                                     return !inQuotes && c == ' ';
                                 })
                          .Select(arg => arg.Trim().TrimMatchingQuotes('\"'))
                          .Where(arg => !string.IsNullOrEmpty(arg));
    }

Хотя, написав это, почему бы не написать необходимые методы расширения. Ладно, ты меня уговорил ...

Во-первых, моя собственная версия Split, которая принимает функцию, которая должна решать, должен ли указанный символ разбивать строку:

    public static IEnumerable<string> Split(this string str, 
                                            Func<char, bool> controller)
    {
        int nextPiece = 0;

        for (int c = 0; c < str.Length; c++)
        {
            if (controller(str[c]))
            {
                yield return str.Substring(nextPiece, c - nextPiece);
                nextPiece = c + 1;
            }
        }

        yield return str.Substring(nextPiece);
    }

В зависимости от ситуации это может привести к появлению пустых строк, но, возможно, эта информация будет полезна в других случаях, поэтому я не удаляю пустые записи в этой функции.

Во-вторых (и более приземленно) небольшой помощник, который обрежет совпадающую пару кавычек в начале и в конце строки. Он более сложен, чем стандартный метод Trim - он обрезает только один символ с каждого конца, и он не будет обрезать только один конец:

    public static string TrimMatchingQuotes(this string input, char quote)
    {
        if ((input.Length >= 2) && 
            (input[0] == quote) && (input[input.Length - 1] == quote))
            return input.Substring(1, input.Length - 2);

        return input;
    }

И я полагаю, вам тоже понадобятся тесты. Что ж, тогда ладно. Но это должно быть совсем последнее! Сначала вспомогательная функция, которая сравнивает результат разделения с ожидаемым содержимым массива:

    public static void Test(string cmdLine, params string[] args)
    {
        string[] split = SplitCommandLine(cmdLine).ToArray();

        Debug.Assert(split.Length == args.Length);

        for (int n = 0; n < split.Length; n++)
            Debug.Assert(split[n] == args[n]);
    }

Тогда я могу написать такие тесты:

        Test("");
        Test("a", "a");
        Test(" abc ", "abc");
        Test("a b ", "a", "b");
        Test("a b \"c d\"", "a", "b", "c d");

Вот тест на ваши требования:

        Test(@"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam",
             @"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""abcdefg@hijkl.com""", @"tasks:""SomeTask,Some Other Task""", @"-someParam");

Обратите внимание, что реализация имеет дополнительную функцию, заключающуюся в том, что она удаляет кавычки вокруг аргумента, если это имеет смысл (благодаря функции TrimMatchingQuotes). Я считаю, что это часть обычной интерпретации командной строки.


Мне пришлось снять отметку с этого ответа, потому что у меня не было ожидаемых результатов. Фактический результат не должен содержать "" в окончательном массиве
— Антон

16
Я прихожу в Stack Overflow, чтобы уйти от требований, которые все время меняются! :) Вы можете использовать Replace ("\" "," ") вместо TrimMatchingQuotes (), чтобы избавиться от всех кавычек. Но Windows поддерживает \", чтобы разрешить передачу символа кавычек. Моя функция разделения не может этого сделать.
— Daniel Earwicker

1
Хороший Earwicker :) Антон: Это решение, которое я пытался описать вам в моем предыдущем посте, но Earwicker сделал намного лучше, написав его;) И также значительно расширил его;)
— Israr Khan

Пробел - не единственный разделительный символ для аргументов командной строки, не так ли?
— Луи Рис

@ Луис Рис - я не уверен. Если это проблема, ее довольно легко решить: использовать char.IsWhiteSpaceвместо== ' '
— Дэниел Эрвикер

25

Синтаксический анализатор командной строки Windows ведет себя так же, как вы говорите, разделяя пространство, если перед ним нет незакрытой цитаты. Я бы порекомендовал написать парсер самостоятельно. Что-то вроде этого может быть:

    static string[] ParseArguments(string commandLine)
    {
        char[] parmChars = commandLine.ToCharArray();
        bool inQuote = false;
        for (int index = 0; index < parmChars.Length; index++)
        {
            if (parmChars[index] == '"')
                inQuote = !inQuote;
            if (!inQuote && parmChars[index] == ' ')
                parmChars[index] = '\n';
        }
        return (new string(parmChars)).Split('\n');
    }

2
В итоге я получил то же самое, за исключением того, что я использовал .Split (new char [] {'\ n'}, StringSplitOptions.RemoveEmptyEntries) в последней строке на случай, если между параметрами были лишние ''. Кажется, работает.
— Антон

3
Я предполагаю, что у Windows должен быть способ избежать кавычек в параметрах ... этот алгоритм не принимает это во внимание.
— rmeador

Удаление пустых строк, удаление внешних кавычек и обработка экранированных кавычек оставлены в качестве упражнения для читателя.
— Джеффри Л. Уитледж

Char.IsWhiteSpace () может здесь помочь
— Сэм Макрил

Это решение хорошо, если аргументы разделены одним пробелом, но не работает, если аргументы разделяются несколькими пробелами. Ссылка на правильное решение: stackoverflow.com/a/59131568/3926504
— Дилип Наннавер

13

Я взял ответ Джеффри Уитледжа и немного улучшил его.

Теперь он поддерживает как одинарные, так и двойные кавычки. Вы можете использовать кавычки в самих параметрах, используя другие типизированные кавычки.

Он также удаляет кавычки из аргументов, поскольку они не вносят вклад в информацию об аргументе.

    public static string[] SplitArguments(string commandLine)
    {
        var parmChars = commandLine.ToCharArray();
        var inSingleQuote = false;
        var inDoubleQuote = false;
        for (var index = 0; index < parmChars.Length; index++)
        {
            if (parmChars[index] == '"' && !inSingleQuote)
            {
                inDoubleQuote = !inDoubleQuote;
                parmChars[index] = '\n';
            }
            if (parmChars[index] == '\'' && !inDoubleQuote)
            {
                inSingleQuote = !inSingleQuote;
                parmChars[index] = '\n';
            }
            if (!inSingleQuote && !inDoubleQuote && parmChars[index] == ' ')
                parmChars[index] = '\n';
        }
        return (new string(parmChars)).Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
    }

7

Хорошее и чистое управляемое решение по Earwicker не в аргументах ручки , как это:

Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");

Он вернул 3 элемента:

"He whispered to her \"I
love
you\"."

Итак, вот исправление для поддержки "кавычки \" escape \ "цитаты":

public static IEnumerable<string> SplitCommandLine(string commandLine)
{
    bool inQuotes = false;
    bool isEscaping = false;

    return commandLine.Split(c => {
        if (c == '\\' && !isEscaping) { isEscaping = true; return false; }

        if (c == '\"' && !isEscaping)
            inQuotes = !inQuotes;

        isEscaping = false;

        return !inQuotes && Char.IsWhiteSpace(c)/*c == ' '*/;
        })
        .Select(arg => arg.Trim().TrimMatchingQuotes('\"').Replace("\\\"", "\""))
        .Where(arg => !string.IsNullOrEmpty(arg));
}

Протестировано с двумя дополнительными случаями:

Test("\"C:\\Program Files\"", "C:\\Program Files");
Test("\"He whispered to her \\\"I love you\\\".\"", "He whispered to her \"I love you\".");

Также отмечено , что принятый ответ на Atif Азиз , который использует CommandLineToArgvW также не удалось. Он вернул 4 элемента:

He whispered to her \ 
I 
love 
you". 

Надеюсь, это поможет кому-то, кто ищет такое решение в будущем.


3
Извините за некромантию, но это решение по-прежнему упускает такие вещи, как bla.exe aAAA"b\"ASDS\"c"dSADSDрезультат, в aAAAb"ASDS"cdSADSDкотором это решение будет выводиться aAAA"b"ASDS"c"dSADSD. Я мог бы подумать об изменении на TrimMatchingQuotesa Regex("(?<!\\\\)\\\"")и использовать его вот так .
— Scis

4

2
Полезно - но это даст вам только аргументы командной строки, отправленные текущему процессу. Требовалось получить строку [] из строки «таким же образом, как это сделал бы C #, если бы команды были указаны в командной строке». Думаю, мы могли бы использовать декомпилятор, чтобы посмотреть, как это реализовано в MS ...
— rohancragg

Как также обнаружил Джон Галлоуэй ( weblogs.asp.net/jgalloway/archive/2006/09/13/… ), декомпилятор мало помогает, что возвращает нас к ответу Атифа ( stackoverflow.com/questions/298830/… )
— rohancragg

4

Я как итераторы, и в настоящее время LINQ делает IEnumerable<String>так же легко использовать в качестве массивов строки, поэтому мое взятие следуя духу ответа Jeffrey L Whitledge в (как метод расширения до string):

public static IEnumerable<string> ParseArguments(this string commandLine)
{
    if (string.IsNullOrWhiteSpace(commandLine))
        yield break;

    var sb = new StringBuilder();
    bool inQuote = false;
    foreach (char c in commandLine) {
        if (c == '"' && !inQuote) {
            inQuote = true;
            continue;
        }

        if (c != '"' && !(char.IsWhiteSpace(c) && !inQuote)) {
            sb.Append(c);
            continue;
        }

        if (sb.Length > 0) {
            var result = sb.ToString();
            sb.Clear();
            inQuote = false;
            yield return result;
        }
    }

    if (sb.Length > 0)
        yield return sb.ToString();
}

3

В своем вопросе вы запросили регулярное выражение, и я большой поклонник и пользователь его, поэтому, когда мне нужно было сделать тот же аргумент, что и вам, я написал свое собственное регулярное выражение после поиска в Google и не нашел простого решения. Мне нравятся короткие решения, поэтому я сделал одно, и вот оно:

            var re = @"\G(""((""""|[^""])+)""|(\S+)) *";
            var ms = Regex.Matches(CmdLine, re);
            var list = ms.Cast<Match>()
                         .Select(m => Regex.Replace(
                             m.Groups[2].Success
                                 ? m.Groups[2].Value
                                 : m.Groups[4].Value, @"""""", @"""")).ToArray();

Он обрабатывает пробелы и кавычки внутри кавычек и преобразует заключенные в "" в ". Не стесняйтесь использовать код!


3

Черт возьми. Это все ... Эх. Но это законно официально. От Microsoft на C # для .NET Core, возможно, только Windows, возможно, кросс-платформенный, но с лицензией MIT.

Выберите лакомые кусочки, объявления методов и заметные комментарии;

internal static unsafe string[] InternalCreateCommandLine(bool includeArg0)
private static unsafe int SegmentCommandLine(char * pCmdLine, string[] argArray, bool includeArg0)
private static unsafe int ScanArgument0(ref char* psrc, char[] arg)
private static unsafe int ScanArgument(ref char* psrc, ref bool inquote, char[] arg)

-

// First, parse the program name (argv[0]). Argv[0] is parsed under special rules. Anything up to 
// the first whitespace outside a quoted subtring is accepted. Backslashes are treated as normal 
// characters.

-

// Rules: 2N backslashes + " ==> N backslashes and begin/end quote
//      2N+1 backslashes + " ==> N backslashes + literal "
//         N backslashes     ==> N backslashes

Это код, перенесенный на .NET Core из .NET Framework из того, что, как я предполагаю, является либо библиотекой MSVC C, либо CommandLineToArgvW.

Вот моя нерешительная попытка справиться с некоторыми махинациями с помощью регулярных выражений и игнорировать нулевой бит аргумента. Это немного волшебно.

private static readonly Regex RxWinArgs
  = new Regex("([^\\s\"]+\"|((?<=\\s|^)(?!\"\"(?!\"))\")+)(\"\"|.*?)*\"[^\\s\"]*|[^\\s]+",
    RegexOptions.Compiled
    | RegexOptions.Singleline
    | RegexOptions.ExplicitCapture
    | RegexOptions.CultureInvariant);

internal static IEnumerable<string> ParseArgumentsWindows(string args) {
  var match = RxWinArgs.Match(args);

  while (match.Success) {
    yield return match.Value;
    match = match.NextMatch();
  }
}

Немного протестировал на дурацком сгенерированном выводе. Его результат соответствует значительному проценту того, что набрали и прошли обезьяны CommandLineToArgvW.



1
Да, похоже, версия C # мертва. github.com/dotnet/runtime/blob/master/src/coreclr/src/utilcode/…
— TylerY86

1
Ограниченное время возрождения. pastebin.com/ajhrBS4t
— TylerY86

2

Эта статья в The Code Project я использовал в прошлом. Это неплохой код, но он может сработать.

Эта статья MSDN - единственное, что я смог найти, объясняющее, как C # разбирает аргументы командной строки.


Я пробовал использовать рефлектор в библиотеке C #, но он переходит к собственному вызову C ++, для которого у меня нет кода, и я не вижу способа вызвать его без p-вызова. Мне также не нужна библиотека синтаксического анализа командной строки, мне просто нужна строка [].
— Антон

Отражение .NET тоже ни к чему не привело. Изучив исходный код Mono, можно предположить, что это разделение аргументов не выполняется CLR, а уже поступает из операционной системы. Подумайте о параметрах argc, argv основной функции C. Так что нет ничего, что можно было бы использовать, кроме API ОС.
— ygoe

2

Поскольку мне нужно было такое же поведение, как у OP (разделить строку точно так же, как это делают Windows cmd), я написал несколько тестовых примеров и протестировал опубликованные здесь ответы:

    Test( 0, m, "One",                    new[] { "One" });
    Test( 1, m, "One ",                   new[] { "One" });
    Test( 2, m, " One",                   new[] { "One" });
    Test( 3, m, " One ",                  new[] { "One" });
    Test( 4, m, "One Two",                new[] { "One", "Two" });
    Test( 5, m, "One  Two",               new[] { "One", "Two" });
    Test( 6, m, "One   Two",              new[] { "One", "Two" });
    Test( 7, m, "\"One Two\"",            new[] { "One Two" });
    Test( 8, m, "One \"Two Three\"",      new[] { "One", "Two Three" });
    Test( 9, m, "One \"Two Three\" Four", new[] { "One", "Two Three", "Four" });
    Test(10, m, "One=\"Two Three\" Four", new[] { "One=Two Three", "Four" });
    Test(11, m, "One\"Two Three\" Four",  new[] { "OneTwo Three", "Four" });
    Test(12, m, "One\"Two Three   Four",  new[] { "OneTwo Three   Four" });
    Test(13, m, "\"One Two\"",            new[] { "One Two" });
    Test(14, m, "One\" \"Two",            new[] { "One Two" });
    Test(15, m, "\"One\"  \"Two\"",       new[] { "One", "Two" });
    Test(16, m, "One\\\"  Two",           new[] { "One\"", "Two" });
    Test(17, m, "\\\"One\\\"  Two",       new[] { "\"One\"", "Two" });
    Test(18, m, "One\"",                  new[] { "One" });
    Test(19, m, "\"One",                  new[] { "One" });
    Test(20, m, "One \"\"",               new[] { "One", "" });
    Test(21, m, "One \"",                 new[] { "One", "" });
    Test(22, m, "1 A=\"B C\"=D 2",        new[] { "1", "A=B C=D", "2" });
    Test(23, m, "1 A=\"B \\\" C\"=D 2",   new[] { "1", "A=B \" C=D", "2" });
    Test(24, m, "1 \\A 2",                new[] { "1", "\\A", "2" });
    Test(25, m, "1 \\\" 2",               new[] { "1", "\"", "2" });
    Test(26, m, "1 \\\\\" 2",             new[] { "1", "\\\"", "2" });
    Test(27, m, "\"",                     new[] { "" });
    Test(28, m, "\\\"",                   new[] { "\"" });
    Test(29, m, "'A B'",                  new[] { "'A", "B'" });
    Test(30, m, "^",                      new[] { "^" });
    Test(31, m, "^A",                     new[] { "A" });
    Test(32, m, "^^",                     new[] { "^" });
    Test(33, m, "\\^^",                   new[] { "\\^" });
    Test(34, m, "^\\\\", new[] { "\\\\" });
    Test(35, m, "^\"A B\"", new[] { "A B" });

    // Test cases Anton

    Test(36, m, @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo", new[] { @"/src:C:\tmp\Some Folder\Sub Folder", @"/users:abcdefg@hijkl.com", @"tasks:SomeTask,Some Other Task", @"-someParam", @"foo" });

    // Test cases Daniel Earwicker 

    Test(37, m, "", new string[] { });
    Test(38, m, "a", new[] { "a" });
    Test(39, m, " abc ", new[] { "abc" });
    Test(40, m, "a b ", new[] { "a", "b" });
    Test(41, m, "a b \"c d\"", new[] { "a", "b", "c d" });

    // Test cases Fabio Iotti 

    Test(42, m, "this is a test ", new[] { "this", "is", "a", "test" });
    Test(43, m, "this \"is a\" test", new[] { "this", "is a", "test" });

    // Test cases Kevin Thach

    Test(44, m, "\"C:\\Program Files\"", new[] { "C:\\Program Files" });
    Test(45, m, "\"He whispered to her \\\"I love you\\\".\"", new[] { "He whispered to her \"I love you\"." });

«ожидаемое» значение получено в результате непосредственного тестирования его с помощью cmd.exe на моем компьютере (Win10 x64) и простой программы печати:

static void Main(string[] args) => Console.Out.WriteLine($"Count := {args.Length}\n{string.Join("\n", args.Select((v,i) => $"[{i}] => '{v}'"))}");

Вот результаты:


Solution                      | Failed Tests
------------------------------|------------------------------------- 
Atif Aziz (749653)            | 2, 3, 10, 11, 12, 14, 16, 17, 18, 26, 28, 31, 32, 33, 34, 35, 36, 37, 39, 45
Jeffrey L Whitledge (298968)  | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Daniel Earwicker (298990)     | 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 45
Anton (299795)                | 12, 16, 17, 18, 19, 21, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
CS. (467313)                  | 12, 18, 19, 21, 27, 31, 32, 33, 34, 35
Vapour in the Alley (2132004) | 10, 11, 12, 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 45
Monoman (7774211)             | 14, 16, 17, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 45
Thomas Petersson (19091999)   | 2, 3, 10, 11, 12, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 39, 45
Fabio Iotti (19725880)        | 1, 2, 3, 7, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 22, 23, 25, 26, 28, 29, 30, 35, 36, 37, 39, 40, 42, 44, 45
ygoe (23961658)               | 26, 31, 32, 33, 34, 35
Kevin Thach (24829691)        | 10, 11, 12, 14, 18, 19, 20, 21, 22, 23, 26, 27, 31, 32, 33, 34, 35, 36
Lucas De Jesus (31621370)     | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45
HarryP (48008872)             | 24, 26, 31, 32, 33, 34, 35
TylerY86 (53290784)           | 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 41, 43, 44, 45
Louis Somers (55903304)       | 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 39, 41, 43, 44, 45
user2126375 (58233585)        | 5, 6, 15, 16, 17, 31, 32, 33, 34, 35
DilipNannaware (59131568)     | 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 43, 44, 45
Mikescher (this)              | -

Поскольку ответ не казался правильным (по крайней мере, на основании моего варианта использования), вот мое решение, в настоящее время оно проходит все тестовые примеры (но если у кого-то есть дополнительные (неудачные) угловые случаи, прокомментируйте):

public static IEnumerable<string> SplitArgs(string commandLine)
{
    var result = new StringBuilder();

    var quoted = false;
    var escaped = false;
    var started = false;
    var allowcaret = false;
    for (int i = 0; i < commandLine.Length; i++)
    {
        var chr = commandLine[i];

        if (chr == '^' && !quoted)
        {
            if (allowcaret)
            {
                result.Append(chr);
                started = true;
                escaped = false;
                allowcaret = false;
            }
            else if (i + 1 < commandLine.Length && commandLine[i + 1] == '^')
            {
                allowcaret = true;
            }
            else if (i + 1 == commandLine.Length)
            {
                result.Append(chr);
                started = true;
                escaped = false;
            }
        }
        else if (escaped)
        {
            result.Append(chr);
            started = true;
            escaped = false;
        }
        else if (chr == '"')
        {
            quoted = !quoted;
            started = true;
        }
        else if (chr == '\\' && i + 1 < commandLine.Length && commandLine[i + 1] == '"')
        {
            escaped = true;
        }
        else if (chr == ' ' && !quoted)
        {
            if (started) yield return result.ToString();
            result.Clear();
            started = false;
        }
        else
        {
            result.Append(chr);
            started = true;
        }
    }

    if (started) yield return result.ToString();
}

Код, который я использовал для создания результатов теста, можно найти здесь


1

Чисто управляемое решение могло бы быть полезным. Слишком много "проблемных" комментариев к функции WINAPI, и она недоступна на других платформах. Вот мой код, который имеет четко определенное поведение (которое вы можете изменить, если хотите).

Он должен делать то же самое, что и .NET / Windows при предоставлении этого string[] argsпараметра, и я сравнил его с рядом «интересных» значений.

Это классическая реализация конечного автомата, которая берет каждый отдельный символ из входной строки и интерпретирует его для текущего состояния, создавая вывод и новое состояние. Состояние определяется в переменных escape, inQuote, hadQuoteи prevCh, а выход собирается в currentArgиargs .

Некоторые особенности, которые я обнаружил в ходе экспериментов в реальной командной строке (Windows 7): \\производит \, \"производит ", в ""пределах указанного диапазона производит ".

^Характер , кажется волшебным, тоже: он всегда исчезает , когда не удваивая ее. В противном случае это не повлияет на реальную командную строку. Моя реализация не поддерживает это, поскольку я не нашел закономерности в этом поведении. Может, кто-нибудь знает об этом побольше.

Что-то, что не вписывается в этот шаблон, - это следующая команда:

cmd /c "argdump.exe "a b c""

cmdКоманда , кажется, поймать внешние кавычки и взять остальное дословно. В этом должен быть какой-то особый волшебный соус.

Я не тестировал свой метод, но считаю его достаточно быстрым. Он не использует Regexи не выполняет конкатенацию строк, а вместо этого использует a StringBuilderдля сбора символов для аргумента и помещает их в список.

/// <summary>
/// Reads command line arguments from a single string.
/// </summary>
/// <param name="argsString">The string that contains the entire command line.</param>
/// <returns>An array of the parsed arguments.</returns>
public string[] ReadArgs(string argsString)
{
    // Collects the split argument strings
    List<string> args = new List<string>();
    // Builds the current argument
    var currentArg = new StringBuilder();
    // Indicates whether the last character was a backslash escape character
    bool escape = false;
    // Indicates whether we're in a quoted range
    bool inQuote = false;
    // Indicates whether there were quotes in the current arguments
    bool hadQuote = false;
    // Remembers the previous character
    char prevCh = '\0';
    // Iterate all characters from the input string
    for (int i = 0; i < argsString.Length; i++)
    {
        char ch = argsString[i];
        if (ch == '\\' && !escape)
        {
            // Beginning of a backslash-escape sequence
            escape = true;
        }
        else if (ch == '\\' && escape)
        {
            // Double backslash, keep one
            currentArg.Append(ch);
            escape = false;
        }
        else if (ch == '"' && !escape)
        {
            // Toggle quoted range
            inQuote = !inQuote;
            hadQuote = true;
            if (inQuote && prevCh == '"')
            {
                // Doubled quote within a quoted range is like escaping
                currentArg.Append(ch);
            }
        }
        else if (ch == '"' && escape)
        {
            // Backslash-escaped quote, keep it
            currentArg.Append(ch);
            escape = false;
        }
        else if (char.IsWhiteSpace(ch) && !inQuote)
        {
            if (escape)
            {
                // Add pending escape char
                currentArg.Append('\\');
                escape = false;
            }
            // Accept empty arguments only if they are quoted
            if (currentArg.Length > 0 || hadQuote)
            {
                args.Add(currentArg.ToString());
            }
            // Reset for next argument
            currentArg.Clear();
            hadQuote = false;
        }
        else
        {
            if (escape)
            {
                // Add pending escape char
                currentArg.Append('\\');
                escape = false;
            }
            // Copy character from input, no special meaning
            currentArg.Append(ch);
        }
        prevCh = ch;
    }
    // Save last argument
    if (currentArg.Length > 0 || hadQuote)
    {
        args.Add(currentArg.ToString());
    }
    return args.ToArray();
}

1

Использование:

public static string[] SplitArguments(string args) {
    char[] parmChars = args.ToCharArray();
    bool inSingleQuote = false;
    bool inDoubleQuote = false;
    bool escaped = false;
    bool lastSplitted = false;
    bool justSplitted = false;
    bool lastQuoted = false;
    bool justQuoted = false;

    int i, j;

    for(i=0, j=0; i<parmChars.Length; i++, j++) {
        parmChars[j] = parmChars[i];

        if(!escaped) {
            if(parmChars[i] == '^') {
                escaped = true;
                j--;
            } else if(parmChars[i] == '"' && !inSingleQuote) {
                inDoubleQuote = !inDoubleQuote;
                parmChars[j] = '\n';
                justSplitted = true;
                justQuoted = true;
            } else if(parmChars[i] == '\'' && !inDoubleQuote) {
                inSingleQuote = !inSingleQuote;
                parmChars[j] = '\n';
                justSplitted = true;
                justQuoted = true;
            } else if(!inSingleQuote && !inDoubleQuote && parmChars[i] == ' ') {
                parmChars[j] = '\n';
                justSplitted = true;
            }

            if(justSplitted && lastSplitted && (!lastQuoted || !justQuoted))
                j--;

            lastSplitted = justSplitted;
            justSplitted = false;

            lastQuoted = justQuoted;
            justQuoted = false;
        } else {
            escaped = false;
        }
    }

    if(lastQuoted)
        j--;

    return (new string(parmChars, 0, j)).Split(new[] { '\n' });
}

Основываясь на ответе Vapor in the Alley , этот также поддерживает ^ побеги.

Примеры:

  • это проверка
    • этот
    • является
    • а
    • контрольная работа
  • это проверка
    • этот
    • это
    • контрольная работа
  • это ^ "это ^" тест
    • этот
    • "является
    • а "
    • контрольная работа
  • это "" "проверка ^^"
    • этот
    • Взаимодействие с другими людьми
    • это ^ тест

Он также поддерживает несколько пробелов (разбивает аргументы только один раз на блок пробелов).


Последний из трех каким-то образом мешает Markdown и не отображается должным образом.
— Питер Мортенсен

Исправлено с пробелом нулевой ширины.
— Фабио Йотти

0

В настоящее время это код, который у меня есть:

    private String[] SplitCommandLineArgument(String argumentString)
    {
        StringBuilder translatedArguments = new StringBuilder(argumentString);
        bool escaped = false;
        for (int i = 0; i < translatedArguments.Length; i++)
        {
            if (translatedArguments[i] == '"')
            {
                escaped = !escaped;
            }
            if (translatedArguments[i] == ' ' && !escaped)
            {
                translatedArguments[i] = '\n';
            }
        }

        string[] toReturn = translatedArguments.ToString().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
        for(int i = 0; i < toReturn.Length; i++)
        {
            toReturn[i] = RemoveMatchingQuotes(toReturn[i]);
        }
        return toReturn;
    }

    public static string RemoveMatchingQuotes(string stringToTrim)
    {
        int firstQuoteIndex = stringToTrim.IndexOf('"');
        int lastQuoteIndex = stringToTrim.LastIndexOf('"');
        while (firstQuoteIndex != lastQuoteIndex)
        {
            stringToTrim = stringToTrim.Remove(firstQuoteIndex, 1);
            stringToTrim = stringToTrim.Remove(lastQuoteIndex - 1, 1); //-1 because we've shifted the indicies left by one
            firstQuoteIndex = stringToTrim.IndexOf('"');
            lastQuoteIndex = stringToTrim.LastIndexOf('"');
        }
        return stringToTrim;
    }

Он не работает с экранированными кавычками, но работает в тех случаях, с которыми я сталкивался до сих пор.


0

Это ответ на код Антона, который не работает с экранированными кавычками. Я переделал 3 места.

  1. Конструктор для StringBuilder в SplitCommandLineArguments , заменяя любой \» с \ г
  2. В цикле for в SplitCommandLineArguments я теперь заменяю символ \ r на \ " .
  3. Изменены SplitCommandLineArgument методы от частного к общественному статики .

public static string[] SplitCommandLineArgument( String argumentString )
{
    StringBuilder translatedArguments = new StringBuilder( argumentString ).Replace( "\\\"", "\r" );
    bool InsideQuote = false;
    for ( int i = 0; i < translatedArguments.Length; i++ )
    {
        if ( translatedArguments[i] == '"' )
        {
            InsideQuote = !InsideQuote;
        }
        if ( translatedArguments[i] == ' ' && !InsideQuote )
        {
            translatedArguments[i] = '\n';
        }
    }

    string[] toReturn = translatedArguments.ToString().Split( new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries );
    for ( int i = 0; i < toReturn.Length; i++ )
    {
        toReturn[i] = RemoveMatchingQuotes( toReturn[i] );
        toReturn[i] = toReturn[i].Replace( "\r", "\"" );
    }
    return toReturn;
}

public static string RemoveMatchingQuotes( string stringToTrim )
{
    int firstQuoteIndex = stringToTrim.IndexOf( '"' );
    int lastQuoteIndex = stringToTrim.LastIndexOf( '"' );
    while ( firstQuoteIndex != lastQuoteIndex )
    {
        stringToTrim = stringToTrim.Remove( firstQuoteIndex, 1 );
        stringToTrim = stringToTrim.Remove( lastQuoteIndex - 1, 1 ); //-1 because we've shifted the indicies left by one
        firstQuoteIndex = stringToTrim.IndexOf( '"' );
        lastQuoteIndex = stringToTrim.LastIndexOf( '"' );
    }
    return stringToTrim;
}

Я занимаюсь той же проблемой, вы могли подумать, что в наши дни существует простое решение для строк аргументов командной строки модульного тестирования. Все, в чем я хочу быть уверен, - это поведение, которое будет результатом заданной строки аргумента командной строки. Я пока откажусь и создам модульные тесты для string [], но могу добавить несколько интеграционных тестов, чтобы скрыть это.
— Чарли Баркер

0

Я не думаю, что для приложений C # существуют одинарные кавычки или кавычки ^. У меня нормально работает следующая функция:

public static IEnumerable<String> SplitArguments(string commandLine)
{
    Char quoteChar = '"';
    Char escapeChar = '\\';
    Boolean insideQuote = false;
    Boolean insideEscape = false;

    StringBuilder currentArg = new StringBuilder();

    // needed to keep "" as argument but drop whitespaces between arguments
    Int32 currentArgCharCount = 0;                  

    for (Int32 i = 0; i < commandLine.Length; i++)
    {
        Char c = commandLine[i];
        if (c == quoteChar)
        {
            currentArgCharCount++;

            if (insideEscape)
            {
                currentArg.Append(c);       // found \" -> add " to arg
                insideEscape = false;
            }
            else if (insideQuote)
            {
                insideQuote = false;        // quote ended
            }
            else
            {
                insideQuote = true;         // quote started
            }
        }
        else if (c == escapeChar)
        {
            currentArgCharCount++;

            if (insideEscape)   // found \\ -> add \\ (only \" will be ")
                currentArg.Append(escapeChar + escapeChar);       

            insideEscape = !insideEscape;
        }
        else if (Char.IsWhiteSpace(c))
        {
            if (insideQuote)
            {
                currentArgCharCount++;
                currentArg.Append(c);       // append whitespace inside quote
            }
            else
            {
                if (currentArgCharCount > 0)
                    yield return currentArg.ToString();

                currentArgCharCount = 0;
                currentArg.Clear();
            }
        }
        else
        {
            currentArgCharCount++;
            if (insideEscape)
            {
                // found non-escaping backslash -> add \ (only \" will be ")
                currentArg.Append(escapeChar);                       
                currentArgCharCount = 0;
                insideEscape = false;
            }
            currentArg.Append(c);
        }
    }

    if (currentArgCharCount > 0)
        yield return currentArg.ToString();
}

0

Вы можете взглянуть на код, который я опубликовал вчера:

[C #] Строки пути и аргументов

Он разбивает имя файла + аргументы на строку []. Обрабатываются короткие пути, переменные среды и отсутствующие расширения файлов.

(Первоначально это было для UninstallString в реестре.)


0

Попробуйте этот код:

    string[] str_para_linha_comando(string str, out int argumentos)
    {
        string[] linhaComando = new string[32];
        bool entre_aspas = false;
        int posicao_ponteiro = 0;
        int argc = 0;
        int inicio = 0;
        int fim = 0;
        string sub;

        for(int i = 0; i < str.Length;)
        {
            if (entre_aspas)
            {
                // Está entre aspas
                sub = str.Substring(inicio+1, fim - (inicio+1));
                linhaComando[argc - 1] = sub;

                posicao_ponteiro += ((fim - posicao_ponteiro)+1);
                entre_aspas = false;
                i = posicao_ponteiro;
            }
            else
            {
            tratar_aspas:
                if (str.ElementAt(i) == '\"')
                {
                    inicio = i;
                    fim = str.IndexOf('\"', inicio + 1);
                    entre_aspas = true;
                    argc++;
                }
                else
                {
                    // Se não for aspas, então ler até achar o primeiro espaço em branco
                    if (str.ElementAt(i) == ' ')
                    {
                        if (str.ElementAt(i + 1) == '\"')
                        {
                            i++;
                            goto tratar_aspas;
                        }

                        // Pular os espaços em branco adiconais
                        while(str.ElementAt(i) == ' ') i++;

                        argc++;
                        inicio = i;
                        fim = str.IndexOf(' ', inicio);
                        if (fim == -1) fim = str.Length;
                        sub = str.Substring(inicio, fim - inicio);
                        linhaComando[argc - 1] = sub;
                        posicao_ponteiro += (fim - posicao_ponteiro);

                        i = posicao_ponteiro;
                        if (posicao_ponteiro == str.Length) break;
                    }
                    else
                    {
                        argc++;
                        inicio = i;
                        fim = str.IndexOf(' ', inicio);
                        if (fim == -1) fim = str.Length;

                        sub = str.Substring(inicio, fim - inicio);
                        linhaComando[argc - 1] = sub;
                        posicao_ponteiro += fim - posicao_ponteiro;
                        i = posicao_ponteiro;
                        if (posicao_ponteiro == str.Length) break;
                    }
                }
            }
        }

        argumentos = argc;

        return linhaComando;
    }

Написано на португальском.


документация скорее португальская
— Enamul Hassan

@EnamulHassan Я бы сказал, что код также на португальском языке, например posicao_ponteiro += ((fim - posicao_ponteiro)+1);.
— MEMark

0

Вот один лайнер, который выполняет свою работу (см. Одну строку, которая выполняет всю работу внутри метода BurstCmdLineArgs (...)).

Не то, что я бы назвал самой читаемой строкой кода, но вы можете разбить ее для удобства чтения. Это просто намеренно и не работает для всех случаев аргументов (например, аргументов имени файла, которые содержат разделитель символов разделенной строки).

Это решение хорошо зарекомендовало себя в моих решениях, в которых оно используется. Как я уже сказал, он выполняет свою работу без лишнего кода для обработки всех возможных аргументов в формате n-факториал.

using System;
using System.Collections.Generic;
using System.Linq;

namespace CmdArgProcessor
{
    class Program
    {
        static void Main(string[] args)
        {
            // test switches and switches with values
            // -test1 1 -test2 2 -test3 -test4 -test5 5

            string dummyString = string.Empty;

            var argDict = BurstCmdLineArgs(args);

            Console.WriteLine("Value for switch = -test1: {0}", argDict["test1"]);
            Console.WriteLine("Value for switch = -test2: {0}", argDict["test2"]);
            Console.WriteLine("Switch -test3 is present? {0}", argDict.TryGetValue("test3", out dummyString));
            Console.WriteLine("Switch -test4 is present? {0}", argDict.TryGetValue("test4", out dummyString));
            Console.WriteLine("Value for switch = -test5: {0}", argDict["test5"]);

            // Console output:
            //
            // Value for switch = -test1: 1
            // Value for switch = -test2: 2
            // Switch -test3 is present? True
            // Switch -test4 is present? True
            // Value for switch = -test5: 5
        }

        public static Dictionary<string, string> BurstCmdLineArgs(string[] args)
        {
            var argDict = new Dictionary<string, string>();

            // Flatten the args in to a single string separated by a space.
            // Then split the args on the dash delimiter of a cmd line "switch".
            // E.g. -mySwitch myValue
            //  or -JustMySwitch (no value)
            //  where: all values must follow a switch.
            // Then loop through each string returned by the split operation.
            // If the string can be split again by a space character,
            // then the second string is a value to be paired with a switch,
            // otherwise, only the switch is added as a key with an empty string as the value.
            // Use dictionary indexer to retrieve values for cmd line switches.
            // Use Dictionary::ContainsKey(...) where only a switch is recorded as the key.
            string.Join(" ", args).Split('-').ToList().ForEach(s => argDict.Add(s.Split()[0], (s.Split().Count() > 1 ? s.Split()[1] : "")));

            return argDict;
        }
    }
}

0

Не могу найти здесь ничего, что мне понравилось. Ненавижу портить стек магией yield для небольшой командной строки (если бы это был поток размером в терабайт, это была бы другая история).

Вот мой вывод, он поддерживает экранирование кавычек с помощью таких двойных кавычек:

param = "15" "экран неплохой" param2 = '15 "экран неплохой' param3 =" "param4 = / param5

результат:

param = "15-дюймовый экран неплохо"

param2 = '15-дюймовый экран неплохо'

param3 = ""

param4 =

/ param5

public static string[] SplitArguments(string commandLine)
{
    List<string> args         = new List<string>();
    List<char>   currentArg   = new List<char>();
    char?        quoteSection = null; // Keeps track of a quoted section (and the type of quote that was used to open it)
    char[]       quoteChars   = new[] {'\'', '\"'};
    char         previous     = ' '; // Used for escaping double quotes

    for (var index = 0; index < commandLine.Length; index++)
    {
        char c = commandLine[index];
        if (quoteChars.Contains(c))
        {
            if (previous == c) // Escape sequence detected
            {
                previous = ' '; // Prevent re-escaping
                if (!quoteSection.HasValue)
                {
                    quoteSection = c; // oops, we ended the quoted section prematurely
                    continue;         // don't add the 2nd quote (un-escape)
                }

                if (quoteSection.Value == c)
                    quoteSection = null; // appears to be an empty string (not an escape sequence)
            }
            else if (quoteSection.HasValue)
            {
                if (quoteSection == c)
                    quoteSection = null; // End quoted section
            }
            else
                quoteSection = c; // Start quoted section
        }
        else if (char.IsWhiteSpace(c))
        {
            if (!quoteSection.HasValue)
            {
                args.Add(new string(currentArg.ToArray()));
                currentArg.Clear();
                previous = c;
                continue;
            }
        }

        currentArg.Add(c);
        previous = c;
    }

    if (currentArg.Count > 0)
        args.Add(new string(currentArg.ToArray()));

    return args.ToArray();
}

0

Я реализовал конечный автомат так, чтобы результаты синтаксического анализа были такими же, как если бы аргументы передавались в приложение .NET и обрабатывались static void Main(string[] args)методом.

    public static IList<string> ParseCommandLineArgsString(string commandLineArgsString)
    {
        List<string> args = new List<string>();

        commandLineArgsString = commandLineArgsString.Trim();
        if (commandLineArgsString.Length == 0)
            return args;

        int index = 0;
        while (index != commandLineArgsString.Length)
        {
            args.Add(ReadOneArgFromCommandLineArgsString(commandLineArgsString, ref index));
        }

        return args;
    }

    private static string ReadOneArgFromCommandLineArgsString(string line, ref int index)
    {
        if (index >= line.Length)
            return string.Empty;

        var sb = new StringBuilder(512);
        int state = 0;
        while (true)
        {
            char c = line[index];
            index++;
            switch (state)
            {
                case 0: //string outside quotation marks
                    if (c == '\\') //possible escaping character for quotation mark otherwise normal character
                    {
                        state = 1;
                    }
                    else if (c == '"') //opening quotation mark for string between quotation marks
                    {
                        state = 2;
                    }
                    else if (c == ' ') //closing arg
                    {
                        return sb.ToString();
                    }
                    else
                    {
                        sb.Append(c);
                    }

                    break;
                case 1: //possible escaping \ for quotation mark or normal character
                    if (c == '"') //If escaping quotation mark only quotation mark is added into result
                    {
                        state = 0;
                        sb.Append(c);
                    }
                    else // \ works as not-special character
                    {
                        state = 0;
                        sb.Append('\\');
                        index--;
                    }

                    break;
                case 2: //string between quotation marks
                    if (c == '"') //quotation mark in string between quotation marks can be escape mark for following quotation mark or can be ending quotation mark for string between quotation marks
                    {
                        state = 3;
                    }
                    else if (c == '\\') //escaping \ for possible following quotation mark otherwise normal character
                    {
                        state = 4;
                    }
                    else //text in quotation marks
                    {
                        sb.Append(c);
                    }

                    break;
                case 3: //quotation mark in string between quotation marks
                    if (c == '"') //Quotation mark after quotation mark - that means that this one is escaped and can added into result and we will stay in string between quotation marks state
                    {
                        state = 2;
                        sb.Append(c);
                    }
                    else //we had two consecutive quotation marks - this means empty string but the following chars (until space) will be part of same arg result as well
                    {
                        state = 0;
                        index--;
                    }

                    break;
                case 4: //possible escaping \ for quotation mark or normal character in string between quotation marks
                    if (c == '"') //If escaping quotation mark only quotation mark added into result
                    {
                        state = 2;
                        sb.Append(c);
                    }
                    else
                    {
                        state = 2;
                        sb.Append('\\');
                        index--;
                    }

                    break;
            }

            if (index == line.Length)
                return sb.ToString();
        }
    }

0

Вот решение, которое обрабатывает пробелы (одиночные или множественные пробелы) как разделитель параметров командной строки и возвращает реальные аргументы командной строки:

static string[] ParseMultiSpacedArguments(string commandLine)
{
    var isLastCharSpace = false;
    char[] parmChars = commandLine.ToCharArray();
    bool inQuote = false;
    for (int index = 0; index < parmChars.Length; index++)
    {
        if (parmChars[index] == '"')
            inQuote = !inQuote;
        if (!inQuote && parmChars[index] == ' ' && !isLastCharSpace)
            parmChars[index] = '\n';

        isLastCharSpace = parmChars[index] == '\n' || parmChars[index] == ' ';
    }

    return (new string(parmChars)).Split('\n');
}

0

Есть пакет NuGet, который содержит именно те функции, которые вам нужны:

Microsoft.CodeAnalysis.Common содержит класс CommandLineParser с методом SplitCommandLineIntoArguments .

Вы используете это так:

using Microsoft.CodeAnalysis;
// [...]
var cli = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""abcdefg@hijkl.com"" tasks:""SomeTask,Some Other Task"" -someParam foo";
var cliArgs = CommandLineParser.SplitCommandLineIntoArguments(cli, true);

Console.WriteLine(string.Join('\n', cliArgs));
// prints out:
// /src:"C:\tmp\Some Folder\Sub Folder"
// /users:"abcdefg@hijkl.com"
// tasks:"SomeTask,Some Other Task"
// -someParam
// foo

-2

Я не уверен, понял ли я вас, но проблема в том, что символ, используемый в качестве разделителя, также находится внутри текста? (За исключением этого экранирования двойным "?)

Если так, я бы создал for цикл и заменил все экземпляры, где присутствует <">, на <|> (или другой« безопасный »символ, но убедитесь, что он заменяет только <">, а не <"">

После итерации строки я бы сделал, как было сказано ранее, разделил строку, но теперь по символу <|>.


Двойные "" являются строковым литералом a @ "..". Двойные "внутри строки @" .. "эквивалентны \ escaped" в обычной строке
— Антон

"единственное ограничение (я верю) состоит в том, что строки разделены пробелами, если только пробел не находится в блоке" ... "-> Может быть, стрелять в птицу из базуки, но поставить логическое значение" истина " если внутри цитаты, и если пробел обнаружен внутри, пока "истина", продолжить, иначе <> = <|>
— Исрар Хан

-6

Да, строковый объект имеет встроенную функцию, вызываемую, Split()которая принимает единственный параметр, определяющий символ, который нужно искать, в качестве разделителя и возвращает массив строк (string []) с отдельными значениями в нем.


1
Это приведет к неправильному разделению части src: "C: \ tmp \ Some Folder \ Sub Folder".
— Антон

Как насчет кавычек внутри строки, которые временно отключают разделение на пробелы?
— Дэниел Эрвикер
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.