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

1096

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

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


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

Теги:

15 ответов

1317
Лучший ответ

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

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);
}
  • 99
    +1 Обратите внимание, что во втором примере вы должны использовать объект RegExp (не только "/ myregexp /"), потому что он сохраняет значение lastIndex в объекте. Без использования объекта Regexp он будет повторяться бесконечно
  • 5
    @ianaz: я не верю, что это правда? http://jsfiddle.net/weEg9/, кажется, работает на Chrome, по крайней мере.
Показать ещё 9 комментариев
155

Вот метод, который вы можете использовать для получения 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);
  • 7
    Это гораздо лучший ответ для остальных, потому что он правильно показывает итерацию по всем совпадениям, а не только получение одного.
  • 11
    Mnn правильно. Это создаст бесконечный цикл, если флаг 'g' отсутствует. Будьте очень осторожны с этой функцией.
Показать ещё 10 комментариев
50

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

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

  • 1
    Это точно наоборот. '\ b' разграничивает слова. слово = '\ w' = [a-zA-Z0-9_]. "format_a_b" это слово.
  • 0
    @BFHonestly, я добавил «не работает на format_a_b » как после того, как думали , 6 лет назад, и я не помню , что я имел в виду там ... :-) Я предполагаю , что это означает «не работает , чтобы захватить только a ", т.е. первая алфавитная часть после format_ .
Показать ещё 1 комментарий
24

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

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

Посмотрев на несколько запутанные вызовы функций с while и .push() выше, мне стало ясно, что проблема может быть решена очень элегантно с помощью mystring.replace() вместо этого (замена не является точкой, а isn ' t даже сделано, функция 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(), чтобы больше ничего не сделать.

16

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

И последнее, но не менее важное: я нашел одну строку кода, которая отлично сработала для меня (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']
11

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

  • Соответствие указывает результат запуска вашего шаблона RegEx против вашей строки следующим образом: someString.match(regexPattern).
  • Соответствующие шаблоны указывают все согласованные части входной строки, которые все находятся внутри массива match. Это все экземпляры вашего шаблона внутри входной строки.
  • Соответствующие группы указывают на то, что все группы пойманы, определенные в шаблоне 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 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

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

9

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

  • 1
    Это работает и чувствует себя более естественно.
  • 2
    Без глобального флага это возвращает все совпадения, с ним вы получите только один большой, так что следите за этим.
7

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

while ( ( match = myRegex.exec( myStr ) ) && matches.push( match[1] ) ) {};
  • 3
    Почему бы не while (match = myRegex.exec(myStr)) matches.push(match[1])
  • 0
    @willlma Да! ..
5

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

Использование вашего кода:

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

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

2

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

/\bformat_(.*?)\b/

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

  • 0
    это список, разделенный пробелами, поэтому я решил, что \ s будет в порядке. странно, что этот код не работал для меня (FF3 Vista)
  • 0
    Да, действительно странно. Вы пробовали это самостоятельно в консоли Firebug? Я имею в виду пустую страницу.
1
/*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));
0
function get_transaction_type_medium(sms_body) {
    if (sms_body.toLowerCase().includes("is debited to a/c") && sms_body.toLowerCase().includes("at atm")) {
        return ["ATM", "M"];
    }
    else {
        return [null, null];
    }
}
0

Посмотрите эту ссылку на уроке 12:

Вы можете группировать символы, используя специальные ( и ) (круглые скобки). Чтобы записать файл изображения, напишите выражение ^(IMG(\d+))\.png$.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню