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


1368

Я хочу відповідати частині рядка, використовуючи регулярний вираз, а потім отримати доступ до цієї підтезу:

var myString = "something format_abc"; // I want "abc"

var arr = /(?:^|\s)format_(.*?)(?:\s|$)/.exec(myString);

console.log(arr);     // Prints: [" format_abc", "abc"] .. so far so good.
console.log(arr[1]);  // Prints: undefined  (???)
console.log(arr[0]);  // Prints: format_undefined (!!!)

Що я роблю неправильно?


Я виявив, що з кодом регулярного виразу вище було нічого поганого: власне рядок, на який я тестував, це:

"date format_%A"

Повідомлення про те, що "% A" не визначено, здається дуже дивною поведінкою, але це безпосередньо не пов'язане з цим питанням, тому я відкрив нове. Чому відповідна підрядка повертає "невизначений" у JavaScript? .


Проблема полягала в тому, що він console.logприймає його параметри як printfоператор, і оскільки рядок, який я реєстрував ( "%A"), мав особливе значення, він намагався знайти значення наступного параметра.

Відповіді:


1673

Ви можете отримати доступ до таких груп, як:

var myString = "something format_abc";
var myRegexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
var match = myRegexp.exec(myString);
console.log(match[1]); // abc

І якщо є кілька матчів, ви можете повторити їх:

var myString = "something format_abc";
var myRegexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
match = myRegexp.exec(myString);
while (match != null) {
  // matched text: match[0]
  // match start: match.index
  // capturing group n: match[n]
  console.log(match[0])
  match = myRegexp.exec(myString);
}

Редагувати: 2019-09-10

Як ви бачите, спосіб перегляду кількох матчів був не дуже інтуїтивним. Це призводить до пропозиції String.prototype.matchAllметоду. Очікується, що цей новий метод буде використаний у специфікації ECMAScript 2020 . Це дає нам чистий API і вирішує багато проблем. Він почав орієнтуватися на основні браузери та JS, як Chrome 73+ / Node 12+ та Firefox 67+.

Метод повертає ітератор і використовується наступним чином:

const string = "something format_abc";
const regexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
const matches = string.matchAll(regexp);
    
for (const match of matches) {
  console.log(match);
  console.log(match.index)
}

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

function getFirstGroup(regexp, str) {
  const array = [...str.matchAll(regexp)];
  return array.map(m => m[1]);
}

// or:
function getFirstGroup(regexp, str) {
  return Array.from(str.matchAll(regexp), m => m[1]);
}

Тим часом, поки ця пропозиція отримує більш широку підтримку, ви можете використовувати офіційний пакет shim .

Також внутрішня робота методу проста. Еквівалентною реалізацією з використанням функції генератора буде така:

function* matchAll(str, regexp) {
  const flags = regexp.global ? regexp.flags : regexp.flags + "g";
  const re = new RegExp(regexp, flags);
  let match;
  while (match = re.exec(str)) {
    yield match;
  }
}

Створюється копія оригінального регулярного вираження; це уникнути побічних ефектів через мутацію lastIndexвластивості при переході кількох збігів.

Крім того, нам потрібно переконатися, що регулярний вираз має глобальний прапор, щоб уникнути нескінченного циклу.

Я також радий бачити, що навіть на це питання StackOverflow згадувалося в обговоренні пропозиції .


114
+1 Зауважте, що у другому прикладі ви повинні використовувати об’єкт RegExp (не тільки "/ myregexp /"), оскільки він зберігає значення lastIndex в об'єкті. Без використання об'єкта Regexp він буде повторюватися нескінченно
ianaz

7
@ianaz: Я не вірю, що це правда? http://jsfiddle.net/weEg9/, здається, працює на Chrome, принаймні.
прядильна

16
Чому вище замість: var match = myString.match(myRegexp); // alert(match[1])?
ДжонАллен

29
Немає необхідності в явному "новому RegExp", проте нескінченний цикл відбуватиметься, якщо не буде вказано / g
Джордж C

4
Ще один спосіб не наткнутися на нескінченний цикл - це експліцитно оновити рядок, наприкладstring = string.substring(match.index + match[0].length)
Ольга

186

Ось метод, який ви можете використовувати для отримання n- ї групи захоплення для кожного матчу:

function getMatches(string, regex, index) {
  index || (index = 1); // default to the first capturing group
  var matches = [];
  var match;
  while (match = regex.exec(string)) {
    matches.push(match[index]);
  }
  return matches;
}


// Example :
var myString = 'something format_abc something format_def something format_ghi';
var myRegEx = /(?:^|\s)format_(.*?)(?:\s|$)/g;

