Что такое группа без захвата в регулярных выражениях?

Ответы:

2331

Позвольте мне попытаться объяснить это на примере.

Рассмотрим следующий текст:

http://stackoverflow.com/
/programming/tagged/regex

Теперь, если я применю регулярное выражение ниже ...

(https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?

... я бы получил следующий результат:

Match "http://stackoverflow.com/"
     Group 1: "http"
     Group 2: "stackoverflow.com"
     Group 3: "/"

Match "/programming/tagged/regex"
     Group 1: "https"
     Group 2: "stackoverflow.com"
     Group 3: "/questions/tagged/regex"

Но мне нет дела до протокола - мне просто нужен хост и путь к URL. Итак, я изменяю регулярное выражение, чтобы включить группу без захвата (?:).

(?:https?|ftp)://([^/\r\n]+)(/[^\r\n]*)?

Теперь мой результат выглядит так:

Match "http://stackoverflow.com/"
     Group 1: "stackoverflow.com"
     Group 2: "/"

Match "/programming/tagged/regex"
     Group 1: "stackoverflow.com"
     Group 2: "/questions/tagged/regex"

Видеть? Первая группа не была захвачена. Парсер использует его для соответствия тексту, но игнорирует его позже, в конечном результате.


РЕДАКТИРОВАТЬ:

В соответствии с просьбой, позвольте мне также попытаться объяснить группы.

Ну, группы служат многим целям. Они могут помочь вам извлечь точную информацию из большего совпадения (которое также может быть названо), они позволяют вам сопоставить предыдущую сопоставленную группу и могут быть использованы для замены. Давайте попробуем несколько примеров, не так ли?

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

   \<(?<TAG>.+?)\> [^<]*? \</\k<TAG>\>
or
   \<(.+?)\> [^<]*? \</\1\>

Первый регулярное выражение имеет именованную группу (TAG), а второй использует общую группу. Оба регулярных выражения делают одно и то же: они используют значение из первой группы (имя тега), чтобы соответствовать закрывающему тегу. Разница в том, что первый использует имя для соответствия значению, а второй использует групповой индекс (который начинается с 1).

Давайте попробуем некоторые замены сейчас. Рассмотрим следующий текст:

Lorem ipsum dolor sit amet consectetuer feugiat fames malesuada pretium egestas.

Теперь давайте используем это тупое регулярное выражение:

\b(\S)(\S)(\S)(\S*)\b

Это регулярное выражение сопоставляет слова, содержащие не менее 3 символов, и использует группы для разделения первых трех букв. Результат таков:

Match "Lorem"
     Group 1: "L"
     Group 2: "o"
     Group 3: "r"
     Group 4: "em"
Match "ipsum"
     Group 1: "i"
     Group 2: "p"
     Group 3: "s"
     Group 4: "um"
...

Match "consectetuer"
     Group 1: "c"
     Group 2: "o"
     Group 3: "n"
     Group 4: "sectetuer"
...

Итак, если мы применим строку подстановки:

$1_$3$2_$4

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

L_ro_em i_sp_um d_lo_or s_ti_ a_em_t c_no_sectetuer f_ue_giat f_ma_es m_la_esuada p_er_tium e_eg_stas.

Вы также можете использовать именованные группы для подстановок, используя ${name}.

Чтобы поиграть с регулярными выражениями, я рекомендую http://regex101.com/ , который предлагает большое количество деталей о том, как работает регулярное выражение; он также предлагает несколько двигателей регулярных выражений на выбор.

Рикардо Нольде
источник
3
@ajsie: Традиционные (собирающие) группы наиболее полезны, если вы выполняете операцию замены результатов. Вот пример, где я беру фамилии и имена, разделенные запятыми, а затем меняю их порядок (благодаря именованным группам) ... regexhero.net/tester/?id=16892996-64d4-4f10-860a-24f28dad7e30
Стив Уортам
2
Нет, это не то же самое.
Рикардо Нольде,
4
Могу также указать, что группы без захвата уникально полезны при использовании регулярных выражений в качестве разделителей: «Алиса и Боб» -split «\ s + (?: и | или) \ s +»
Евгений
7
Было бы интересно узнать разницу между группами, не фиксирующими захват (? :), и утверждениями о заглядывании вперед и о взгляде (? =,?!). Я только начал изучать регулярные выражения, но из того, что я понимаю, не захватывающие группы используются для сопоставления и «возврата» того, что им соответствует, но это «возвращаемое значение» не «сохраняется» для обратной ссылки. С другой стороны, утверждения типа «взгляд вперед» и «взгляд назад» не только не «хранятся», они также не являются частью совпадения, они просто утверждают, что что-то будет соответствовать, но их значение «совпадения» игнорируется, если я не ошибаюсь .. . (Я примерно прав?)
Кристиан,
5
[] набор; [123] соответствует любому символу внутри набора один раз; [^ 123] соответствует чему-либо НЕ внутри набора один раз; [^ / \ r \ n] + соответствует одному или нескольким символам, которые отличаются от /, \ r, \ n.
Рикардо Нольде
180

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

Допустим, вы хотите сопоставить числовой текст, но некоторые числа могут быть записаны как 1-й, 2-й, 3-й, 4-й, ... Если вы хотите захватить числовую часть, но не (необязательный) суффикс, вы можете использовать группу без захвата ,

([0-9]+)(?:st|nd|rd|th)?

Это будет соответствовать числам в форме 1, 2, 3 ... или в форме 1, 2, 3, ..., но это будет захватывать только числовую часть.

Билл Ящерица
источник
3
Краткое и, вероятно, лучшее объяснение здесь.
НельсонГон
107

?: используется, когда вы хотите сгруппировать выражение, но не хотите сохранять его как совпадающую / захваченную часть строки.

Примером будет что-то, чтобы соответствовать IP-адресу:

/(?:\d{1,3}\.){3}\d{1,3}/

Обратите внимание, что меня не волнует сохранение первых 3 октетов, но (?:...)группировка позволяет мне сократить регулярное выражение без дополнительных затрат на захват и сохранение совпадения.

RC.
источник
38

Это делает группу не записывающей, что означает, что подстрока, соответствующая этой группе, не будет включена в список захватов. Пример в ruby, чтобы проиллюстрировать разницу:

"abc".match(/(.)(.)./).captures #=> ["a","b"]
"abc".match(/(?:.)(.)./).captures #=> ["b"]
sepp2k
источник
Почему мы не можем просто использовать "abc" .match (/.(.)./). Здесь?
ПРАЗАННА САРАФ
@PRASANNASARAF Вы можете, конечно. Смысл кода заключался в том, чтобы показать, что (?:)не производит захват, а не в демонстрации полезного примера (?:). (?:)полезно, когда вы хотите сгруппировать подвыражение (скажем, когда вы хотите применить квантификаторы к неатомарному подвыражению или если вы хотите ограничить область действия a |), но вы не хотите ничего захватывать.
19
26

ИСТОРИЧЕСКАЯ МОТИВАЦИЯ:

Существование не захватывающих групп можно объяснить с помощью скобок.

Рассмотрим выражения (a|b)cи a|bc, ввиду приоритета конкатенации |, эти выражения представляют два разных языка ( {ac, bc}и {a, bc}соответственно).

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

Если вы хотите иметь круглые скобки, но не захватывать подвыражение, вы используете НЕЗАХВАТЫВАЮЩИЕ ГРУППЫ. В примере(?:a|b)c

user2369060
источник
6
Мне было интересно, почему. Как я думаю, «почему» жизненно важно для запоминания этой информации.
JMI MADISON
22

Позвольте мне попробовать это на примере:

Код регулярного выражения: (?:animal)(?:=)(\w+)(,)\1\2

Строка поиска:

Линия 1 - animal=cat,dog,cat,tiger,dog

Строка 2 - animal=cat,cat,dog,dog,tiger

Строка 3 - animal=dog,dog,cat,cat,tiger

(?:animal) -> Незахваченная группа 1

(?:=)-> Незахваченная группа 2

(\w+)-> Захваченная группа 1

(,)-> Захваченная группа 2

\1 -> результат захваченной группы 1, т.е. в строке 1 - кошка, в строке 2 - кошка, в строке 3 - собака.

\2 -> результат захваченной группы 2, т.е. запятая (,)

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

Согласно порядку кода (?:animal)должна быть группа 1 и (?:=)должна быть группа 2 и продолжается ..

но, давая ?:нам, мы делаем группу совпадений не захваченной (которые не учитываются в сопоставленной группе, поэтому номер группировки начинается с первой захваченной группы, а не не захваченной), так что повторение результата сопоставления группы (?:animal)не может быть вызван позже в коде.

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

введите описание изображения здесь

шехар гехлот
источник
14

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

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

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

Пример использования позже в регулярном выражении (обратная ссылка):

<([A-Z][A-Z0-9]*)\b[^>]*>.*?</\1> [Находит тег xml (без поддержки ns)]

([A-Z][A-Z0-9]*) группа захвата (в данном случае это тэг)

Позже в регулярном выражении будет указано, \1что это означает, что он будет соответствовать только тому же тексту, который был в первой группе ( ([A-Z][A-Z0-9]*)группе) (в этом случае он соответствует конечному тегу).

Боб Финхеймер
источник
Не могли бы вы привести простой пример того, как он будет использоваться позже, чтобы соответствовать ИЛИ?
never_had_a_name
Я имею в виду, что вы можете использовать, чтобы соответствовать позже или вы можете использовать его в замене. Или в этом предложении было только для того, чтобы показать вам, что есть две
цели
9

Ну, я разработчик JavaScript и постараюсь объяснить его значение для JavaScript.

Рассмотрим сценарий, в котором вы хотите совпасть, cat is animal когда вы хотите совпасть с кошкой и животным, и оба должны иметь isмежду ними.

 // this will ignore "is" as that's is what we want
"cat is animal".match(/(cat)(?: is )(animal)/) ;
result ["cat is animal", "cat", "animal"]

 // using lookahead pattern it will match only "cat" we can
 // use lookahead but the problem is we can not give anything
 // at the back of lookahead pattern
"cat is animal".match(/cat(?= is animal)/) ;
result ["cat"]

 //so I gave another grouping parenthesis for animal
 // in lookahead pattern to match animal as well
"cat is animal".match(/(cat)(?= is (animal))/) ;
result ["cat", "cat", "animal"]

 // we got extra cat in above example so removing another grouping
"cat is animal".match(/cat(?= is (animal))/) ;
result ["cat", "animal"]
Gaurav
источник
7

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

Джек Пэн
источник
7

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

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

Чтобы получить доступ к определенной части регулярного выражения без определенных посторонних символов, вам всегда нужно использовать .group(<index>)

Скотт Андерсон
источник
2
Вы предоставили самый важный совет, который отсутствовал в остальных ответах. Я перепробовал все примеры в них и использовал отборные ругательства, так как не получил желаемого результата. Только твоя публикация показала мне, где я ошибся.
Сешадри Р
Рад это слышать!
Скотт Андерсон
6

tl; dr группы без захвата, как следует из названия, являются частями регулярного выражения, которые вы не хотите включать в совпадение, и ?:является способом определения группы как не захватывающей.

Допустим, у вас есть адрес электронной почты example@example.com. Следующее регулярное выражение создаст две группы , часть id и часть @ example.com. (\p{Alpha}*[a-z])(@example.com), Для простоты мы извлекаем все доменное имя, включая @символ.

Теперь, скажем, вам нужна только часть идентификатора адреса. То , что вы хотите сделать, чтобы захватить первую группу результата матча, окруженную ()в регулярных выражениях и способ сделать это состоит в использовании синтаксиса не-захвата группы, то есть ?:. Таким образом, регулярное выражение (\p{Alpha}*[a-z])(?:@example.com)вернет только часть идентификатора электронной почты.

6 упак. Малыш
источник
5

Одна интересная вещь, с которой я столкнулся, это то, что вы можете иметь группу захвата внутри группы без захвата. Посмотрите на регулярное выражение для соответствия веб-URL:

var parse_url_regex = /^(?:([A-Za-z]+):)(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;

Входная строка URL:

var url = "http://www.ora.com:80/goodparts?q#fragment";

Первая группа в моем регулярном выражении (?:([A-Za-z]+):)- это группа без захвата, которая соответствует схеме протокола и :символу двоеточия, т. http:Е. Когда я работал под кодом ниже, я увидел, что первый индекс возвращаемого массива содержал строку, httpкогда я думал, что httpи двоеточие :оба не будут сообщены, поскольку они находятся в группе без захвата.

console.debug(parse_url_regex.exec(url));

введите описание изображения здесь

Я подумал, что если первая группа (?:([A-Za-z]+):)не захватывает группу, то почему она возвращает httpстроку в выходном массиве.

Поэтому, если вы заметили, что ([A-Za-z]+)внутри группы без захвата есть вложенная группа. Эта вложенная группа сама по себе ([A-Za-z]+)является группой захвата (изначально не имеющей ?:) внутри группы без захвата (?:([A-Za-z]+):). Вот почему текст по- httpпрежнему фиксируется, но :символ двоеточия, который находится внутри группы без захвата, но за пределами группы захвата, не отображается в массиве вывода.

RBT
источник
2

Откройте Google Chrome devTools, а затем вкладку «Консоль» и введите:

"Peace".match(/(\w)(\w)(\w)/)

Запустите его, и вы увидите:

["Pea", "P", "e", "a", index: 0, input: "Peace", groups: undefined]

В JavaScriptRegExp захват двигателя три группы, элементы с индексами 1,2,3. Теперь используйте не захватывающую метку, чтобы увидеть результат.

"Peace".match(/(?:\w)(\w)(\w)/)

Результат:

["Pea", "e", "a", index: 0, input: "Peace", groups: undefined]

Это очевидно, что не захватывает группу.

AmerllicA
источник
2

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

Переменные захвата $1и т. Д. Недействительны, если совпадение не выполнено, и они также не очищены.

#!/usr/bin/perl  
use warnings;
use strict;   
$_ = "bronto saurus burger";
if (/(?:bronto)? saurus (steak|burger)/)
{
    print "Fred wants a  $1";
}
else
{
    print "Fred dont wants a $1 $2";
}

В приведенном выше примере, чтобы избежать захвата бронто $1, (?:)используется.

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

Итак, вывод будет таким, как показано ниже:

Fred wants a burger

Это полезно, если вы не хотите, чтобы совпадения были сохранены.

харини
источник
1

Это очень просто, мы можем понять на примере простой даты, предположим, что если дата упоминается как 1 января 2019 года или 2 мая 2019 года или любая другая дата, и мы просто хотим преобразовать ее в формат дд / мм / гггг , нам не понадобится месяц имя, которое в этом случае будет январь или февраль, поэтому для захвата числовой части, но не (необязательного) суффикса, вы можете использовать группу без захвата.

поэтому регулярное выражение будет

([0-9]+)(?:January|February)?

Это так просто.

Навед Ахмад
источник