Как вы получаете доступ к соответствующим группам в регулярном выражении JavaScript?

1369

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

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» не определено, кажется очень странным поведением, но оно не имеет прямого отношения к этому вопросу, поэтому я открыл новый вопрос. Почему совпадающая подстрока возвращает «undefined» в JavaScript? ,


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

nickf
источник

Ответы:

1676

Вы можете получить доступ к группам захвата следующим образом:

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 упоминался в обсуждениях предложения .

CMS
источник
114
+1 Обратите внимание, что во втором примере вы должны использовать объект RegExp (не только «/ myregexp /»), потому что он сохраняет значение lastIndex в объекте. Без использования объекта Regexp он будет повторяться бесконечно
ianaz
7
@ianaz: я не верю, что это правда? http://jsfiddle.net/weEg9/, кажется, работает на Chrome, по крайней мере.
spinningarrow
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
Я улучшил это, чтобы сделать его похожим на python re.findall (). Он группирует все совпадения в массив массивов. Также исправлена ​​проблема с бесконечным циклом глобального модификатора. jsfiddle.net/ravishi/MbwpV
Равиши
5
@MichaelMikowski теперь вы только что скрыли свой бесконечный цикл, но ваш код будет работать медленно. Я бы сказал, что лучше разбивать код плохим способом, чтобы вы поймали его при разработке. Ввод некоторых библейских максимальных итераций является небрежным. Сокрытие проблем вместо устранения их первопричины не является ответом.
Уоллесер
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звонок - важная вещь.

PhiLho
источник
2
Это точно наоборот. '\ b' разграничивает слова. слово = '\ w' = [a-zA-Z0-9_]. "format_a_b" - это слово.
BF
1
@BFHonestly, я добавил "не работает format_a_b", как после 6 лет назад, и я не помню, что я имел в виду ... :-) Полагаю, это означало "не работает aтолько для захвата ", то есть. первая алфавитная часть после format_.
Филип
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 () вместо этого (замена - это НЕ точка, и даже не сделано , ЧИСТАЯ, опция встроенного рекурсивного вызова функции для второго параметра есть!):

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 () для чего-либо.

Alexz
источник
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
БУМ! Это наиболее элегантное решение здесь. Я обнаружил, что это лучше, чем полноценный подход Alexz, потому что он менее перспективный и более элегантный для нескольких результатов. Хорошая работа в этом, Себастьен Х.replace
Коди
Это работает так хорошо, что это определенно входит в мои утилиты :)
Коди
1
@ Коди, ха-ха, спасибо, мужик!
Себастьен Х.
19

Терминология, использованная в этом ответе:

  • Match показывает результат выполнения вашего шаблона RegEx против вашей строки следующим образом: someString.match(regexPattern).
  • Совпадающие шаблоны указывают на все совпадающие части входной строки, которые все находятся в массиве совпадений . Это все экземпляры вашего шаблона внутри входной строки.
  • Совпадающие группы указывают на все группы, которые нужно поймать, определенные в шаблоне RegEx. (Шаблоны в круглых скобках, например, так /format_(.*?)/g, где (.*?)будет сопоставленная группа.) Они находятся в согласованных шаблонах .

Описание

Чтобы получить доступ к сопоставленным группам , в каждом из сопоставленных шаблонов вам нужна функция или нечто подобное для итерации по совпадению . Есть много способов сделать это, как показывают многие другие ответы. Большинство других ответов используют цикл while для итерации по всем сопоставленным шаблонам , но я думаю, что мы все знаем о потенциальных опасностях такого подхода. Необходимо сопоставлять new RegExp()вместо самого шаблона, который упоминается только в комментарии. Это связано с тем, что .exec()метод ведет себя аналогично функции генератора - он останавливается каждый раз, когда происходит совпадение , но сохраняет его .lastIndexдля продолжения оттуда при следующем .exec()вызове.

Примеры кода

Ниже приведен пример функции, searchStringкоторая возвращает один Arrayиз всех сопоставленных шаблонов , где каждый matchпредставляет собой 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 (больше кода, меньше синтаксического сахара)

// 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(см. черновой вариант 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" ]

ПРИМЕЧАНИЕ . См. Сведения о совместимости браузера .

Виктор Стрибьев
источник
Прекрасный пример для пар ключ-значение. Краткий и легкий для чтения, очень простой в использовании. Кроме того, для лучшей обработки ошибок спред будет возвращать пустой массив, а не ноль, поэтому больше нет «ошибки, нет свойства« длина »нуля»
Джаррод МакГуайр,
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])
17
7

Используя ваш код:

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

Изменить: Safari 3, если это имеет значение.

eyelidlessness
источник
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);

и вы получите что-то вроде

{protocol: "https", имя хоста: "stackoverflow.com", имя пути: "questions / 432493 / how-do-you-you-access-the-matched-groups-in-a-javascript-регулярное выражение", строка запроса: " некоторые = параметр "}

Дэвид Чунг
источник
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/

(Но, конечно, я не уверен, потому что я не знаю контекст регулярного выражения.)

PEZ
источник
1
это разделенный пробелами список, поэтому я подумал, что \ s будет в порядке. странно, что этот код не работал для меня (FF3 Vista)
nickf
1
Да, действительно странно. Вы пробовали это самостоятельно в консоли Firebug? Я имею в виду пустую страницу.
PEZ
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является начальным индексом в строке, где была найдена соответствующая группа - в этом случае не используется.

ccpizza
источник
1

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

/([a-z])\1/

В коде \ 1 представлено соответствие первой группе ([az])

Г-н А. Барик
источник
1

Одноканальное решение:

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

Таким образом, вы можете использовать этот способ (необходимо использовать / г):

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

результат:

[" format_abc"]
Кайо Сантос
источник
0

Получить все вхождения группы

let m=[], s = "something format_abc  format_def  format_ghi";

s.replace(/(?:^|\s)format_(.*?)(?:\s|$)/g, (x,y)=> m.push(y));

console.log(m);

Камил Келчевски
источник
0

Если вы похожи на меня и хотите, чтобы регулярные выражения возвращали объект как этот:

{
    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)

Delcon
источник
0

ПРОСТО ИСПОЛЬЗУЙТЕ RegExp. $ 1 ... $ n-ю группу, например:

1. Для соответствия 1-й группе RegExp. $ 1

  1. Для соответствия 2-й группе RegExp. $ 2

если вы используете 3 группы в регулярных выражениях подобно (обратите внимание на использование после string.match (регулярное выражение))

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)
 
}

ßãlãjî
источник