RegEx para extraer todas las coincidencias de la cadena usando RegExp.exec

175

Estoy tratando de analizar el siguiente tipo de cadena:

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

donde hay clave arbitraria: pares "val" dentro. Quiero tomar el nombre de la clave y el valor. Para aquellos curiosos, estoy tratando de analizar el formato de base de datos de task warrior.

Aquí está mi cadena de prueba:

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

lo que pretende resaltar que cualquier cosa puede estar en una clave o valor aparte del espacio, sin espacios alrededor de los dos puntos, y los valores siempre están entre comillas dobles.

En el nodo, esta es mi salida:

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

Pero description:"aoeu"también coincide con este patrón. ¿Cómo puedo recuperar todos los partidos?

Gatlin
fuente
Puede ser que mi expresión regular sea incorrecta y / o que simplemente esté usando incorrectamente las funciones de expresión regular en JavaScript. Esto parece funcionar:> var s = "Quince es 15 y ocho es 8"; > var re = / \ d + / g; > var m = s.match (re); m = ['15', '8']
gatlin
66
Javascript ahora tiene una función .match (): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Se usa así:"some string".match(/regex/g)
Stefnotch

Respuestas:

237

Continúa llamando re.exec(s)en un bucle para obtener todas las coincidencias:

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

Pruébelo con este JSFiddle: https://jsfiddle.net/7yS2V/

lawnsea
fuente
8
¿Por qué no en whilelugar de do … while?
Gumbo
15
Usar un ciclo while hace que sea un poco incómodo inicializar m. Tienes que escribir while(m = re.exec(s)), que es un IMO antipatrón, o tienes que escribir m = re.exec(s); while (m) { ... m = re.exec(s); }. Prefiero el do ... if ... whileidioma, pero otras técnicas también funcionarían.
lawnsea
14
Al hacer esto en cromo, mi pestaña se bloqueó.
EdgeCaseBerg
47
@EdgeCaseBerg Debe tener gconfigurado el indicador; de lo contrario, el puntero interno no se moverá hacia adelante. Doc .
Tim
12
Otro punto es que si la expresión regular puede coincidir con una cadena vacía, será un bucle infinito
FabioCosta
139

str.match(pattern), si patterntiene el indicador global g, devolverá todas las coincidencias como una matriz.

Por ejemplo:

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

Anis
fuente
15
Cuidado: las coincidencias no son objetos coincidentes, sino las cadenas coincidentes. Por ejemplo, no hay acceso a los grupos en "All of us except @Emran:emran26, @Raju:raju13 and @Noman:noman42".match(/@(\w+):(\w+)/g)(que volverán ["@Emran:emran26", "@Raju:raju13", "@Noman:noman42"])
madprog
44
@madprog, Correcto, es la forma más fácil pero no adecuada cuando los valores del grupo son esenciales.
Anis
1
Esto no está funcionando para mí. Solo consigo el primer partido.
Anthony Roberts el
77
@AnthonyRoberts debes agregar la bandera "g". /@\w/gonew RegExp("@\\w", "g")
Aruna Herath
88

Para recorrer todas las coincidencias, puede usar la replacefunción:

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

s.replace(re, function(match, g1, g2) { console.log(g1, g2); });
Christophe
fuente
Creo que es demasiado complicado. Sin embargo, es bueno saber acerca de las diferentes formas de hacer algo simple (voto a favor su respuesta).
Arashsoft
24
Es un código contraintuitivo. No estás "reemplazando" nada en ningún sentido significativo. Solo está explotando algunas funciones para un propósito diferente.
Luke Maurer
66
@dudewad si los ingenieros solo siguieran las reglas sin pensar fuera de la caja, ni siquiera estaríamos pensando en visitar otros planetas en este momento ;-)
Christophe
1
@dudewad lo siento, no veo la parte perezosa aquí. Si el mismo método se llamara "proceso" en lugar de "reemplazar", estaría de acuerdo con él. Me temo que estás atascado en la terminología.
Christophe
1
@Christophe Definitivamente no estoy atascado en la terminología. Estoy atrapado en el código limpio. Usar cosas que están destinadas a un propósito para un propósito diferente se llama "hacky" por una razón. Crea un código confuso que es difícil de entender y, en la mayoría de los casos, sufre un rendimiento en términos de rendimiento. El hecho de que haya respondido esta pregunta sin una expresión regular en sí misma la convierte en una respuesta no válida, ya que el OP pregunta cómo hacerlo con expresiones regulares. Sin embargo, me parece importante mantener a esta comunidad en un alto nivel, por lo que defiendo lo que dije anteriormente.
dudewad
56

