Como você acessa os grupos correspondentes em uma expressão regular do JavaScript?

1368

Quero corresponder a uma parte de uma string usando uma expressão regular e acessar essa substring entre parênteses:

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 (!!!)

O que estou fazendo errado?


Eu descobri que não havia nada errado com o código de expressão regular acima: a string real com a qual eu estava testando era a seguinte:

"date format_%A"

Relatar que "% A" é indefinido parece um comportamento muito estranho, mas não está diretamente relacionado a essa pergunta. Por isso, abri um novo. Por que uma substring correspondente retorna "indefinida" em JavaScript? .


O problema era que console.logleva seus parâmetros como uma printfinstrução e, como a string que eu estava registrando ( "%A") tinha um valor especial, estava tentando encontrar o valor do próximo parâmetro.

nickf
fonte

Respostas:

1675

Você pode acessar grupos de captura como este:

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

E se houver várias correspondências, você poderá iterar sobre elas:

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

Edição: 2019-09-10

Como você pode ver, a maneira de iterar em várias correspondências não foi muito intuitiva. Isso levou à proposta do String.prototype.matchAllmétodo. Espera-se que este novo método seja fornecido na especificação ECMAScript 2020 . Ele nos fornece uma API limpa e resolve vários problemas. Ele foi iniciado nos principais navegadores e mecanismos JS como Chrome 73+ / Node 12+ e Firefox 67+.

O método retorna um iterador e é usado da seguinte maneira:

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

Como ele retorna um iterador, podemos dizer que é preguiçoso, isso é útil ao lidar com um número particularmente grande de grupos de captura ou cadeias muito grandes. Mas se você precisar, o resultado poderá ser facilmente transformado em uma matriz usando a sintaxe de propagação ou o Array.frommétodo:

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

Enquanto isso, enquanto esta proposta obtém suporte mais amplo, você pode usar o pacote oficial de calço .

Além disso, o funcionamento interno do método é simples. Uma implementação equivalente usando uma função de gerador seria a seguinte:

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

Uma cópia do regexp original é criada; isso é para evitar efeitos colaterais devido à mutação da lastIndexpropriedade ao passar pelas correspondências múltiplas.

Além disso, precisamos garantir que o regexp tenha o sinalizador global para evitar um loop infinito.

Também fico feliz em ver que mesmo essa pergunta sobre o StackOverflow foi referenciada nas discussões da proposta .

CMS
fonte
114
+1 Observe que, no segundo exemplo, você deve usar o objeto RegExp (não apenas "/ myregexp /"), porque ele mantém o valor lastIndex no objeto. Sem usar o objeto Regexp ele irá interagir infinitamente
ianaz
7
@ianaz: Não acredito que seja verdade? http://jsfiddle.net/weEg9/ parece funcionar no Chrome, pelo menos.
Spinningarrow
16
Por que o acima, em vez de var match = myString.match(myRegexp); // alert(match[1]):?
JohnAllen
29
Não há necessidade de "new RegExp" explícito; no entanto, o loop infinito ocorrerá a menos que / g seja especificado.
George C
4
Outra maneira de não executar um loop infinito é atualizar explicitamente as strings, por exemplo:string = string.substring(match.index + match[0].length)
Olga
186

Aqui está um método que pode utilizar para obter o n º grupo de captura para cada jogo:

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

Mathias Bynens
fonte
12
Essa é uma resposta muito superior às demais porque mostra corretamente a iteração em todas as correspondências, em vez de apenas obter uma.
Rob Evans
13
MNN está certo. Isso produzirá um loop infinito se o sinalizador 'g' não estiver presente. Tenha muito cuidado com esta função.
Druska
4
Eu melhorei isso para torná-lo semelhante ao re.findall () do python. Agrupa todas as correspondências em uma matriz de matrizes. Ele também corrige o problema de loop infinito do modificador global. jsfiddle.net/ravishi/MbwpV
ravishi
5
@MichaelMikowski agora você acabou de ocultar seu loop infinito, mas seu código será lento. Eu diria que é melhor ter uma quebra de código de maneira ruim, para que você a pegue no desenvolvimento. Colocar algumas iterações máximas de bs é desleixado. Ocultar problemas em vez de corrigir sua causa raiz não é a resposta.
Wallacer 29/10/14
4
@ MichaelMikowski que não é significativamente mais lento quando você não está atingindo o limite de execução. Quando você está, é claramente muito mais lento. Não estou dizendo que seu código não funciona, estou dizendo que, na prática, acho que causará mais mal do que bem. As pessoas que trabalham em um ambiente de desenvolvimento verão o código funcionando sem carga, apesar de executar 10.000 execuções desnecessárias de algum pedaço de código. Em seguida, eles o enviam para um ambiente de produção e se perguntam por que o aplicativo fica inativo. Na minha experiência, é melhor se as coisas quebrarem de uma maneira óbvia e mais cedo no ciclo de desenvolvimento.
Wallacer
58

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