// Get an array containing the first capturing group for every match
var matches = getMatches(myString, myRegEx, 1);

// Log results
document.write(matches.length + ' matches found: ' + JSON.stringify(matches))
console.log(matches);


12
Це набагато краща відповідь для інших, оскільки вона правильно показує ітерацію всіх матчів, а не лише одну.
Роб Еванс

13
mnn прав. Це створить нескінченний цикл, якщо прапор 'g' відсутній. Будьте дуже обережні з цією функцією.
Друська

4
Я вдосконалив це, щоб зробити його подібним до re.findall () python (). Він групує всі збіги в масив масивів. Він також виправляє проблему нескінченного циклу глобального модифікатора. jsfiddle.net/ravishi/MbwpV
ravishi

5
@MichaelMikowski тепер ви просто приховали свій нескінченний цикл, але ваш код буде працювати повільно. Я б заперечував, що краще зламати код погано, щоб ви впіймали його в розробці. Введення деяких максимальних ітерацій в BS не вдається. Приховування проблем замість виправлення їх першопричини не є відповіддю.
wallacer

4
@MichaelMikowski - це не значимо повільніше, коли ви не досягаєте межі виконання. Коли ти є, це явно набагато повільніше. Я не кажу, що ваш код не працює, я кажу, що на практиці я думаю, що це принесе більше шкоди, ніж користі. Люди, що працюють в середовищі розробників, побачать, що код працює без будь-якого навантаження, незважаючи на виконання 10 000 непотрібних виконання деяких фрагментів коду. Потім вони висунуть його у виробниче середовище і задаються питанням, чому їх додаток знижується під навантаженням. На мій досвід, краще, якщо все буде порушено очевидним чином і раніше в циклі розвитку.
wallacer

58

var myString = "something format_abc";
var arr = myString.match(/\bformat_(.*?)\b/);
console.log(arr[0] + " " + arr[1]);

Це \bне зовсім те саме. (Це працює --format_foo/, але не працює format_a_b) Але я хотів показати альтернативу вашому вираженню, що чудово. Звісно, matchважливим є дзвінок.


2
Це точно навпаки. '\ b' розмежовує слова. слово = '\ w' = [a-zA-Z0-9_]. "format_a_b" - це слово.
BF

1
@BFHonestly, я додав, що "не працює на format_a_b", як задумався 6 років тому, і я не пам'ятаю, що я там мав на увазі ... :-) Я вважаю, що це означало, що "не працює aлише для захоплення ", тобто. перша алфавітна частина після format_.
PhiLho

1
Я хотів сказати, що \ b (- format_foo /} \ b не повертаються "--format_foo /", тому що "-" і "/" не є символами \ word. Але \ b (format_a_b) \ b повертаються "format_a_b "Право? Я посилаюсь на вашу текстову заяву в круглих дужках. (Не відмовились!)
BF

31

Що стосується вищезгаданих прикладів з багатоскладними дужками, я шукав відповідь тут, не отримавши того, чого хотів:

var matches = mystring.match(/(?:neededToMatchButNotWantedInResult)(matchWanted)/igm);

Подивившись на злегка заплутану функцію дзвінків з посиланням while і .push (), мені зрозуміло, що проблему можна вирішити дуже елегантно замість mystring.replace () замість (заміна НЕ суть, і навіть не зроблена , CLEAN, вбудована рекурсивна функція виклику функції для другого параметра є!):

var yourstring = 'something format_abc something format_def something format_ghi';

var matches = [];
yourstring.replace(/format_([^\s]+)/igm, function(m, p1){ matches.push(p1); } );

Після цього, я не думаю, що я ніколи не буду використовувати .match () майже навряд чи що-небудь знову.


26

І останнє, але не менш важливе, я знайшов один рядок коду, який добре працював для мене (JS ES6):

let reg = /#([\S]+)/igm; // Get hashtags.
let string = 'mi alegría es total! ✌🙌\n#fiestasdefindeaño #PadreHijo #buenosmomentos #france #paris';

let matches = (string.match(reg) || []).map(e => e.replace(reg, '$1'));
console.log(matches);

Це поверне:

['fiestasdefindeaño', 'PadreHijo', 'buenosmomentos', 'france', 'paris']

1
БУМ! Це найбільш елегантне рішення тут. Я вважав, що це краще, ніж повний підхід від Алекса, тому що він менш перспективний і більш елегантний для отримання декількох результатів. Гарна робота над цим, Себастьян Х.replace
Коді

Це працює настільки добре, що це безумовно заходить у мої утиліти :)
Коді