Esta es una solucion

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

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

Esto se basa en la respuesta de lawnsea, pero más breve.

Tenga en cuenta que la bandera 'g' debe estar configurada para mover el puntero interno hacia adelante a través de invocaciones.

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

devuelve todas las coincidencias como una matriz.

Si, por alguna razón misteriosa, necesita la información adicional que viene exec, como alternativa a las respuestas anteriores, puede hacerlo con una función recursiva en lugar de un bucle de la siguiente manera (que también se ve mejor).

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)

Como se indicó en los comentarios anteriores, es importante tener gal final de la definición de expresiones regulares para mover el puntero hacia adelante en cada ejecución.

noego
fuente
1
si. Se ve recursivo elegante y fresco. Los bucles iterativos son sencillos, más fáciles de mantener y depurar.
Andy N
11

Finalmente comenzamos a ver una matchAllfunción incorporada, consulte aquí la descripción y la tabla de compatibilidad . Parece que a partir de mayo de 2020, Chrome, Edge, Firefox y Node.js (12+) son compatibles, pero no IE, Safari y Opera. Parece que fue redactado en diciembre de 2018, así que dedíqueles tiempo para llegar a todos los navegadores, pero confío en que llegue allí.

La matchAllfunción incorporada es agradable porque devuelve un iterable . ¡También devuelve grupos de captura para cada partida! Entonces puedes hacer cosas 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

También parece que cada objeto de coincidencia usa el mismo formato que match(). Así, cada objeto es una matriz de los grupos de los partidos y captura, junto con tres propiedades adicionales index, input, y groups. Entonces se ve así:

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

Para obtener más información sobre matchAlltambién hay una página de desarrolladores de Google . También hay polyfills / shims disponibles.

woojoo666
fuente
Realmente me gusta esto, pero aún no ha aterrizado en Firefox 66.0.3. Caniuse tampoco tiene una lista de soporte al respecto. Estoy deseando que llegue este. Lo veo funcionando en Chromium 74.0.3729.108.
Lonnie Best
1
@LonnieBest, sí, puedes ver la sección de compatibilidad de la página MDN que he vinculado. Parece que Firefox comenzó a admitirlo en la versión 67. Aún así, no recomendaría usarlo si está intentando enviar un producto. Hay polyfills / shims disponibles, que agregué a mi respuesta
woojoo666
10

Basado en la función de Agus, pero prefiero devolver solo los valores coincidentes:

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;]
Beto
fuente
8

Los Iterables son más bonitos:

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 en un bucle:

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

O si quieres una matriz:

[ ...matches('abcdefabcdef', /ab/g) ]
sdgfsdh
fuente
1
Typo: if (m)should beif (match)
Botje
Las matrices ya son iterables, por lo que todos los que devuelven una variedad de coincidencias también son iterables. Lo mejor es que si la consola registra una matriz, el navegador realmente puede imprimir el contenido. Pero el registro de la consola en un iterable genérico solo te da [objeto Object] {...}
StJohn3D
Todos los arreglos son iterables, pero no todos los iterables son arreglos. Un iterable es superior si no sabe qué necesitará hacer la persona que llama. Por ejemplo, si solo desea la primera coincidencia, un iterable es más eficiente.
sdgfsdh
su sueño se está haciendo realidad, los navegadores están implementando soporte para unmatchAll
dispositivo
1
Me he encontrado con esta respuesta después de la implementación de MatchAll. Escribí un código para el navegador JS que lo soportaba, pero Node en realidad no. Esto se comporta de manera idéntica para coincidir con Todos, así que no he tenido que volver a escribir cosas - ¡Salud!
user37309
8