O \bnão é exatamente a mesma coisa. (Funciona --format_foo/, mas não funciona format_a_b) Mas eu queria mostrar uma alternativa à sua expressão, o que é bom. Obviamente, a matchligação é importante.

PhiLho
fonte
2
É exatamente o contrário. '\ b' delimita palavras. word = '\ w' = [a-zA-Z0-9_]. "format_a_b" é uma palavra.
BF
1
@BFHonestly, eu adicionei "não funciona format_a_b", como pensado há 6 anos, e não me lembro do que eu quis dizer lá ... :-) Suponho que isso significava "não funciona aapenas para capturar ", ie a primeira parte alfabética depois format_.
PhiLho
1
Eu queria dizer que \ b (- format_foo /} \ b não retorna "--format_foo /" porque "-" e "/" não são caracteres \ word. Mas \ b (format_a_b) \ b retornam "format_a_b ". Certo? Refiro-me à sua declaração de texto entre colchetes. (Não votei abaixo!)
BF
31

Com relação aos exemplos de parênteses com várias correspondências acima, eu estava procurando uma resposta aqui depois de não obter o que queria:

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

Depois de examinar as chamadas de função levemente complicadas com while e .push () acima, ocorreu-me que o problema pode ser resolvido de maneira muito elegante com mystring.replace () (a substituição NÃO é o objetivo e nem está concluída) , a opção de chamada de função recursiva CLEAN, integrada para o segundo parâmetro é!):

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

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

Depois disso, acho que nunca mais vou usar .match () para quase nada.

Alexz
fonte
26

Por último, mas não menos importante, encontrei uma linha de código que funcionou bem para mim (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);

Isso retornará:

['fiestasdefindeaño', 'PadreHijo', 'buenosmomentos', 'france', 'paris']
Sebastien H.
fonte
1
ESTRONDO! Essa é a solução mais elegante aqui. Achei isso melhor do que a replaceabordagem completa de Alexz, porque essa é menos moderna e mais elegante para vários resultados. Bom trabalho nisso, Sebastien H.
Cody
Isso funciona tão bem que definitivamente está entrando nos meus utils :)
Cody
1
@Cody haha ​​graças cara!
Sebastien H.
19

Terminologia usada nesta resposta:

  • Jogo indica o resultado da execução de seu padrão de RegEx contra sua seqüência assim: someString.match(regexPattern).
  • Os padrões correspondentes indicam todas as partes correspondentes da sequência de entrada, todas residindo dentro do matriz de correspondências . Todas estas são instâncias do seu padrão dentro da sequência de entrada.
  • Grupos correspondentes indicam todos os grupos a serem capturados, definidos no padrão RegEx. (Os padrões entre parênteses, assim:, /format_(.*?)/gonde (.*?)seria um grupo correspondente.) Estes residem dentro dos padrões correspondentes .

Descrição

Para obter acesso aos grupos correspondentes , em cada um dos padrões correspondentes , você precisa de uma função ou algo semelhante para iterar durante a correspondência . Existem várias maneiras de fazer isso, como mostram muitas das outras respostas. A maioria das outras respostas usa um loop while para iterar todos os padrões correspondentes , mas acho que todos conhecemos os perigos em potencial com essa abordagem. É necessário fazer a comparação entre a e new RegExp()não apenas o próprio padrão, mencionado apenas em um comentário. Isso ocorre porque o .exec()método se comporta de maneira semelhante a uma função de gerador - ele para sempre que há uma correspondência , mas mantém sua.lastIndex a continuar a partir daí na próxima .exec()chamada.

Exemplos de código

Abaixo está um exemplo de uma função searchStringque retorna um Arrayde todos os padrões correspondentes , onde cada matchum é um Arraycom todos os grupos correspondentes correspondentes . Em vez de usar um loop while, forneci exemplos usando ambos osArray.prototype.map() função e uma maneira mais foreficiente - usando um loop simples .

Versões concisas (menos código, mais açúcar sintático)

Eles têm menos desempenho, porque basicamente implementam um forEachloop em vez do mais rápido for.

// 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

Versões de desempenho (mais código, menos açúcar sintático)

// 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

Ainda tenho que comparar essas alternativas com as mencionadas anteriormente nas outras respostas, mas duvido que essa abordagem tenha menos desempenho e menos falhas do que as outras.

Daniel Hallgren
fonte
19

String#matchAll(consulte a proposta do Estágio 3/7 de dezembro de 2018 ), simplifica o acesso a todos os grupos no objeto de correspondência (lembre-se de que o grupo 0 é a correspondência inteira, enquanto outros grupos correspondem aos grupos de captura no padrão):