1
@Cody haha ​​дякую людино!
Себастьян Х.

19

Термінологія, що використовується у цій відповіді:

  • Match показує результат виконання вашого шаблону RegEx проти вашої рядка наступним чином: someString.match(regexPattern).
  • Зібрані візерунки вказують на всі відповідні частини вхідного рядка, які всі знаходяться всередині відповідного масиву. Це всі випадки вашого шаблону всередині вхідного рядка.
  • Зібрані групи позначають усі групи для лову, визначені в шаблоні RegEx. (Шаблони всередині дужок, як-от так:, /format_(.*?)/gде (.*?)була б відповідна група.) Вони знаходяться у відповідних шаблонах .

Опис

Щоб отримати доступ до відповідних груп , у кожному зі зібраних шаблонів вам потрібна функція або щось подібне до повторення матчу . Існує ряд способів зробити це, як показує багато інших відповідей. Більшість інших відповідей використовують цикл часу, щоб перебрати всі відповідні шаблони , але я думаю, що всі ми знаємо потенційні небезпеки при такому підході. Потрібно відповідати new RegExp()замість просто того самого шаблону, про який згадували лише в коментарі. Це відбувається тому, що .exec()метод поводиться аналогічно функції генератора - він зупиняється щоразу, коли є збіг , але зберігає його .lastIndexдля продовження звідти під час наступного .exec()дзвінка.

Приклади коду

Нижче наводиться приклад функції, searchStringяка повертає Arrayвсі відповідні шаблони , де кожен matchє an Arrayзі всіма відповідними групами . Замість використання циклу while я наводив приклади, що використовують як Array.prototype.map()функцію, так і більш ефективний спосіб - використовуючи звичайний for-loop.

Короткі версії (менше коду, більше синтаксичного цукру)

Вони менш ефективні, оскільки в основному вони реалізують forEach-loop замість швидшого for-loop.

// Concise ES6/ES2015 syntax
const searchString = 
    (string, pattern) => 
        string
        .match(new RegExp(pattern.source, pattern.flags))
        .map(match => 
            new RegExp(pattern.source, pattern.flags)
            .exec(match));

// Or if you will, with ES5 syntax
function searchString(string, pattern) {
    return string
        .match(new RegExp(pattern.source, pattern.flags))
        .map(match =>
            new RegExp(pattern.source, pattern.flags)
            .exec(match));
}

let string = "something format_abc",
    pattern = /(?:^|\s)format_(.*?)(?:\s|$)/;

let result = searchString(string, pattern);
// [[" format_abc", "abc"], null]
// The trailing `null` disappears if you add the `global` flag

Виконавчі версії (більше коду, менше синтаксичного цукру)

// Performant ES6/ES2015 syntax
const searchString = (string, pattern) => {
    let result = [];

    const matches = string.match(new RegExp(pattern.source, pattern.flags));

    for (let i = 0; i < matches.length; i++) {
        result.push(new RegExp(pattern.source, pattern.flags).exec(matches[i]));
    }

    return result;
};

// Same thing, but with ES5 syntax
function searchString(string, pattern) {
    var result = [];

    var matches = string.match(new RegExp(pattern.source, pattern.flags));

    for (var i = 0; i < matches.length; i++) {
        result.push(new RegExp(pattern.source, pattern.flags).exec(matches[i]));
    }

    return result;
}

let string = "something format_abc",
    pattern = /(?:^|\s)format_(.*?)(?:\s|$)/;

let result = searchString(string, pattern);
// [[" format_abc", "abc"], null]
// The trailing `null` disappears if you add the `global` flag

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


19

String#matchAll(Див проекту Stage 3/7 грудня 2018 пропозиції ), спрощує для всіх Гості можуть скористатися групами в об'єкті матчу (пом, група 0 є всім матчем, в той час як додаткові групи відповідають захопленим групам в шаблоні):

За matchAllнаявності, ви можете уникнути whileциклу і execз /g... Натомість, використовуючи matchAll, ви отримуєте назад ітератор, який ви можете використовувати з більш зручним for...of, розмаїттям масивів або Array.from()конструкціями

Цей метод дає аналогічний вихід Regex.Matchesу C #, re.finditerу Python, preg_match_allу PHP.

Дивіться демонстрацію JS (протестована в Google Chrome 73.0.3683.67 (офіційна збірка), бета-версія (64-бітна)):

var myString = "key1:value1, key2-value2!!@key3=value3";
var matches = myString.matchAll(/(\w+)[:=-](\w+)/g);
console.log([...matches]); // All match with capturing group values

У console.log([...matches])шоу

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

Ви також можете отримати значення відповідності або певні значення групи, використовуючи

