Как вложенные группы захвата нумеруются в регулярных выражениях?

52

Существует ли определенное поведение для того, как регулярные выражения должны обрабатывать поведение захвата вложенных круглых скобок? Более конкретно, можете ли вы разумно ожидать, что разные двигатели будут захватывать внешние скобки в первой позиции и вложенные круглые скобки в последующих позициях?

Рассмотрим следующий PHP-код (с использованием регулярных выражений PCRE)

<?php
  $test_string = 'I want to test sub patterns';
  preg_match('{(I (want) (to) test) sub (patterns)}', $test_string, $matches);
  print_r($matches);
?>

Array
(
    [0] => I want to test sub patterns  //entire pattern
    [1] => I want to test           //entire outer parenthesis
    [2] => want             //first inner
    [3] => to               //second inner
    [4] => patterns             //next parentheses set
)

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

Итак, именно это "фиксирует всю вещь сначала" определенное поведение в механизмах регулярных выражений, или оно будет зависеть от контекста шаблона и/или поведения двигателя (PCRE отличается от С#, отличного от Java отличается от и т.д.)?

  • 0
    Если вы действительно интересуетесь всеми разновидностями регулярных выражений, вам нужен тег «независимый от языка». Существует слишком много разновидностей, чтобы перечислить их все, и большинство из них не соответствуют никаким реальным стандартам (хотя они удивительно последовательны, когда дело доходит до нумерации групп захвата).
  • 0
    Доступ к группе можно получить с помощью $ 1, $ 2, $ 3 .... и т. Д. Как получить доступ к 10-й группе? Это будет 10 долларов? Я не думаю, что 10 долларов будут работать, потому что они будут интерпретироваться как 1 доллар, за которым следует 0. Означает ли это, что у нас может быть максимум 9 групп? Если автор может, пожалуйста, включить это как часть вопроса, тогда это будет единственное место, чтобы узнать все о вложенных группах в регулярных выражениях.
Теги:
language-agnostic

4 ответа

46
Лучший ответ

От perlrequick

Если группировки в регулярном выражении вложенной, 1 доллар получает группу с крайняя левая открывающая скобка, $2 следующую открывающую скобку и т.д.

Обновление

Я не использую PCRE много, поскольку я обычно использую настоящую вещь;), но Документы PCRE показывают то же, что и Perl:

подшаблонов

2. Он устанавливает подшаблон как подшаблон захвата. Это означает, что, когда весь шаблон совпадает, эта часть строки субъекта, которая соответствует подшаблону, возвращается обратно вызывающему абоненту через аргумент ovector pcre_exec(). Открывающиеся круглые скобки подсчитываются слева направо (начиная с 1), чтобы получить номер для подматрицы захвата.

Например, если строка "красный король" сопоставляется с шаблоном

the ((red|white) (king|queen))

захваченные подстроки являются "красным королем", "красным" и "королем" и пронумерованы соответственно 1, 2 и 3.

Если PCRE отклоняется от совместимости регулярных выражений Perl, возможно, аббревиатура должна быть переопределена - "Perl Cognate Regular Expressions", "Perl Comparable Regular Expressions" или что-то еще. Или просто разделите буквы значения.

  • 0
    +1 но учтите, что он не использует Perl.
  • 1
    @Sinan: он использует PCRE в PHP, то есть "Perl-совместимые регулярные выражения"; так что это должно быть так же, как использование Perl напрямую
Показать ещё 5 комментариев
14

