RegEx para extrair todas as correspondências da string usando o RegExp.exec

175

Estou tentando analisar o seguinte tipo de string:

[key:"val" key2:"val2"]

onde há chave arbitrária: pares "val" dentro. Eu quero pegar o nome da chave e o valor. Para aqueles curiosos, estou tentando analisar o formato de banco de dados do task warrior.

Aqui está a minha string de teste:

[description:"aoeu" uuid:"123sth"]

que visa destacar que qualquer coisa pode estar em uma chave ou valor além do espaço, sem espaços ao redor dos dois pontos e os valores sempre estão entre aspas duplas.

No nó, esta é minha saída:

[deuteronomy][gatlin][~]$ node
> var re = /^\[(?:(.+?):"(.+?)"\s*)+\]$/g
> re.exec('[description:"aoeu" uuid:"123sth"]');
[ '[description:"aoeu" uuid:"123sth"]',
  'uuid',
  '123sth',
  index: 0,
  input: '[description:"aoeu" uuid:"123sth"]' ]

Mas description:"aoeu"também corresponde a esse padrão. Como posso recuperar todos os jogos?

gatlin
fonte
Pode ser que meu regex esteja errado e / ou simplesmente usando incorretamente os recursos de regex no JavaScript. Isso parece funcionar:> var s = "Quinze tem 15 e oito são 8"; > var re = / \ d + / g; > var m = s.match (re); m = ['15', '8']
gatlin
6
O Javascript agora tem uma função .match (): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Usado assim:"some string".match(/regex/g)
Stefnotch 5/16/16

Respostas:

237

Continue chamando re.exec(s)em um loop para obter todas as correspondências:

var re = /\s*([^[:]+):\"([^"]+)"/g;
var s = '[description:"aoeu" uuid:"123sth"]';
var m;

do {
    m = re.exec(s);
    if (m) {
        console.log(m[1], m[2]);
    }
} while (m);

Experimente com este JSFiddle: https://jsfiddle.net/7yS2V/

Lawnsea
fonte
8
Por que não em whilevez de do … while?
Gumbo 12/06
15
O uso de um loop while torna um pouco estranho inicializar m. Você precisa escrever while(m = re.exec(s)), que é um IMO antipadrão, ou precisa escrever m = re.exec(s); while (m) { ... m = re.exec(s); }. Eu prefiro o do ... if ... whileidioma, mas outras técnicas funcionariam também.
lawnsea
14
fazer isso no cromo resultou na falha da minha guia.
EdgeCaseBerg
47
@EdgeCaseBerg Você precisa ter o gsinalizador definido, caso contrário, o ponteiro interno não será movido para frente. Docs .
Tim
12
Outro ponto é que, se o regex pode combinar string vazia será um loop infinito
FabioCosta
139

str.match(pattern), se patterntiver o sinalizador global g, retornará todas as correspondências como uma matriz.

Por exemplo:

const str = 'All of us except @Emran, @Raju and @Noman was there';
console.log(
  str.match(/@\w*/g)
);
// Will log ["@Emran", "@Raju", "@Noman"]

Anis
fonte
15
Cuidado: as correspondências não são objetos correspondentes, mas as seqüências correspondentes. Por exemplo, não há acesso para os grupos "All of us except @Emran:emran26, @Raju:raju13 and @Noman:noman42".match(/@(\w+):(\w+)/g)(que voltará ["@Emran:emran26", "@Raju:raju13", "@Noman:noman42"])
madprog
4
@madprog, Certo, é a maneira mais fácil, mas não é adequada quando os valores do grupo são essenciais.
Anis
1
Isso não está funcionando para mim. Eu só recebo a primeira partida.
Anthony Roberts
7
@AnthonyRoberts você deve adicionar a bandeira "g". /@\w/gounew RegExp("@\\w", "g")
Aruna Herath
88

Para percorrer todas as correspondências, você pode usar a replacefunção:

var re = /\s*([^[:]+):\"([^"]+)"/g;
var s = '[description:"aoeu" uuid:"123sth"]';

