Разделить строку, содержащую параметры командной строки, на строку [] в 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.