Si tienes ES9

(Es decir, si su sistema: Chrome, Node.js, Firefox, etc. admite Ecmascript 2019 o posterior)

Use el nuevo yourString.matchAll( /your-regex/ ).

Si no tienes ES9

Si tiene un sistema anterior, aquí hay una función para copiar y pegar fácilmente

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
}

ejemplo de uso:

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

salidas:

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

Aquí está mi función para obtener los partidos:

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
fuente
Esta solución previene bucles infinitos cuando olvida agregar la bandera global.
usuario68311
2

Desde ES9, ahora hay una manera mejor y más simple de obtener todas las coincidencias, junto con información sobre los grupos de captura y su índice:

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

// ["ratones", índice: 0, entrada: "a los ratones les gusta cortar el arroz", grupos: indefinido]

// ["dados", índice: 13, entrada: "a los ratones les gusta cortar el arroz", grupos: indefinido]

// ["arroz", índice: 18, entrada: "a los ratones les gusta cortar el arroz", grupos: indefinido]

Actualmente es compatible con Chrome, Firefox, Opera. Dependiendo de cuándo lea esto, consulte este enlace para ver su soporte actual.

iuliu.net
fuente
¡Magnífico! Pero aún es importante tener en cuenta que la expresión regular debe tener un indicador gy lastIndexdebe restablecerse a 0 antes de la invocación de matchAll.
N. Kudryavtsev
1

Utilizar este...

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

Devolverá una serie de todas las coincidencias ... Eso funcionaría bien ... Pero recuerde que no tendrá en cuenta a los grupos ... Solo devolverá las coincidencias completas ...

Subham Debnath
fuente
0

Definitivamente recomendaría usar la función String.match () y crear un RegEx relevante para ello. Mi ejemplo es con una lista de cadenas, que a menudo es necesaria al escanear entradas de usuario para palabras clave y 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 esto ayude!

Sebastian Scholl
fuente
0

Esto realmente no va a ayudar con su problema más complejo, pero lo estoy publicando de todos modos porque es una solución simple para las personas que no están haciendo una búsqueda global como usted.

He simplificado la expresión regular en la respuesta para que sea más clara (esto no es una solución a su problema exacto).

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

Eso se ve más detallado de lo que es debido a los comentarios, así es como se ve sin comentarios

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

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

Tenga en cuenta que los grupos que no coincidan se enumerarán en la matriz como undefinedvalores.

Esta solución utiliza el operador de propagación ES6 para purificar la matriz de valores específicos de expresiones regulares. Tendrá que ejecutar su código a través de Babel si desea soporte para IE11.

Daniel Tonon
fuente
0

Aquí hay una solución de una línea sin un ciclo while .

El orden se conserva en la lista resultante.

Las desventajas potenciales son

  1. Clona la expresión regular para cada partido.
  2. El resultado está en una forma diferente a las soluciones esperadas. Tendrá que procesarlos una vez más.
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
fuente
0

Supongo que si hubiera casos extremos como espacios extra o faltantes, esta expresión con menos límites también podría ser una opción:

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

Si desea explorar / simplificar / modificar la expresión, se explica en el panel superior derecho de regex101.com . Si lo desea, también puede ver en este enlace cómo coincidiría con algunas entradas de muestra.


Prueba

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

jex.im visualiza expresiones regulares:

ingrese la descripción de la imagen aquí

Emma
fuente
-5

Aquí está mi respuesta:

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
fuente
3
Su cadena de entrada ( str) tiene el formato incorrecto (demasiados corchetes). Solo captura la clave, no el valor. Su código tiene un error de sintaxis y no se ejecuta (los últimos paréntesis). Si responde una pregunta "antigua" con una respuesta ya aceptada, asegúrese de agregar más conocimiento y una mejor respuesta que la ya aceptada. No creo que tu respuesta haga eso.
Autorizado el