s.replace(re, function(match, g1, g2) { console.log(g1, g2); });
Christophe
fonte
Eu acho que é muito complicado. No entanto, é bom saber sobre diferentes maneiras de fazer uma coisa simples (voto positivo na sua resposta).
Arashsoft 12/12/16
24
É um código contra-intuitivo. Você não está "substituindo" nada em nenhum sentido significativo. É apenas explorar a função some para um propósito diferente.
Luke Maurer
6
@dudewad se os engenheiros estavam apenas seguindo as regras sem pensar fora da caixa, nós nem estaria pensando em visitar outros planetas agora ;-)
Christophe
1
@dudewad desculpe, eu não vejo a parte preguiçosa aqui. Se o mesmo método exato fosse chamado "processo" em vez de "substituir", você estaria bem com ele. Receio que você esteja preso à terminologia.
Christophe
1
@Christophe Eu definitivamente não estou preso à terminologia. Estou preso no código limpo. O uso de itens destinados a uma finalidade para uma finalidade diferente é chamado de "hacky" por um motivo. Ele cria um código confuso, difícil de entender e, na maioria das vezes, sofre desempenho. O fato de você ter respondido a essa pergunta sem uma regex por si só a torna uma resposta inválida, pois o OP está pedindo como fazê-lo com a regex. Acho importante, no entanto, manter essa comunidade com um alto padrão, e é por isso que mantenho o que disse acima.
Dudewad 19/09/19
56

Esta é uma solução

var s = '[description:"aoeu" uuid:"123sth"]';

var re = /\s*([^[:]+):\"([^"]+)"/g;
var m;
while (m = re.exec(s)) {
  console.log(m[1], m[2]);
}

Isso é baseado na resposta do Lawnsea, mas mais curto.

