Регулярное выражение для разделения на пробелы, кроме как в кавычках

Я хотел бы использовать метод.Net Regex.Split, чтобы разбить эту входную строку на массив. Он должен разбиваться на пробелы, если он не заключен в кавычки.

Входные данные: вот "моя строка", она имеет "шесть совпадений"

Ожидаемый результат:

  1. Вот
  2. является
  3. моя строка
  4. Это
  5. имеет
  6. шесть матчей

Какой шаблон мне нужен? Также мне нужно указать какие-либо RegexOptions?

12 ответов

Решение

Варианты не требуются

Regex:

\w+|"[\w\s]*"

C#:

Regex regex = new Regex(@"\w+|""[\w\s]*""");

Или, если вам нужно исключить "символы:

    Regex
        .Matches(input, @"(?<match>\w+)|\""(?<match>[\w\s]*)""")
        .Cast<Match>()
        .Select(m => m.Groups["match"].Value)
        .ToList()
        .ForEach(s => Console.WriteLine(s));

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

(?<=")\w[\w\s]*(?=")|\w+|"[\w\s]*"

Входные данные: вот "моя строка", она имеет "шесть совпадений"

Выход:

  1. Вот
  2. является
  3. "моя строка"
  4. Это
  5. имеет
  6. "шесть матчей"

К сожалению, это в том числе цитаты. Если вы вместо этого используете следующее:

(("((?<token>.*?)(?<!\\)")|(?<token>[\w]+))(\s)*)

И явно захватить совпадения "токен" следующим образом:

    RegexOptions options = RegexOptions.None;
    Regex regex = new Regex( @"((""((?<token>.*?)(?<!\\)"")|(?<token>[\w]+))(\s)*)", options );
    string input = @"   Here is ""my string"" it has   "" six  matches""   ";
    var result = (from Match m in regex.Matches( input ) 
                  where m.Groups[ "token" ].Success
                  select m.Groups[ "token" ].Value).ToList();

    for ( int i = 0; i < result.Count(); i++ )
    {
        Debug.WriteLine( string.Format( "Token[{0}]: '{1}'", i, result[ i ] ) );
    }

Отладочный вывод:

Token[0]: 'Here'
Token[1]: 'is'
Token[2]: 'my string'
Token[3]: 'it'
Token[4]: 'has'
Token[5]: ' six  matches'

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

"the lib.lib" "another lib".lib

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

    /// <summary>
    /// Splits the string passed in by the delimiters passed in.
    /// Quoted sections are not split, and all tokens have whitespace
    /// trimmed from the start and end.
    public static List<string> split(string stringToSplit, params char[] delimiters)
    {
        List<string> results = new List<string>();

        bool inQuote = false;
        StringBuilder currentToken = new StringBuilder();
        for (int index = 0; index < stringToSplit.Length; ++index)
        {
            char currentCharacter = stringToSplit[index];
            if (currentCharacter == '"')
            {
                // When we see a ", we need to decide whether we are
                // at the start or send of a quoted section...
                inQuote = !inQuote;
            }
            else if (delimiters.Contains(currentCharacter) && inQuote == false)
            {
                // We've come to the end of a token, so we find the token,
                // trim it and add it to the collection of results...
                string result = currentToken.ToString().Trim();
                if (result != "") results.Add(result);

                // We start a new token...
                currentToken = new StringBuilder();
            }
            else
            {
                // We've got a 'normal' character, so we add it to
                // the curent token...
                currentToken.Append(currentCharacter);
            }
        }

        // We've come to the end of the string, so we add the last token...
        string lastResult = currentToken.ToString().Trim();
        if (lastResult != "") results.Add(lastResult);

        return results;
    }

Я использовал ответ Бартека Сабата, но мне нужно было захватить больше, чем просто символы \ w в моих токенах. Чтобы решить эту проблему, я слегка изменил его регулярное выражение, аналогично ответу Грзенио:

Regular Expression: (?<match>[^\s"]+)|(?<match>"[^"]*")

C# String:          (?<match>[^\\s\"]+)|(?<match>\"[^\"]*\")

Код Бартека (который возвращает токены без заключенных в кавычки) становится:

Regex
        .Matches(input, "(?<match>[^\\s\"]+)|(?<match>\"[^\"]*\")")
        .Cast<Match>()
        .Select(m => m.Groups["match"].Value)
        .ToList()
        .ForEach(s => Console.WriteLine(s));

Я нашел регулярное выражение в этом ответе весьма полезным. Чтобы он работал в C#, вам нужно использовать класс MatchCollection.

//need to escape \s
string pattern = "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'";

MatchCollection parsedStrings = Regex.Matches(line, pattern);

for (int i = 0; i < parsedStrings.Count; i++)
{
    //print parsed strings
    Console.Write(parsedStrings[i].Value + " ");
}
Console.WriteLine();

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

"[^"]+"|\s?\w+?\s

С небольшим количеством беспорядка, обычные языки могут отслеживать четное / нечетное подсчет кавычек, но если ваши данные могут включать в себя экранированные кавычки (\"), то у вас есть реальная проблема с производством или пониманием регулярного выражения, которое будет обрабатывать это правильно,

РЕДАКТИРОВАТЬ: Извините за мой предыдущий пост, это, очевидно, возможно.

Для обработки всех не алфавитно-цифровых символов вам нужно что-то вроде этого:

MatchCollection matchCollection = Regex.Matches(input, @"(?<match>[^""\s]+)|\""(?<match>[^""]*)""");
foreach (Match match in matchCollection)
        {
            yield return match.Groups["match"].Value;
        }

