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

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

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") имел особое значение, он пытался найти значение следующего параметра.

24 ответа

Решение

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

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

Вот метод, который вы можете использовать, чтобы получить 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);

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

\b это не совсем то же самое. (Работает на --format_foo/, но не работает на format_a_b) Но я хотел показать альтернативу вашему выражению, что нормально. Конечно, match Звонок это важная вещь.

И последнее, но не менее важное: я нашел одну строку кода, которая отлично сработала для меня (JS ES6):

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

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

Это вернет:

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

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

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

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

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

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

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

Описание

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

Примеры кода

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

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

Они менее производительны, так как они в основном реализуют forEachпетля вместо быстрее 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

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

Ваш синтаксис, вероятно, не лучший, чтобы сохранить. 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"]

Нет необходимости вызывать 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 имеет второе совпадение, изолированное в ваших круглых скобках. Вложенные скобки сложны, так что будьте осторожны!

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

const url =
  'https://stackru.com/questions/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", имя хоста: "stackru.com", имя пути: "questions/432493/how-do-you-you-access-the-matched-groups-in-a-javascript-регулярное выражение", строка запроса: " некоторые = параметр "}

Один вкладыш, который практичен, только если у вас есть одна пара круглых скобок:

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

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

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

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

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

Ваш код работает для меня (FF3 на Mac), даже если я согласен с PhiLho, что регулярное выражение должно быть:

/\bformat_(.*?)\b/

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

/*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));

Как сказано в @cms в ECMAScript (ECMA-262), вы можете использовать matchAll. Он возвращает итератор и помещает его в [... ] (оператор распространения) он преобразуется в массив.(это регулярное выражение извлекает URL-адреса имен файлов)

let text = `<a href="http://myhost.com/myfile_01.mp4">File1</a> <a href="http://myhost.com/myfile_02.mp4">File2</a>`;

let fileUrls = [...text.matchAll(/href="(http\:\/\/[^"]+\.\w{3})\"/g)].map(r => r[1]);

console.log(fileUrls);

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

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

1.Для совпадения с регулярным выражением 1-й группы $ 1

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

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

Регулярное выражение $ 1 Регулярное выражение $ 2 Регулярное выражение $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)
 
}

Я такой же, как я, и хочу, чтобы регулярное выражение возвращало такой объект:

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

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

/([a-z])\1/

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

Однострочное решение:

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

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

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

результат:

[" format_abc"]

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

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

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

console.log(m);

  • входнаяСтрока:
  • совпадение регулярного выражения js с группой:
            const regexPattern = /^([+-])\[(\w+)\s+([\w:]+)\]$/
    const regexMatch = inputStr.match(regexPattern)
    
  • выход:regexMatch"="["+[NSURL URLWithString:]", "+", "NSURL", "URLWithString:"]
    • regexMatch[0]= весь ввод str ="+[NSURL URLWithString:]"
    • regexMatch[1]= группа №1 =([+-])"=""+"
    • regexMatch[2]= группа №2 =(\w+)"=""NSURL"
    • regexMatch[3]= группа №3 =([\w:]+)"=""URLWithString:"

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

  • - граница слова
  • - 0+ слов
  • abc - ваше точное совпадение
  • \w* - 0+ слов
  • \b - граница слова

Ссылки: Regex: сопоставьте все слова, содержащие какое-то слово https://javascript.info/regexp-introduction

Другие вопросы по тегам