Observe que o sinalizador `g 'deve ser definido para mover o ponteiro interno para frente através de invocações.

lovasoa
fonte
17
str.match(/regex/g)

retorna todas as correspondências como uma matriz.

Se, por algum motivo misterioso, você precisar das informações adicionais exec, como alternativa às respostas anteriores, poderá fazê-lo com uma função recursiva em vez de um loop da seguinte maneira (que também parece mais interessante).

function findMatches(regex, str, matches = []) {
   const res = regex.exec(str)
   res && matches.push(res) && findMatches(regex, str, matches)
   return matches
}

// Usage
const matches = findMatches(/regex/g, str)

conforme declarado nos comentários anteriores, é importante ter gno final da definição de regex para mover o ponteiro para frente em cada execução.

noego
fonte
1
sim. aparência recursiva elegante e mais fria. Os loops iterativos são diretos, mais fáceis de manter e depurar.
21719 Andy N
11

Finalmente estamos começando a ver uma matchAllfunção interna, veja aqui a tabela de descrição e compatibilidade . Parece que, a partir de maio de 2020, Chrome, Edge, Firefox e Node.js (mais de 12 anos) são suportados, mas não o IE, Safari e Opera. Parece que foi redigido em dezembro de 2018, portanto, espere algum tempo para alcançar todos os navegadores, mas acredito que chegará lá.

O built-in matchAll função interna é boa porque retorna uma iterável . Também retorna grupos de captura para cada partida! Então você pode fazer coisas como

// get the letters before and after "o"
let matches = "stackoverflow".matchAll(/(\w)o(\w)/g);

for (match of matches) {
    console.log("letter before:" + match[1]);
    console.log("letter after:" + match[2]);
}

arrayOfAllMatches = [...matches]; // you can also turn the iterable into an array

Também parece que todo objeto de correspondência usa o mesmo formato que match(). Portanto, cada objeto é uma matriz dos grupos de correspondência e captura, além de três propriedades adicionaisindex , input, e groups. Então parece:

[<match>, <group1>, <group2>, ..., index: <match offset>, input: <original string>, groups: <named capture groups>]

Para mais informações matchAll, também há uma página de desenvolvedores do Google . Existem também polyfills / calços disponíveis.

woojoo666
fonte
Eu realmente gosto disso, mas ele ainda não chegou ao Firefox 66.0.3. Caniuse também não tem uma lista de suporte. Estou ansioso por este. Eu o vejo trabalhando no Chromium 74.0.3729.108.
Lonnie Best
1
@LonnieBest Sim, você pode ver a seção de compatibilidade da página MDN que eu vinculei . Parece que o Firefox começou a suportá-lo na versão 67. Ainda não recomendaria usá-lo se você estiver tentando enviar um produto. Há polyfills / calços disponíveis, que eu adicionei a minha resposta
woojoo666
10

Baseado na função de Agus, mas prefiro retornar apenas os valores correspondentes:

var bob = "&gt; bob &lt;";
function matchAll(str, regex) {
    var res = [];
    var m;
    if (regex.global) {
        while (m = regex.exec(str)) {
            res.push(m[1]);
        }
    } else {
        if (m = regex.exec(str)) {
            res.push(m[1]);
        }
    }
    return res;
}
var Amatch = matchAll(bob, /(&.*?;)/g);
console.log(Amatch);  // yeilds: [&gt;, &lt;]
prumo
fonte
8

Os iteráveis ​​são mais agradáveis:

const matches = (text, pattern) => ({
  [Symbol.iterator]: function * () {
    const clone = new RegExp(pattern.source, pattern.flags);
    let match = null;
    do {
      match = clone.exec(text);
      if (match) {
        yield match;
      }
    } while (match);
  }
});

Uso em um loop:

for (const match of matches('abcdefabcdef', /ab/g)) {
  console.log(match);
}

Ou se você deseja uma matriz:

[ ...matches('abcdefabcdef', /ab/g) ]
sdgfsdh
fonte
1
if (m)if (match)
Erro de digitação
As matrizes já são iteráveis; portanto, todos os que retornam uma matriz de correspondências também retornam iteráveis. O melhor é que, se você registra um array em um console, o navegador pode realmente imprimir o conteúdo. Mas consola registrando um iterable genérico só fica você [object Object] {...}
StJohn3D
Todas as matrizes são iteráveis, mas nem todas as iteráveis ​​são matrizes. Um iterável é superior se você não sabe o que o chamador precisará fazer. Por exemplo, se você deseja apenas a primeira correspondência, uma iterável é mais eficiente.
sdgfsdh
seu sonho está se tornando realidade, os navegadores estão lançando suporte para um built-in matchAllque retorna um iterável : D
woojoo666
1
Encontrei esta resposta pós-matchAll implementação. Escrevi algum código para o navegador JS que o suportava, mas o Node na verdade não. Isso se comporta de forma idêntica a matchAll, então eu não tive que reescrever coisas - Saúde!
user37309
8

Se você tem ES9

(Ou seja, se o seu sistema: Chrome, Node.js, Firefox, etc suporta EcmaScript 2019 ou posterior)

Use o novoyourString.matchAll( /your-regex/ ) .

Se você não tem ES9

Se você possui um sistema mais antigo, aqui está uma função para facilitar a cópia e colar

function findAll(regexPattern, sourceString) {
    let output = []
    let match
    // make sure the pattern has the global flag
    let regexPatternWithGlobal = RegExp(regexPattern,"g")
    while (match = regexPatternWithGlobal.exec(sourceString)) {
        // get rid of the string copy
        delete match.input
        // store the match data
        output.push(match)
    } 
    return output
}

exemplo de uso:

console.log(   findAll(/blah/g,'blah1 blah2')   ) 

saídas:

[ [ 'blah', index: 0 ], [ 'blah', index: 6 ] ]
Jeff Hykin
fonte
5

Aqui está a minha função para obter as correspondências:

function getAllMatches(regex, text) {
    if (regex.constructor !== RegExp) {
        throw new Error('not RegExp');
    }

    var res = [];
    var match = null;

    if (regex.global) {
        while (match = regex.exec(text)) {
            res.push(match);
        }
    }
    else {
        if (match = regex.exec(text)) {
            res.push(match);
        }
    }

    return res;
}

// Example:

var regex = /abc|def|ghi/g;
var res = getAllMatches(regex, 'abcdefghi');

res.forEach(function (item) {
    console.log(item[0]);
});
Agus Syahputra
fonte
Essa solução evita loops infinitos quando você esquece de adicionar o sinalizador global.
user68311
2

Desde o ES9, agora existe uma maneira melhor e mais simples de obter todas as correspondências, juntamente com informações sobre os grupos de captura e seu índice:

const string = 'Mice like to dice rice';
const regex = /.ice/gu;
for(const match of string.matchAll(regex)) {
    console.log(match);
}

// ["ratos", índice: 0, entrada: "ratos gostam de cortar arroz", grupos: indefinidos]

// ["dados", índice: 13, entrada: "camundongos gostam de cortar arroz", grupos: indefinidos]

// ["arroz", índice: 18, entrada: "camundongos gostam de cortar arroz", grupos: indefinidos]

Atualmente, ele é suportado no Chrome, Firefox, Opera. Dependendo de quando você ler isso, verifique este link para ver seu suporte atual.

iuliu.net
fonte
Soberbo! Mas ainda é importante ter em mente que a regex deve ter um sinalizador ge lastIndexdeve ser redefinida para 0 antes da chamada de matchAll.
N.Kudryavtsev
1

Usa isto...

var all_matches = your_string.match(re);
console.log(all_matches)

Ele retornará uma matriz de todas as correspondências ... Isso funcionaria muito bem ... Mas lembre-se de que isso não levará em conta os grupos.

Subham Debnath
fonte
0

Eu recomendaria definitivamente o uso da função String.match () e a criação de um RegEx relevante para ela. Meu exemplo é com uma lista de strings, que geralmente é necessária ao verificar as entradas do usuário em busca de palavras-chave e frases.

    // 1) Define keywords
    var keywords = ['apple', 'orange', 'banana'];

    // 2) Create regex, pass "i" for case-insensitive and "g" for global search
    regex = new RegExp("(" + keywords.join('|') + ")", "ig");
    => /(apple|orange|banana)/gi

    // 3) Match it against any string to get all matches 
    "Test string for ORANGE's or apples were mentioned".match(regex);
    => ["ORANGE", "apple"]

Espero que isto ajude!

Sebastian Scholl
fonte
0

Isso realmente não vai ajudar no seu problema mais complexo, mas estou postando isso de qualquer maneira, porque é uma solução simples para pessoas que não estão fazendo uma pesquisa global como você.

Simplifiquei a expressão regular na resposta para ficar mais clara (essa não é uma solução para o seu problema exato).

var re = /^(.+?):"(.+)"$/
var regExResult = re.exec('description:"aoeu"');
var purifiedResult = purify_regex(regExResult);

// We only want the group matches in the array
function purify_regex(reResult){

  // Removes the Regex specific values and clones the array to prevent mutation
  let purifiedArray = [...reResult];

  // Removes the full match value at position 0
  purifiedArray.shift();

  // Returns a pure array without mutating the original regex result
  return purifiedArray;
}

// purifiedResult= ["description", "aoeu"]

Isso parece mais detalhado do que é por causa dos comentários, é assim que parece sem comentários

var re = /^(.+?):"(.+)"$/
var regExResult = re.exec('description:"aoeu"');
var purifiedResult = purify_regex(regExResult);

function purify_regex(reResult){
  let purifiedArray = [...reResult];
  purifiedArray.shift();
  return purifiedArray;
}

Observe que qualquer grupo que não corresponda será listado na matriz como undefinedvalores.

Esta solução usa o operador de dispersão ES6 para purificar a matriz de valores específicos de regex. Você precisará executar seu código no Babel se desejar suporte ao IE11.

Daniel Tonon
fonte
0

Aqui está uma solução de uma linha sem um loop while .

O pedido é preservado na lista resultante.

As desvantagens potenciais são

  1. Clona o regex para cada partida.
  2. O resultado está em uma forma diferente das soluções esperadas. Você precisará processá-los mais uma vez.
let re = /\s*([^[:]+):\"([^"]+)"/g
let str = '[description:"aoeu" uuid:"123sth"]'

(str.match(re) || []).map(e => RegExp(re.source, re.flags).exec(e))

[ [ 'description:"aoeu"',
    'description',
    'aoeu',
    index: 0,
    input: 'description:"aoeu"',
    groups: undefined ],
  [ ' uuid:"123sth"',
    'uuid',
    '123sth',
    index: 0,
    input: ' uuid:"123sth"',
    groups: undefined ] ]
Jae Won Jang
fonte
0

Meu palpite é que, se houver casos extremos, como espaços extras ou ausentes, essa expressão com menos limites também poderá ser uma opção:

^\s*\[\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*\]\s*$

Se você deseja explorar / simplificar / modificar a expressão, isso foi explicado no painel superior direito de regex101.com . Se desejar, também é possível assistir neste link , como ele corresponderia a algumas entradas de amostra.


Teste

const regex = /^\s*\[\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*([^\s\r\n:]+)\s*:\s*"([^"]*)"\s*\]\s*$/gm;
const str = `[description:"aoeu" uuid:"123sth"]
[description : "aoeu" uuid: "123sth"]
[ description : "aoeu" uuid: "123sth" ]
 [ description : "aoeu"   uuid : "123sth" ]
 [ description : "aoeu"uuid  : "123sth" ] `;
let m;

while ((m = regex.exec(str)) !== null) {
    // This is necessary to avoid infinite loops with zero-width matches
    if (m.index === regex.lastIndex) {
        regex.lastIndex++;
    }
    
    // The result can be accessed through the `m`-variable.
    m.forEach((match, groupIndex) => {
        console.log(`Found match, group ${groupIndex}: ${match}`);
    });
}

Circuito RegEx

O jex.im visualiza expressões regulares:

insira a descrição da imagem aqui

Emma
fonte
-5

Aqui está a minha resposta:

var str = '[me nombre es] : My name is. [Yo puedo] is the right word'; 

var reg = /\[(.*?)\]/g;

var a = str.match(reg);

a = a.toString().replace(/[\[\]]/g, "").split(','));
daguang
fonte
3
Sua string de entrada ( str) tem o formato errado (colchetes demais). Você captura apenas a chave, não o valor. Seu código tem erro de sintaxe e não é executado (os últimos parênteses). Se você responder à pergunta "antiga" com uma resposta já aceita, adicione mais conhecimento e uma resposta melhor que a já aceita. Eu não acho que sua resposta faça isso.
Cancelado