Com matchAlldisponível, você pode evitar o whileloop e execcom /g... Em vez disso, usando matchAll, você recupera um iterador que pode ser usado com as construções mais convenientes for...of, dispersão de matriz ou Array.from()construções

Este método produz uma saída semelhante à Regex.Matchesdo C #, re.finditerno Python, preg_match_allno PHP.

Veja uma demonstração JS (testada no Google Chrome 73.0.3683.67 (versão oficial), beta (64 bits)):

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

Os console.log([...matches])shows

insira a descrição da imagem aqui

Você também pode obter um valor de correspondência ou valores de grupo específicos usando

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

NOTA : Veja os detalhes de compatibilidade do navegador .

Wiktor Stribiżew
fonte
Exemplo perfeito para pares de valores-chave. Conciso e fácil de ler, muito simples de usar. Além disso, para uma melhor manipulação de erros, a propagação retornará uma matriz vazia em vez de nula, portanto, não haverá mais 'erro, nenhuma propriedade "comprimento" de nula' '
Jarrod McGuire 29/03/19
17

Sua sintaxe provavelmente não é a melhor para manter. FF / Gecko define RegExp como uma extensão da Function.
(FF2 foi tão longetypeof(/pattern/) == 'function' )

Parece que isso é específico para o FF - IE, Opera e Chrome - todos lançam exceções.

Em vez disso, use um dos métodos mencionados anteriormente por outras pessoas: RegExp#execou String#match.
Eles oferecem os mesmos resultados:

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"]
Jonathan Lonowski
fonte
16

Não há necessidade de chamar o execmétodo! Você pode usar o método "match" diretamente na string. Só não esqueça os parênteses.

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...

A posição 0 tem uma sequência com todos os resultados. A posição 1 tem a primeira correspondência representada por parênteses e a posição 2 tem a segunda correspondência isolada entre parênteses. Parênteses aninhados são complicados, então cuidado!

Andre Carneiro
fonte
4
Sem a bandeira global, isso retornará todas as correspondências; com ela, você terá apenas uma grande, então fique atento a isso.
precisa saber é o seguinte
8

Um liner único que é prático apenas se você tiver um único par de parênteses:

while ( ( match = myRegex.exec( myStr ) ) && matches.push( match[1] ) ) {};
Nabil Kadimi
fonte
4
Por que nãowhile (match = myRegex.exec(myStr)) matches.push(match[1])
willlma
7

Usando seu código:

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

Edit: Safari 3, se isso importa.

ausência de pálpebra
fonte
7

Com es2018, agora você pode String.match()com grupos nomeados, torna seu regex mais explícito do que estava tentando fazer.

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

e você terá algo como

{protocol: "https", nome do host: "stackoverflow.com", nome do caminho: "questions / 432493 / como fazer para acessar os grupos correspondentes em uma expressão regular de javascript", querystring: " some = parâmetro "}

David Cheung
fonte
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);

Jack
fonte
3

Seu código funciona para mim (FF3 no Mac), mesmo que eu concorde com o PhiLo de que o regex provavelmente deve ser:

/\bformat_(.*?)\b/

(Mas, é claro, não tenho certeza porque não conheço o contexto da regex.)

PEZ
fonte
1
é uma lista separada por espaços, então achei que seria bom. estranho que esse código não estava funcionando para mim (FF3 Vista)
nickf
1
Sim, verdadeiramente estranho. Você já tentou por conta própria no console do Firebug? De uma página vazia, quero dizer.
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));
Pawel Kwiecien
fonte
2

Você realmente não precisa de um loop explícito para analisar várias correspondências - passe uma função de substituição como o segundo argumento, conforme descrito em 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);

O m0argumento representa o substring combinada completa {0}, {1}etc. m1representa o primeiro grupo correspondente, ou seja, a parte entre colchetes no regex que é 0para o primeiro jogo. E positioné o índice inicial dentro da string em que o grupo correspondente foi encontrado - não utilizado neste caso.

ccpizza
fonte
1

Podemos acessar o grupo correspondente em expressões regulares usando barra invertida seguida pelo número do grupo correspondente:

/([a-z])\1/

No código \ 1 representado correspondente ao primeiro grupo ([az])

Md. A. Barik
fonte
1

Solução de uma linha:

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

Então você pode usar desta maneira (deve usar / g):

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

resultado:

[" format_abc"]
Caio Santos
fonte
0

Obter toda a ocorrência do grupo

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

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

console.log(m);

Kamil Kiełczewski
fonte
0

Eu você é como eu e gostaria que o regex retornasse um Objeto como este:

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

depois corte a função de baixo

/**
 * @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
fonte
0

APENAS USAR RegExp. $ 1 ... $ n º grupo, por exemplo:

1.Para corresponder à 1ª Reg. Do grupo

  1. Para corresponder ao 2º grupo RegExp. $ 2

se você usar 3 grupos no regex likey (observe o uso após 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)
 
}

ßãlãjî
fonte