Да, это все очень хорошо определено для всех интересующих вас языков:

  • Java - http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#cg
    "Захватывающие группы нумеруются путем подсчета их открывающих скобок слева направо. Ноль группы всегда обозначает все выражение".
  • .Net - http://msdn.microsoft.com/en-us/library/bs2twtah(VS.71).aspx
    "Захваты с использованием() нумеруются автоматически в соответствии с порядком открывающей скобки, начиная с 1. Первый захват, номер элемента захвата нуля - это текст, сопоставляемый всем шаблоном регулярного выражения." )
  • PHP (функции PCRE) - http://www.php.net/manual/en/function.preg-replace.php#function.preg-replace.parameters
    "\ 0 или $0 относится к тексту, сопоставляемому всем шаблоном. Открывающиеся круглые скобки подсчитываются слева направо (начиная с 1), чтобы получить номер подматрицы захвата". (Это также относится к устаревшим функциям POSIX)
  • PCRE - http://www.pcre.org/pcre.txt
    Чтобы добавить к тому, что сказал Алан М, выполните поиск "Как pcre_exec() возвращает захваченные подстроки" и прочитайте следующий пятый абзац:

    The  first  pair  of  integers, ovector[0] and ovector[1], identify the
    portion of the subject string matched by the entire pattern.  The next
    pair  is  used for the first capturing subpattern, and so on. The value
    returned by pcre_exec() is one more than the highest numbered pair that
    has  been  set.  For example, if two substrings have been captured, the
    returned value is 3. If there are no capturing subpatterns, the  return
    value from a successful match is 1, indicating that just the first pair
    of offsets has been set.
    
  • Perl different - http://perldoc.perl.org/perlre.html#Capture-buffers
    $ 1, $2 и т.д. Соответствуют группам захвата, как и следовало ожидать (т.е. По наличию открывающей скобки), однако $0 возвращает имя программы, а не всю строку запроса - чтобы вы использовали $& вместо.

Вы, скорее всего, найдете похожие результаты для других языков (Python, Ruby и др.).

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

Помещение всей строки матча в позицию 0 также имеет смысл - в основном для согласованности. Он позволяет всей согласованной строке оставаться в одном индексе независимо от группы захвата числа от регулярного выражения к регулярному выражению и независимо от количества групп захвата, которые фактически соответствуют чему-либо (например, Java скроет длину массива согласованных групп для каждого захвата группа не соответствует какому-либо контенту (например, подумайте, например, как "a (. *) pattern" ). Вы всегда можете проверить capture_group_results [capturing_group_results_length - 2], но это плохо переводит языки на Perl, которые динамически создают переменные ($ 1, $2 и т.д.) (Perl - неудачный пример, так как он использует $& для согласованного выражения, но вы получаете идею:).

  • 0
    Хороший ответ .. Но как насчет обновления для Python (2 и 3) тоже :-)
8

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

Где интересно, это с именованными группами. В большинстве случаев они следуют той же политике нумерации по относительным позициям parens - это имя является просто псевдонимом для числа. Однако в регулярных выражениях .NET именованные группы нумеруются отдельно от пронумерованных групп. Например:

Regex.Replace(@"one two three four", 
              @"(?<one>\w+) (\w+) (?<three>\w+) (\w+)",
              @"$1 $2 $3 $4")

// result: "two four one three"

Фактически, это псевдоним для имени; числа, назначенные именованным группам, начинаются там, где "реальные" нумерованные группы остаются в силе. Это может показаться странной политикой, но для этого есть веская причина: в регулярных выражениях .NET вы можете использовать одно и то же имя группы более одного раза в регулярном выражении. Это позволяет использовать регулярные выражения, такие как этот поток для сопоставления чисел с плавающей запятой из разных локалей:

^[+-]?[0-9]{1,3}
(?:
    (?:(?<thousand>\,)[0-9]{3})*
    (?:(?<decimal>\.)[0-9]{2})?
|
    (?:(?<thousand>\.)[0-9]{3})*
    (?:(?<decimal>\,)[0-9]{2})?
|
    [0-9]*
    (?:(?<decimal>[\.\,])[0-9]{2})?
)$

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

И тогда есть Perl 5.10+, который дает нам больше контроля над захватом групп, чем я знаю, что делать.: D

4

Порядок захвата в порядке слева - стандартный для всех платформ, на которых я работал. (perl, php, ruby, egrep)

  • 0
    "захват в порядке левой парен". Спасибо за это, это гораздо более краткий способ описания поведения.
  • 2
    На каких платформах вы работали?
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню