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

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

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

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

string parameterString = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam foo";

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

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

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

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

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

tvanfosson 18.11.2008 17:27

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

MikeJansen 03.04.2014 17:46

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

ygoe 30.05.2014 23:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
93
4
91 803
26
Перейти к ответу Данный вопрос помечен как решенный

Ответы 26

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

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

Anton 18.11.2008 17:16

А как насчет кавычек внутри строки, которые временно отключают разделение на пробелы?

Daniel Earwicker 18.11.2008 17:16

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

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

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

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

Anton 18.11.2008 17:24

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

Israr Khan 18.11.2008 17:43

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

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

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

Anton 18.11.2008 17:27

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

ygoe 30.05.2014 23:20

Синтаксический анализатор командной строки 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');
    }

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

Anton 18.11.2008 18:05

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

rmeador 18.11.2008 18:16

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

Jeffrey L Whitledge 18.11.2008 19:54

Char.IsWhiteSpace () может здесь помочь

Sam Mackrill 15.03.2011 18:02

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

Dilip Nannaware 02.12.2019 04:30

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

    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:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam",
             @"/src:""C:\tmp\Some Folder\Sub Folder""", @"/users:""[email protected]""", @"tasks:""SomeTask,Some Other Task""", @"-someParam");

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

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

Anton 18.11.2008 19:23

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

Daniel Earwicker 18.11.2008 21:15

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

Israr Khan 19.11.2008 11:57

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

Louis Rhys 08.09.2010 08:31

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

Daniel Earwicker 08.09.2010 13:03

Я думаю, что Test ("a \" \ "", "a", "") завершится неудачно (т.е. невозможно передать пустую строку в качестве аргумента. Проверка на то, что строка пуста, должна выполняться до обрезки кавычек.

Thomas Materna 08.05.2015 04:17

Я думаю, вы ошиблись в логике Select и Where. Сначала вы удаляете все кавычки, а затем удаляете все пустые строки. Таким образом вы удалите все пустые строки, даже те, которые были указаны в кавычках. Должно быть наоборот: сначала проверьте пустые строки, чем удалите кавычки. Таким образом, пустые строки, явно заключенные в кавычки, останутся в конечном результате.

Martini Bianco 21.06.2018 12:02

Environment.GetCommandLineArgs ()

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

rohancragg 12.10.2011 12:35

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

rohancragg 12.10.2011 12:42

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

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

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

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

  1. конструктор для StringBuilder в SplitCommandLineArguments, заменяя любой \ " на
  2. В for-loop в SplitCommandLineArguments я теперь заменяю символ обратно на \ ".
  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 [], но могу добавить несколько интеграционных тестов, чтобы скрыть это.

Charlie Barker 20.10.2011 02:02
Ответ принят как подходящий

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

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

Parses a Unicode command line string and returns an array of pointers to the command line arguments, along with a count of such arguments, in a way that is similar to the standard C run-time argv and argc values.

Пример вызова этого 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);
    }
}

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

Magnus Lindhe 25.08.2009 17:35

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

Scott Wegner 15.08.2012 09:40

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

LRN 14.11.2012 14:20

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

v.oddou 02.07.2015 08:49

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

TylerY86 07.12.2019 04:55

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

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

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

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

Мне нравятся итераторы, и в настоящее время LINQ делает IEnumerable<String> таким же простым в использовании, как массивы строк, поэтому я следую духу Ответ Джеффри Л. Уитледжа (как метод расширения для 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();
}

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

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

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

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

В своем вопросе вы запросили регулярное выражение, и я большой поклонник и пользователь его, поэтому, когда мне нужно было сделать тот же аргумент, что и вам, я написал свое собственное регулярное выражение после поиска в 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();

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

Использовать:

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

Основываясь на ответе Пар в переулке, этот также поддерживает экранирование ^.

Примеры:

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

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

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

Peter Mortensen 16.09.2018 22:40

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

Fabio Iotti 17.09.2018 20:50

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

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

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

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

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

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

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

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

Я не тестировал свой метод, но считаю его достаточно быстрым. Он не использует Regex и не выполняет конкатенацию строк, а вместо этого использует 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();
}

хорошее и чистое управляемое решение от 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\".");

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

He whispered to her \ 
I 
love 
you". 

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

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

Scis 21.02.2016 17:06

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

    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 25.07.2015 03:07

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

MEMark 09.09.2018 09:13

Вот один лайнер, который выполняет свою работу (см. Одну строку, которая выполняет всю работу внутри метода 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;
        }
    }
}

Я не думаю, что для приложений 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();
}

О черт. Это все ... Эх. Но это законно официально. От 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.

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

TylerY86 08.07.2020 16:23

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

TylerY86 08.07.2020 16:26

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

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

param = "a 15"" screen isn't bad" param2='a 15" screen isn''t bad' param3 = "" param4= /param5

результат:

param = "a 15" screen isn't bad"

param2='a 15" screen isn't bad'

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

Я реализовал конечный автомат так, чтобы результаты синтаксического анализа были такими же, как если бы аргументы передавались в приложение .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();
        }
    }

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

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

Поскольку мне нужно было такое же поведение, как у 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:""[email protected]"" tasks:""SomeTask,Some Other Task"" -someParam foo", new[] { @"/src:C:\tmp\Some Folder\Sub Folder", @"/users:[email protected]", @"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();
}

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

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

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

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

using Microsoft.CodeAnalysis;
// [...]
var cli = @"/src:""C:\tmp\Some Folder\Sub Folder"" /users:""[email protected]"" 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:"[email protected]"
// tasks:"SomeTask,Some Other Task"
// -someParam
// foo

Я написал метод отделения имени файла от его аргументов для использования с ProcessStartInfo, который требует разделения имени файла и строки аргумента.

Например "C:\Users\Me\Something.exe" -a даст { "C:\Users\Me\Something.exe", "-a" } в результате

Код ниже:

    public static string[] SplitCommandFromArgs(string commandLine)
    {
        commandLine = commandLine.Trim();
        if (commandLine[0] == '"')
        {
            bool isEscaped = false;
            for (int c = 1; c < commandLine.Length; c++)
            {
                if (commandLine[c] == '"' && !isEscaped)
                {
                    return new string[] { commandLine.Substring(1, c - 1), commandLine.Substring(c + 1).Trim() };
                }
                isEscaped = commandLine[c] == '\\';
            }
        }
        else
        {
            for (int c = 1; c < commandLine.Length; c++) {
                if (commandLine[c] == ' ')
                {
                    return new string[] { commandLine.Substring(0, c), commandLine.Substring(c).Trim() };
                }
            }
        }
        return new string[] { commandLine, "" };
    }

Другие вопросы по теме