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

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

Я создаю приложение с использованием С#, которое может принимать аргументы командной строки. например

  1. Start -p:SomeNameValue -h
  2. DisplayMessage -m:Hello
  3. DisplayMessage -m:'Hello World'
  4. DisplayMessage -m:"Hello World"

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

  1. Start -p:SomeNameValue -h
  2. DisplayMessage -m:Hello
  3. DisplayMessage -m:'Hello World'
  4. DisplayMessage -m:"Hello World"

Ответы, которые я нашел здесь, кажется, ломаются. например Они удаляют символ : или вообще не работают. Некоторые из кодов, которые я пробовал, выглядят следующим образом:

var res1 = Regex.Matches(payload, @"[\""].+?[\""]|[^ ]+")
    .Cast<Match>()
    .Select(m => m.Value)
    .ToList();
var res2 = payload.Split('"')
    .Select((element, index) => index % 2 == 0  
        ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
        : new string[] { element })  // Keep the entire item
    .SelectMany(element => element).ToList();
var res3 = Regex
    .Matches(payload, @"\w+|""[\w\s]*""")
    .Cast<Match>()
    .Select(m => m.Groups["match"].Value)
    .ToList();
string[] res4 = Regex.Split(payload, ",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
Regex regex = new Regex(@"\w+|""[\w\s]*""");
var res5 = regex.Matches(payload).Cast<Match>().ToList();

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


person user2026382    schedule 03.03.2021    source источник
comment
Что вы пробовали? Похоже, вы могли бы легко сделать это в цикле, где вы отслеживаете открытие и закрытие кавычек.   -  person Rufus L    schedule 03.03.2021
comment
Пожалуйста, покажите нам, что вы пробовали, что не сработало для вас. Ребята из Stack Overflow рады помочь вам отладить ваш код, но не так рады написать его для вас.   -  person STLDev    schedule 03.03.2021
comment
Кстати, вам нужно решить, хотите ли вы рассматривать непревзойденные цитаты. Разобрать это не тривиальное упражнение, но @RufusL дал вам хороший совет: обработайте каждый символ и создайте небольшую конечную машину с флагом или двумя, чтобы увидеть, находитесь ли вы внутри чего-то цитируемого или в обычном режиме. Вам, вероятно, лучше делать это так, чем с регулярным выражением   -  person Flydog57    schedule 03.03.2021
comment
Вы проверили, делает ли это инфраструктура C# бесплатно? Я думаю, что если вы запустите приложение, такое как app.exe abc "xyz 123", ваша основная функция увидит два аргумента командной строки, один "abc", а другой "xyz 123"   -  person Flydog57    schedule 03.03.2021
comment
Вы уверены, что хотите разрешить одинарные кавычки заключать аргумент, когда им также разрешено находиться внутри строки в кавычках? Эта гибкость не добавляет ценности конечному пользователю, но значительно усложняет синтаксический анализ.   -  person Rufus L    schedule 03.03.2021
comment
Также см. ответы на этот вопрос для множества встроенных парсеров командной строки.   -  person Rufus L    schedule 03.03.2021
comment
@Rufus L Это очень хороший момент. Я отредактировал вопрос, чтобы удалить это требование.   -  person user2026382    schedule 03.03.2021


Ответы (2)


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

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {       
        string[] testStrings = new string[] {
            "Start -p:SomeNameValue -h",
            "DisplayMessage -m:Hello",
            "DisplayMessage -m:'Hello World'",
            "DisplayMessage -m:\"Hello World\"",
            "DisplayMessage -m:\"'Inside double quotes'\"",
            "DisplayMessage -m:'\"Inside single quotes\"'"              
        };

        foreach (string str in testStrings)
        {
            Console.WriteLine(str);
            string[] parsedStrings = ParseString(str);

            for (int i = 0; i < parsedStrings.Length; i++)
            {
                Console.WriteLine("    " + (i + 1) + ". " + parsedStrings[i]);              
            }
            Console.WriteLine();
        }
    }

    private static string[] ParseString(string str)
    {
        var retval = new List<string>();
        if (String.IsNullOrWhiteSpace(str)) return retval.ToArray();
        int ndx = 0;
        string s = "";
        bool insideDoubleQuote = false;
        bool insideSingleQuote = false;

        while (ndx < str.Length)
        {
            if (str[ndx] == ' ' && !insideDoubleQuote && !insideSingleQuote)
            {
                if (!String.IsNullOrWhiteSpace(s.Trim())) retval.Add(s.Trim());
                s = "";
            }
            if (str[ndx] == '"') insideDoubleQuote = !insideDoubleQuote;
            if (str[ndx] == '\'') insideSingleQuote = !insideSingleQuote;
            s += str[ndx];
            ndx++;
        }
        if (!String.IsNullOrWhiteSpace(s.Trim())) retval.Add(s.Trim());
        return retval.ToArray();
    }
}

Эта программа выдаст следующий результат:

Start -p:SomeNameValue -h

1. Start

2. -p:SomeNameValue

3. -h

DisplayMessage -m:Hello

1. DisplayMessage

2. -m:Hello

DisplayMessage -m:'Hello World'

1. DisplayMessage

2. -m:'Hello World'

DisplayMessage -m:"Hello World"

1. DisplayMessage

2. -m:"Hello World"

DisplayMessage -m:"'Inside double quotes'"

1. DisplayMessage

2. -m:"'Inside double quotes'"

DisplayMessage -m:'"Inside single quotes"'

1. DisplayMessage

2. -m:'"Inside single quotes"'

person Icemanind    schedule 03.03.2021

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

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

public static List<string> GetArgs(string cmdLine)
{
    var args = new List<string>();
    if (string.IsNullOrWhiteSpace(cmdLine)) return args;

    var parts = cmdLine.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
    var openQuote = false;
    var currentPart = new StringBuilder();

    foreach (var part in parts)
    {
        if (part.Count(c => c == '"') % 2 == 1)
        {
            if (currentPart.Length > 0) currentPart.Append(" ");
            currentPart.Append(part);

            if (openQuote)
            {
                args.Add(currentPart.ToString());
                currentPart.Clear();
            }

            openQuote = !openQuote;
        }
        else if (openQuote)
        {
            if (currentPart.Length > 0) currentPart.Append(" ");
            currentPart.Append(part);
        }
        else
        {
            args.Add(part);
        }
    }

    if (currentPart.Length > 0) args.Add(currentPart.ToString());

    return args;
}
person Rufus L    schedule 03.03.2021