Розділіть рядок, що містить параметри командного рядка, на рядок [] у 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"
};

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

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



5
Щоразу, коли хтось відповідає, ви, здається, маєте заперечення на основі матеріалів, яких немає у вашому дописі. Пропоную вам оновити свою публікацію цим матеріалом. Ви можете отримати кращі відповіді.
tvanfosson

1
Гарне запитання, шукаю те саме. Сподівався знайти когось, хто сказав би: "привіт. .Net виставляє це тут ..." :) Якщо я натраплю на це в якийсь момент, я розміщу його тут, хоча це приблизно 6 років. Все ще актуальне питання!
MikeJansen

Я створив чисто керовану версію у відповіді нижче, оскільки мені також потрібна була ця функція.
ygoe

Відповіді:


75

На додаток до благу і чистого керованого рішення по Earwicker , може бути варто згадати, для повноти картини , що Windows , також забезпечує CommandLineToArgvWфункцію розбиваючи рядок в масив рядків:

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

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

Приклад виклику цього API з C # та розпакування отриманого масиву рядків у керованому коді можна знайти за адресою: « Перетворення рядка командного рядка в аргументи [] за допомогою 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 \\", щоб це функціонувало для правильного аналізу рядка.
Magnus Lindhe

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

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

7
OMG це такий безлад. типовий суп для MS. ніщо не канонізоване, і ніколи KISS не поважається у світі MS.
v.oddou

1
Я опублікував крос-платформну версію перекладеної Microsoft реалізації MSVCRT та наближення з високою точністю, використовуючи Regex. Я знаю, що це старе, але привіт - жодного сувої тіла.
TylerY86

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

Це може дати деякі порожні рядки залежно від ситуації, але, можливо, ця інформація буде корисна в інших випадках, тому я не видаляю порожні записи в цій функції.

По-друге (і більш повсякденно) маленький помічник, який обріже відповідну пару лапок з початку та кінця рядка. Це більш метушливий спосіб, ніж стандартний метод обрізання - він обрізає лише один символ з кожного кінця і не буде обрізаний лише з одного кінця:

    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 підтримує \", щоб дозволити передавати символ лапки. Моя функція Split не може цього зробити.
Даніель Ервікер

1
Гарний вухар :) Антон: Це рішення, яке я намагався описати вам у своєму попередньому дописі, але Ервікер зробив набагато кращу роботу, записавши його;) А також значно розширив його;)
Ісрар Хан,

пробіл - це не єдиний розділовий символ для аргументів командного рядка, чи не так?
Louis Rhys,

@Louis Rhys - Я не впевнений. Якщо це викликає занепокоєння, це досить легко вирішити: використовувати char.IsWhiteSpaceзамість== ' '
Daniel Earwicker

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

Видалення порожніх рядків, видалення зовнішніх лапок та обробка екранованих лапок залишаються як надмір для читача.
Jeffrey L Whitledge

Char.IsWhiteSpace () міг би тут допомогти
Сем Макрілл

Це рішення добре, якщо аргументи розділяються одним пробілом, але не вдається - аргументи розділяються кількома пробілами. Посилання на правильне рішення: stackoverflow.com/a/59131568/3926504
Діліп Nannaware

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\"."

Отже, ось виправлення на підтримку "цитованої \" втечі \ "цитати":

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

Перевірено з 2 додатковими кейсами:

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

Як Jon Galloway також знайшов ( weblogs.asp.net/jgalloway/archive/2006/09/13 / ... ) декомпілятор не надто допомагає , яка повертає нас до відповіді Atıf ( в stackoverflow.com/questions/298830 / ... )
rohancragg

4

Мені подобаються ітератори, і в наш час 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();
}

3

У вашому запитанні ви попросили регулярний вираз, і я їх великий шанувальник і користувач, тому, коли мені потрібно було зробити такий самий аргумент, як і ви, я написав власний регулярний вираз після того, як погуглив і не знайшов простого рішення. Мені подобаються короткі рішення, тому я зробив одне і ось воно:

            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

Ця стаття Проекту Code - це те, що я використовував у минулому. Це непоганий код, але він може працювати.

Ця стаття MSDN - це єдине, що я міг знайти, що пояснює, як C # аналізує аргументи командного рядка.


Я спробував reflector'ing у бібліотеці C #, але він переходить до власного виклику C ++, для якого я не маю коду і не бачу жодного способу дзвінка без його виклику. Я також не хочу бібліотеки парсингу командного рядка, я просто хочу рядок [].
Антон

Відображення .NET також нікуди не привело. Дивлячись в Mono вихідний код запропонував , що цей аргумент розщеплення не робиться з допомогою CLR, а вже походить від операційної системи. Подумайте про параметри argc, argv основної функції C. Отже, немає нічого для повторного використання, крім OS API.
ygoe

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

Заснований на парі у відповіді Алеї , він також підтримує ^ втечі.

Приклади:

  • це перевірка
    • це
    • є
    • a
    • тест
  • це перевірка
    • це
    • є
    • тест
  • це ^ "це ^" тест
    • це
    • "
    • тест
  • це "" "є тестом ^^"
    • це
    • U
    • це ^ тест

Він також підтримує кілька пробілів (розбиває аргументи лише один раз на блок пробілів).


Останній із трьох якось заважає Markdown і не відображається за призначенням.
Пітер Мортенсен,

Виправлено пробіл нульової ширини.
Фабіо Іотті

1

Оскільки я хотів мати таку ж поведінку, як 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();
}

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


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

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

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

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


швидше документація є португальською
Енамул Хасан

@EnamulHassan Я б сказав, що код також на португальській мові, наприклад posicao_ponteiro += ((fim - posicao_ponteiro)+1);.
ПОВІДОМЛЕННЯ

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

Тут не вдалося знайти нічого, що мені сподобалось. Я ненавиджу псувати стек магією виходу для невеликого командного рядка (якби це був потік терабайт, це була б інша історія).

Ось мій прийом, він підтримує екранування цитат із подвійними лапками, як це:

param = "a 15" "screen is not bad" param2 = 'a 15 "screen is not''t bad' 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 цикл і замінив би всі екземпляри, де <"> присутній, <|> (або іншим" безпечним "символом, але переконайтесь, що він замінює лише <">, а не <"">

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


Подвійні символи "", оскільки це літерал рядка @ "..", подвійні "" всередині рядка @ ".." еквівалентні \ escapeted "у звичайному рядку
Антон

"єдиним обмеженням (я вважаю) є те, що рядки розмежовані космосом, якщо пробіл не виникає в межах" ... "блоку" -> Можливо, стріляти в птаха з базуки, але поставити логічне значення, яке відповідає "істина" коли всередині лапки, і якщо всередині виявлено пробіл, поки "true", продовжуйте, інакше <> = <|>
Ісрар Хан,

-6

Так, об’єкт рядка має вбудовану функцію, яка називається Split()єдиним параметром, що визначає символ, який потрібно шукати як роздільник, і повертає масив рядків (рядок []) з окремими значеннями в ньому.


1
Це призведе до неправильного розділення частини src: "C: \ tmp \ Some Folder \ Sub Folder".
Антон

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