Що таке група, яка не фіксує в регулярних виразах?


1763

Як групи, що не захоплюють, тобто (?:)використовуються в регулярних виразах і для чого вони хороші?


42
Це запитання було додано до поширених запитань щодо регулярного вираження стека в розділі "Групи".
aliteralmind

Відповіді:


2323

Дозвольте спробувати пояснити це на прикладі.

Розглянемо наступний текст:

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

Цей регулярний вираз поєднує слова з принаймні трьома символами та використовує групи для розділення перших трьох букв. Результат такий:

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

2
Ні, це не те саме.
Рікардо Нолде

4
Можна також зазначити, що групи, що не захоплюють, є однозначно корисними при використанні регулярних виразів як розділених роздільників: "Аліса і Боб" -split "\ s + (?: і | або) \ s +"
Євгеній

7
Було б цікаво пояснити різницю між групами, які не захоплюють (? :), і lookahead та дивлячись за твердженнями (? =,?!). Я щойно почав дізнаватися про регулярні вирази, але, наскільки я розумію, групи, що не захоплюють, використовуються для відповідності та «повернення» того, що вони відповідають, але це «повернене значення» не «зберігається» для зворотного посилання. Lookahead та зауваження, що дивляться позаду, з іншого боку, не тільки не "зберігаються", вони також не є частиною матчу, вони просто стверджують, що щось відповідатиме, але їх значення "match" ігнорується, якщо я не помиляюся .. . (Я грубо прав?)
Крістіан

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
Коротке і, мабуть, найкраще пояснення тут.
NelsonGon

106

?: використовується, коли ви хочете згрупувати вираз, але ви не хочете його зберігати як збірну / захоплену частину рядка.

Прикладом може слугувати відповідність IP-адреси:

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

Зауважте, що я не дбаю про збереження перших 3 октетів, але (?:...)групування дозволяє мені скоротити регулярний вираз, не зазнаючи накладних витрат на захоплення та зберігання відповідності.


38

Це робить групу незахопленою, а це означає, що підрядка, відповідна цій групі, не буде включена до списку захоплень. Приклад в рубіні для ілюстрації різниці:

"abc".match(/(.)(.)./).captures #=> ["a","b"]
"abc".match(/(?:.)(.)./).captures #=> ["b"]

Чому ми просто не можемо використати "abc" .match (/.(.)./).
PRASANNA SARAF

@PRASANNASARAF Можна, звичайно. Суть коду полягала в тому, щоб показати, що (?:)не створює захоплення, не демонструвати корисний приклад (?:). (?:)корисно, коли ви хочете згрупувати підвираз (скажімо, коли ви хочете застосувати квантори до неатомного підвиразу або якщо ви хочете обмежити область a |), але ви не хочете нічого захоплювати.
sepp2k

26

ІСТОРИЧНА МОТИВАЦІЯ:

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

Розглянемо вирази, (a|b)cі a|bcчерез пріоритет конкатенації над |цими виразами є дві різні мови ( {ac, bc}і {a, bc}відповідно).

Однак дужки також використовуються як група, що відповідає (як пояснено іншими відповідями ...).

Якщо ви хочете мати дужки, але не захоплювати підвираз, ви використовуєте НЕЗАПУСТАВНІ ГРУПИ. У прикладі(?:a|b)c


6
Мені було цікаво, чому. Як я вважаю, "чому" є життєво важливим для запам'ятовування цієї інформації.
JMI MADISON

22

Дозвольте спробувати це на прикладі:

Код Regex: (?: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) -> Група, яка не потрапила у полон

(?:=)-> Не захоплені 2 група

(\w+)-> Захоплена група 1

(,)-> Захоплена група 2

\1 -> результат захопленої групи 1, тобто в рядку 1 - кішка, в рядку 2 - кішка, в рядку 3 - собака.

\2 -> результат захопленої групи 2, тобто кома (,)

Так у цьому коді, даючи \1і\2 ми згадуємо або повторюємо результат захопленої групи 1 та 2 відповідно згодом у коді.

Відповідно до порядку коду (?:animal)повинні бути групи 1 і(?:=) повинна бути група 2 і продовжується ..

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

Сподіваюсь, це пояснює використання групи, яка не захоплює.

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


14

Групи, які захоплюють, ви можете використовувати пізніше в регулярному виразі для відповідності АБО ви можете використовувати їх у замінній частині регулярного виразу. Створення групи, яка не захоплює, просто звільняє її від використання з будь-якої з цих причин.

Групи, які не захоплюють, чудові, якщо ви намагаєтеся зафіксувати багато різних речей, і є деякі групи, які ви не хочете захоплювати.

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

Приклад використання пізніше в регулярному вираженні (backreference):

<([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"]

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)поверне лише id-адресу електронної пошти.


5

Одна цікава річ, на яку я натрапив - це той факт, що ви можете мати групу захоплення всередині групи, яка не захоплює. Перегляньте нижче регекс для відповідності веб-адрес:

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

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

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

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

Тож якщо ви помітили, що ([A-Za-z]+)всередині групи, що не захоплює, є вкладена група. Ця вкладена група ([A-Za-z]+)є захоплюючою групою (що не має ?:на початку) всередині групи, яка не захоплює (?:([A-Za-z]+):). Ось чому текст httpяк і раніше захоплюється, але :символ двокрапки, який знаходиться всередині групи, що не фіксує, але поза групою захоплення, не надходить у вихідний масив.


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]

Це очевидно, що не є захоплюючою групою.


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 року або будь-яка інша дата, і ми просто хочемо перетворити її у формат dd / mm / yyyy , і нам би не потрібен місяць назва для цього питання січень чи лютий, тому для того, щоб захопити числову частину, але не (необов'язковий) суфікс, ви можете використовувати групу, яка не захоплює.

тож регулярним виразом було б,

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

Це так просто, як це.

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