вы можете сделать foreach умнее, если используете.Net >2.0

Shaun,

Я считаю, что следующее регулярное выражение должно сделать это

(?<=")\w[\w\s]*(?=")|\w+  

С Уважением,
Ливен

Посмотрите на разделенную функцию LSteinle, которая поддерживает классификаторы текста в проекте Code

Вот фрагмент из его проекта, который вас интересует.

using System.Text.RegularExpressions;

public string[] Split(string expression, string delimiter, string qualifier, bool ignoreCase)
{
    string _Statement = String.Format("{0}(?=(?:[^{1}]*{1}[^{1}]*{1})*(?![^{1}]*{1}))", 
                        Regex.Escape(delimiter), Regex.Escape(qualifier));

    RegexOptions _Options = RegexOptions.Compiled | RegexOptions.Multiline;
    if (ignoreCase) _Options = _Options | RegexOptions.IgnoreCase;

    Regex _Expression = New Regex(_Statement, _Options);
    return _Expression.Split(expression);
}

Просто следите за вызовом этого в цикле, поскольку он создает и компилирует оператор Regex каждый раз, когда вы вызываете его. Поэтому, если вам нужно вызывать его несколько раз, я бы посмотрел на создание кеша Regex.

Если вы хотите взглянуть на общее решение этой проблемы в форме бесплатного объекта javascript с открытым исходным кодом, вы можете посетить http://splitterjsobj.sourceforge.net/ для демонстрации (и загрузки), Объект имеет следующие особенности:

  • Пары определяемых пользователем символов кавычек могут использоваться для экранирования (не допускайте разделения внутри кавычек). Кавычки могут быть экранированы с помощью пользовательского escape-символа и / или "двойным кавычкой". Экранирующая буква может быть экранирована (сама с собой). В одном из 5 выходных массивов (свойств объекта) выход не экранирован. (Например, если escape-символ = /, "a///"b"не экранируется как a/"b)
  • Разбить на массив разделителей; разбирать файл за один звонок. (Выходные массивы будут вложенными.)
  • Все escape-последовательности, распознаваемые JavaScript, могут быть оценены во время процесса разделения и / или в предварительном процессе.
  • Функциональность обратного вызова
  • Кросс-браузерная согласованность