let matchData = "key1:value1, key2-value2!!@key3=value3".matchAll(/(\w+)[:=-](\w+)/g)
var matches = [...matchData]; // Note matchAll result is not re-iterable

console.log(Array.from(matches, m => m[0])); // All match (Group 0) values
// => [ "key1:value1", "key2-value2", "key3=value3" ]
console.log(Array.from(matches, m => m[1])); // All match (Group 1) values
// => [ "key1", "key2", "key3" ]

ПРИМІТКА . Перегляньте деталі сумісності браузера .


Ідеальний приклад для пар ключових значень. Короткий і легкий для читання, дуже простий у використанні. Крім того, для кращої обробки помилок, спред поверне порожній масив, а не нульовий, тому більше не буде "помилка, немає властивості" довжина "нуля"
Jarrod McGuire

17

Напевно, ваш синтаксис не найкраще зберігати. FF / Gecko визначає RegExp як розширення Функції.
(FF2 пішов далеко typeof(/pattern/) == 'function')

Здається, це специфічно для FF - IE, Opera та Chrome - всі викиди для нього.

Натомість використовуйте будь-який метод, який раніше згадували інші: RegExp#execабо String#match.
Вони пропонують однакові результати:

var regex = /(?:^|\s)format_(.*?)(?:\s|$)/;
var input = "something format_abc";

regex(input);        //=> [" format_abc", "abc"]
regex.exec(input);   //=> [" format_abc", "abc"]
input.match(regex);  //=> [" format_abc", "abc"]

16

Немає потреби викликати execметод! Ви можете використовувати метод "match" прямо на рядку. Просто не забувайте дужки.

var str = "This is cool";
var matches = str.match(/(This is)( cool)$/);
console.log( JSON.stringify(matches) ); // will print ["This is cool","This is"," cool"] or something like that...

Позиція 0 містить рядок з усіма результатами. Позиція 1 містить перший збіг, представлений дужками, а позиція 2 - другий збіг, виділений у ваших дужках. Вкладені дужки складні, тому будьте обережні!


4
Без глобального прапора це повертає всі матчі, з ним ви отримаєте лише один великий, тому стежте за цим.
Shadymilkman01

8

Один вкладиш, який практичний, лише якщо у вас є одна кругла дужка:

while ( ( match = myRegex.exec( myStr ) ) && matches.push( match[1] ) ) {};

4
Чому б і ніwhile (match = myRegex.exec(myStr)) matches.push(match[1])
willlma

7

Використання вашого коду:

console.log(arr[1]);  // prints: abc
console.log(arr[0]);  // prints:  format_abc

Редагувати: Safari 3, якщо це має значення.


7

Завдяки es2018 тепер ви можете String.match()з названими групами, робить ваш регулярний вираз більш чітким, що він намагався зробити.

const url =
  '/programming/432493/how-do-you-access-the-matched-groups-in-a-javascript-regular-expression?some=parameter';
const regex = /(?<protocol>https?):\/\/(?<hostname>[\w-\.]*)\/(?<pathname>[\w-\./]+)\??(?<querystring>.*?)?$/;
const { groups: segments } = url.match(regex);
console.log(segments);

і ти отримаєш щось подібне

{протокол: "https", ім'я хоста: "stackoverflow.com", ім'я шляху: "питання / 432493 / how-do-you-access-the-match-groups-in-a-javascript-regular-izraz", querystring: " some = параметр "}


6

function getMatches(string, regex, index) {
  index || (index = 1); // default to the first capturing group
  var matches = [];
  var match;
  while (match = regex.exec(string)) {
    matches.push(match[index]);
  }
  return matches;
}


// Example :
var myString = 'Rs.200 is Debited to A/c ...2031 on 02-12-14 20:05:49 (Clear Bal Rs.66248.77) AT ATM. TollFree 1800223344 18001024455 (6am-10pm)';
var myRegEx = /clear bal.+?(\d+\.?\d{2})/gi;

// Get an array containing the first capturing group for every match
var matches = getMatches(myString, myRegEx, 1);

// Log results
document.write(matches.length + ' matches found: ' + JSON.stringify(matches))
console.log(matches);

function getMatches(string, regex, index) {
  index || (index = 1); // default to the first capturing group
  var matches = [];
  var match;
  while (match = regex.exec(string)) {
    matches.push(match[index]);
  }
  return matches;
}


// Example :
var myString = 'something format_abc something format_def something format_ghi';
var myRegEx = /(?:^|\s)format_(.*?)(?:\s|$)/g;

// Get an array containing the first capturing group for every match
var matches = getMatches(myString, myRegEx, 1);

