Як нумеровані групи захоплення нумеруються в регулярних виразах?


84

Чи існує визначена поведінка того, як регулярні вирази повинні обробляти поведінку захоплення вкладених дужок? Більш конкретно, чи можете ви обґрунтовано очікувати, що різні двигуни захоплять зовнішні дужки в першій позиції, а вкладені дужки в наступні позиції?

Розглянемо наступний 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
)

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

Отже, чи це "фіксуйте все насамперед" у поведінці механізмів регулярних виразів, або це буде залежати від контексту шаблону та / або поведінки механізму (PCRE відрізняється від того, що C # відрізняється від того, що відрізняється від Java ніж тощо)?


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

До групи можна отримати доступ за допомогою $ 1, $ 2, $ 3 .... тощо. Як отримати доступ до 10-ї групи? Це буде 10 доларів? Я не думаю, що 10 доларів працюватимуть, оскільки вони будуть інтерпретовані як 1 долар, а потім 0. Чи означає це, що ми можемо мати максимум 9 груп? Якщо автор може, будь ласка, включіть це як частину запитання, тоді це буде одне місце, щоб знати все про вкладені групи у регулярних виразах.
LionHeart

Відповіді:


59

Від 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" чи щось інше. Або просто позбавитись букв значення.


1
@Sinan: він використовує PCRE у PHP, що є "Perl-сумісними регулярними виразами"; отже, це має бути так само, як безпосереднє використання Perl
Паскаль МАРТІН

3
Паскаль, PCRE почав із спроби стати набором регулярних виразів, сумісних з Perl, але за останні роки ці два дещо розійшлися. Досі дуже схожі, але є незначні відмінності в розширених наборах функцій. (Крім того, за питанням, мене цікавлять усі платформи)
Алан Сторм,

1
Насправді, саме Perl сьогодні робить більшу частину «віддалення», але ви маєте рацію: «Perl-сумісний» швидко змінюється з помилкового імені на non-sequitur. : D
Алан Мур

1
@ Алан, Perl точно рухається. Р5.10 змінив декілька речей, але 6 буде зовсім іншим. P майже напевно потрібно інтерпретувати як "Perl 5". PCRE - це чудовий проект, який я не можу похвалити досить, він був знахідкою для кількох проектів.
daotoad

1
Я додав це під першою цитатою Caveat : За винятком дужки, що не відкриває групу (? =). Я не розумів, що не ввійшов у систему, коли редагував його. Лише коли я додав цей коментар, мені було запропоновано ввести облікові дані. Отже, йому потрібно схвалити ще 1 людину!
JGFMK

17

Так, це все досить чітко визначено для всіх мов, які вас цікавлять:

  • 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
    "Знімки за допомогою () нумеруються автоматично залежно від порядку відкриття дужок, починаючи з одного. Перший захват, номер елемента захоплення нуль, це текст, який відповідає цілому шаблону регулярних виразів. ")
  • 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 () повертає захоплені підрядки" і прочитайте п'ятий абзац, що далі:

    Перша пара цілих чисел, овектор [0] і овектор [1], ідентифікують
    частина рядка теми, що відповідає цілому шаблону. Наступний
    пара використовується для першого зйомки підпрограми тощо. Значення
    повертається pcre_exec () - це на одну більше, ніж пара з найбільшим номером
    встановлено. Наприклад, якщо було захоплено два підрядки, файл
    повертається значення 3. Якщо немає підзаписів для захоплення, повертається
    значення успішного збігу дорівнює 1, що вказує на те, що лише перша пара
    встановлено.
    
  • Різні Perl - http://perldoc.perl.org/perlre.html#Capture-buffers
    $ 1, $ 2 і т. Д. Відповідають групам захоплення, як ви очікували (тобто внаслідок відкриття дужки), однак $ 0 повертає назву програми, а не весь рядок запиту - щоб отримати замість того, що ви використовуєте $ &.

Ви, швидше за все, знайдете подібні результати для інших мов (Python, Ruby та інших).

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

Помістити весь рядок збігу в положення 0 також має сенс - переважно для послідовності. Це дозволяє всьому узгодженому рядку залишатися на одному індексі незалежно від кількості груп захоплення від регулярного виразу до регулярного виразу та незалежно від кількості груп захоплення, які насправді відповідають чому-небудь (наприклад, Java згорне довжину масиву відповідних груп для кожного захоплення group не відповідає жодному вмісту (подумайте, наприклад, щось на зразок "шаблону (. *)"). Ви завжди можете перевірити capturing_group_results [capturing_group_results_length - 2], але це погано перекладається на мови Perl, які динамічно створюють змінні ($ 1 , $ 2 тощо) (Perl, звичайно, поганий приклад, оскільки він використовує $ & для відповідного виразу, але ви розумієте :).


1
Приємна відповідь .. Але як щодо оновлення для Python (2 і 3) теж :-)
JGFMK

А як щодо JavaScript!?!
mesqueeb

9

У кожному ароматі регулярного виразу я знаю групи чисел за порядком, у якому з’являються початкові дужки. Те, що зовнішні групи пронумеровані до їхніх підгруп, є лише природним результатом, а не явною політикою.

Цікавим стає те, що стосується названих груп . У більшості випадків вони дотримуються однакової політики нумерації за відносними положеннями парен - ім'я є лише псевдонімом номера. Однак у регулярних виразах .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)


"захоплення в порядку лівого батька" Дякую за це, це набагато більш стислий спосіб опису поведінки.
Алан Сторм,

1
Ви можете перенумерувати захоплення в Perl 5.10 та Perl 6.
Бред Гілберт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.