Объект также доступен в виде плагина jQuery, но как новый пользователь на этом сайте я могу включить только одну ссылку в это сообщение.

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

        public static Argument[] ParseCmdLine(string args) {
    List<string> ls = new List<string>();
    StringBuilder sb = new StringBuilder(128);

    // support quoted text nesting up to 8 levels deep
    Span<char> quoteChar = stackalloc char[8];
    int quoteLevel = 0;
      
    for (int i = 0; i < args.Length; ++i) {
      char ch = args[i];
      switch (ch) {
        case ' ':
          if (quoteLevel == 0) {
            ls.Add(sb.ToString());
            sb.Clear();
            break;
          } 
          goto default; 
        case '"':
        case '\'':
          if (quoteChar[quoteLevel] == ch) {
            --quoteLevel;
          } else {
            quoteChar[++quoteLevel] = ch;
          }
          goto default; 
        default:
          sb.Append(ch);
          break;
      }
    }
    if (sb.Length > 0) { ls.Add(sb.ToString()); sb.Clear(); }

    return Arguments.ParseCmdLine(ls.ToArray());
  }

И вот дополнительный код для синтаксического анализа аргументов командной строки для объектов:

        public struct Argument {
    public string Prefix;
    public string Name;
    public string Eq;
    public string QuoteType;
    public string Value;

    public string[] ToArray() => this.Eq == " " ? new string[] { $"{Prefix}{Name}", $"{QuoteType}{Value}{QuoteType}" } : new string[] { this.ToString() };
    public override string ToString() => $"{Prefix}{Name}{Eq}{QuoteType}{Value}{QuoteType}";
  }

  private static readonly Regex RGX_MatchArg = new Regex(@"^(?<prefix>-{1,2}|\/)(?<name>[a-zA-Z][a-zA-Z_-]*)(?<assignment>(?<eq>[:= ]|$)(?<quote>[""'])?(?<value>.+?)(?:\k<quote>|\s*$))?");
  private static readonly Regex RGX_MatchQuoted = new Regex(@"(?<quote>[""'])?(?<value>.+?)(?:\k<quote>|\s*$)");

  public static Argument[] ParseCmdLine(string[] rawArgs) {
    int count = 0;
    Argument[] pairs = new Argument[rawArgs.Length];

    int i = 0;
    while(i < rawArgs.Length) {
      string current = rawArgs[i];
      i+=1;
      Match matches = RGX_MatchArg.Match(current);
      Argument arg = new Argument();
      arg.Prefix = matches.Groups["prefix"].Value;
      arg.Name = matches.Groups["name"].Value;
      arg.Value = matches.Groups["value"].Value;
      if(!string.IsNullOrEmpty(arg.Value)) {
        arg.Eq = matches.Groups["eq"].Value;
        arg.QuoteType = matches.Groups["quote"].Value;
      } else if ((i < rawArgs.Length) && !rawArgs[i].StartsWith('-') && !rawArgs[i].StartsWith('/')) {
        arg.Eq = " ";
        Match quoted = RGX_MatchQuoted.Match(rawArgs[i]);
        arg.QuoteType = quoted.Groups["quote"].Value;
        arg.Value = quoted.Groups["value"].Value;
        i+=1;
      }
      if(string.IsNullOrEmpty(arg.QuoteType) && arg.Value.IndexOfAny(new char[] { ' ', '/', '\\', '-', '=', ':' }) >= 0) {
        arg.QuoteType = "\"";
      }
      pairs[count++] = arg;
    }

    return pairs.Slice(0..count);
  }

  public static ILookup<string, Argument> ToLookup(this Argument[] args) => args.ToLookup((arg) => arg.Name, StringComparer.OrdinalIgnoreCase);
}

Он может анализировать все виды вариантов аргументов:

      -test -environment staging /DEqTest=avalue /Dcolontest:anothervalue /DwithSpaces="heys: guys" /slashargflag -action="Do: 'The Thing'" -action2 "do: 'Do: \"The Thing\"'" -init

Просто нужно чередовать вложенные кавычки между разными типами котировок.

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