// Log results
document.write(matches.length + ' matches found: ' + JSON.stringify(matches))
console.log(matches);


3

Ваш код працює для мене (FF3 на Mac), навіть якщо я погоджуюся з PhiLo, що регулярний вираз повинен бути:

/\bformat_(.*?)\b/

(Але, звичайно, я не впевнений, тому що не знаю контексту регулярного виразу.)


1
це розділений пробілом список, тому я подумав, що це буде добре. дивно, що цей код не працював для мене (FF3 Vista)
nickf

1
Так, по-справжньому дивно. Ви пробували його самостійно в консолі Firebug? Я маю на увазі інакше порожню сторінку.
ПЕЗ

2
/*Regex function for extracting object from "window.location.search" string.
 */

var search = "?a=3&b=4&c=7"; // Example search string

var getSearchObj = function (searchString) {

    var match, key, value, obj = {};
    var pattern = /(\w+)=(\w+)/g;
    var search = searchString.substr(1); // Remove '?'

    while (match = pattern.exec(search)) {
        obj[match[0].split('=')[0]] = match[0].split('=')[1];
    }

    return obj;

};

console.log(getSearchObj(search));

2

Вам не потрібен явний цикл для розбору декількох збігів - передайте функцію заміни як другий аргумент, як описано в String.prototype.replace(regex, func):

var str = "Our chief weapon is {1}, {0} and {2}!"; 
var params= ['surprise', 'fear', 'ruthless efficiency'];
var patt = /{([^}]+)}/g;

str=str.replace(patt, function(m0, m1, position){return params[parseInt(m1)];});

document.write(str);

m0Аргумент являє повну знайдену підрядок {0}, {1}і т.д. m1є першою групу відповідності, тобто та частина , укладена в дужках в регулярному виразі , який 0для першого матчу. І positionє початковим індексом у рядку, де була знайдена відповідна група - у даному випадку не використовується.


1

Ми можемо отримати доступ до відповідної групи у регулярних виразах, використовуючи зворотний косий рядок із наступним номером групи, що відповідає:

/([a-z])\1/

У коді \ 1, представлений відповідним першою групою ([az])


1

Рішення з однієї лінії:

const matches = (text,regex) => [...text.matchAll(regex)].map(([match])=>match)

Отже, ви можете використовувати такий спосіб (must use / g):

matches("something format_abc", /(?:^|\s)format_(.*?)(?:\s|$)/g)

результат:

[" format_abc"]


0

Я ти такий, як я, і хочу, щоб regex повернув такий об'єкт:

{
    match: '...',
    matchAtIndex: 0,
    capturedGroups: [ '...', '...' ]
}

потім перенесіть функцію знизу

/**
 * @param {string | number} input
 *          The input string to match
 * @param {regex | string}  expression
 *          Regular expression 
 * @param {string} flags
 *          Optional Flags
 * 
 * @returns {array}
 * [{
    match: '...',
    matchAtIndex: 0,
    capturedGroups: [ '...', '...' ]
  }]     
 */
function regexMatch(input, expression, flags = "g") {
  let regex = expression instanceof RegExp ? expression : new RegExp(expression, flags)
  let matches = input.matchAll(regex)
  matches = [...matches]
  return matches.map(item => {
    return {
      match: item[0],
      matchAtIndex: item.index,
      capturedGroups: item.length > 1 ? item.slice(1) : undefined
    }
  })
}

let input = "key1:value1, key2:value2 "
let regex = /(\w+):(\w+)/g

let matches = regexMatch(input, regex)

console.log(matches)


0

ВЖЕ ВИКОРИСТУЙТЕ RegExp. $ 1 ... $ n-а група, наприклад:

1.У відповідність 1-й групі Регістр $ 1

  1. Для відповідності 2-й групі Регістр $ 2

якщо ви використовуєте 3 групи в регулярному виразі likey (зверніть увагу на використання після string.match (regex))

RegExp. $ 1 RegExp. $ 2 RegExp. $ 3

 var str = "The rain in ${india} stays safe"; 
  var res = str.match(/\${(.*?)\}/ig);
  //i used only one group in above example so RegExp.$1
console.log(RegExp.$1)

//easiest way is use RegExp.$1 1st group in regex and 2nd grounp like
 //RegExp.$2 if exist use after match

var regex=/\${(.*?)\}/ig;
var str = "The rain in ${SPAIN} stays ${mainly} in the plain"; 
  var res = str.match(regex);
for (const match of res) {
  var res = match.match(regex);
  console.log(match);
  console.log(RegExp.$1)
 
}

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