RegEx pour extraire toutes les correspondances de la chaîne en utilisant RegExp.exec

176

J'essaie d'analyser le type de chaîne suivant:

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

où il y a des paires clé arbitraire: "val" à l'intérieur. Je veux saisir le nom de la clé et la valeur. Pour les curieux, j'essaye d'analyser le format de la base de données de task warrior.

Voici ma chaîne de test:

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

qui vise à souligner que tout peut être dans une clé ou une valeur en dehors de l'espace, aucun espace autour des deux-points et les valeurs sont toujours entre guillemets.

Dans node, voici ma sortie:

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

Mais description:"aoeu"correspond également à ce modèle. Comment puis-je récupérer tous les matchs?

Gatlin
la source
Il se peut que mon regex soit erronée et / ou que j'utilise simplement les fonctionnalités de regex dans JavaScript de manière incorrecte. Cela semble fonctionner:> var s = "Quinze vaut 15 et huit vaut 8"; > var re = / \ d + / g; > var m = s.match (re); m = ['15', '8']
gatlin
6
Javascript a maintenant une fonction .match (): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... Utilisé comme ceci:"some string".match(/regex/g)
Stefnotch

Réponses:

237

Continuez à appeler re.exec(s)dans une boucle pour obtenir toutes les correspondances:

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

Essayez-le avec ce JSFiddle: https://jsfiddle.net/7yS2V/

pelouse
la source
8
Pourquoi pas whileau lieu de do … while?
Gumbo le
15
L'utilisation d'une boucle while rend légèrement l'initialisation de m. Soit vous devez écrire while(m = re.exec(s)), ce qui est une IMO anti-pattern, soit vous devez écrire m = re.exec(s); while (m) { ... m = re.exec(s); }. Je préfère l' do ... if ... whileidiome, mais d'autres techniques fonctionneraient aussi.
lawnsea
14
faire cela dans le chrome a entraîné le crash de mon onglet.
EdgeCaseBerg
47
@EdgeCaseBerg Vous devez avoir le gdrapeau défini, sinon le pointeur interne n'est pas déplacé vers l'avant. Docs .
Tim
12
Un autre point est que si l'expression régulière peut correspondre à une chaîne vide, ce sera une boucle infinie
FabioCosta
139

str.match(pattern), si patterna l'indicateur global g, retournera toutes les correspondances sous forme de tableau.

Par exemple:

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

Anis
la source
15
Attention: les correspondances ne sont pas des objets de correspondance, mais les chaînes correspondantes. Par exemple, il n'y a pas d'accès aux groupes dans "All of us except @Emran:emran26, @Raju:raju13 and @Noman:noman42".match(/@(\w+):(\w+)/g)(qui reviendront ["@Emran:emran26", "@Raju:raju13", "@Noman:noman42"])
madprog
4
@madprog, d'accord, c'est le moyen le plus simple mais pas adapté lorsque les valeurs du groupe sont essentielles.
Anis
1
Cela ne fonctionne pas pour moi. Je n'ai que le premier match.
Anthony Roberts
7
@AnthonyRoberts vous devez ajouter le drapeau "g". /@\w/gounew RegExp("@\\w", "g")
Aruna Herath
88

Pour parcourir toutes les correspondances, vous pouvez utiliser la replacefonction:

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

s.replace(re, function(match, g1, g2) { console.log(g1, g2); });
Christophe
la source
Je pense que c'est trop compliqué. Cependant, il est bon de connaître les différentes façons de faire une chose simple (je vote votre réponse).
Arashsoft
24
C'est du code contre-intuitif. Vous ne «remplacez» rien dans un sens significatif. Il s'agit simplement d'exploiter certaines fonctions dans un but différent.
Luke Maurer
6
@dudewad si les ingénieurs suivaient simplement les règles sans sortir des sentiers battus, nous ne penserions même pas à visiter d'autres planètes en ce moment ;-)
Christophe
1
@dudewad désolé, je ne vois pas la partie paresseuse ici. Si la même méthode était appelée «processus» au lieu de «remplacer», vous seriez d'accord. J'ai peur que vous ne soyez coincé avec la terminologie.
Christophe
1
@Christophe Je ne suis certainement pas coincé sur la terminologie. Je suis coincé sur du code propre. Utiliser des choses qui sont destinées à un but dans un but différent est appelé «piratage» pour une raison. Cela crée un code déroutant qui est difficile à comprendre et qui souffre le plus souvent en termes de performances. Le fait que vous ayez répondu à cette question sans regex en soi en fait une réponse invalide, puisque l'OP demande comment le faire avec regex. Je trouve cependant important de maintenir cette communauté à un niveau élevé, c'est pourquoi je maintiens ce que j'ai dit ci-dessus.
dudewad
56

C'est une solution

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

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

Ceci est basé sur la réponse de Lawnsea, mais plus courte.

Notez que l'indicateur «g» doit être défini pour déplacer le pointeur interne vers l'avant à travers les appels.

Lovasoa
la source
17
str.match(/regex/g)

renvoie toutes les correspondances sous forme de tableau.

Si, pour une raison mystérieuse, vous avez besoin des informations supplémentaires fournies exec, comme alternative aux réponses précédentes, vous pouvez le faire avec une fonction récursive au lieu d'une boucle comme suit (qui semble également plus cool).

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)

comme indiqué dans les commentaires précédents, il est important d'avoir gà la fin de la définition de regex pour faire avancer le pointeur à chaque exécution.

noego
la source
1
Oui. récursif semble élégant et plus frais. Les boucles itératives sont simples, plus faciles à maintenir et à déboguer.
Andy N
11

Nous commençons enfin à voir une matchAllfonction intégrée, voir ici pour la description et le tableau de compatibilité . Il semble que depuis mai 2020, Chrome, Edge, Firefox et Node.js (12+) soient pris en charge, mais pas IE, Safari et Opera. On dirait qu'il a été rédigé en décembre 2018, alors laissez-lui un peu de temps pour atteindre tous les navigateurs, mais j'espère qu'il y parviendra.

La matchAllfonction intégrée est agréable car elle renvoie un itérable . Il renvoie également des groupes de capture pour chaque match! Ainsi, vous pouvez faire des choses comme

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

Il semble également que chaque objet de correspondance utilise le même format que match(). Ainsi , chaque objet est un tableau de groupes de correspondance et la capture, avec trois autres propriétés index, input, et groups. Donc ça ressemble à:

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

Pour plus d'informations, matchAllil existe également une page des développeurs Google . Il existe également des polyfills / cales disponibles.

woojoo666
la source
J'aime vraiment ça, mais ça n'a pas encore atterri dans Firefox 66.0.3. Caniuse n'a pas encore de liste de support à ce sujet. J'ai hâte de celui-ci. Je le vois fonctionner dans Chromium 74.0.3729.108.
Lonnie Best
1
@LonnieBest ouais, vous pouvez voir la section de compatibilité de la page MDN que j'ai liée. Il semble que Firefox a commencé à le supporter dans la version 67. Je ne recommanderais toujours pas de l'utiliser si vous essayez d'expédier un produit. Il existe des polyfills / shims disponibles, que j'ai ajoutés à ma réponse
woojoo666
10

Basé sur la fonction d'Agus, mais je préfère ne renvoyer que les valeurs de correspondance:

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;]
bob
la source
8

Les itérables sont plus agréables:

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

Utilisation en boucle:

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

Ou si vous voulez un tableau:

[ ...matches('abcdefabcdef', /ab/g) ]
sdgfsdh
la source
1
Typo: if (m)devrait êtreif (match)
Botje
Les tableaux sont déjà itérables, donc tout le monde qui renvoie un tableau de correspondances renvoie également des itérables. Ce qui est mieux, c'est que si vous consignez un tableau dans la console, le navigateur peut réellement imprimer le contenu. Mais la journalisation de la console d'un itérable générique vous permet d'obtenir simplement [objet Object] {...}
StJohn3D
Tous les tableaux sont itérables mais tous les itérables ne sont pas des tableaux. Un itérable est supérieur si vous ne savez pas ce que l'appelant devra faire. Par exemple, si vous ne voulez que la première correspondance, un itérable est plus efficace.
sdgfsdh
votre rêve est en train de devenir une réalité, les navigateurs matchAll
déploient la
1
Je suis tombé sur cette implémentation post-matchAll. J'ai écrit du code pour le navigateur JS qui le supportait, mais Node ne l'a pas fait. Cela se comporte de manière identique pour correspondre à tous, donc je n'ai pas eu à réécrire des choses - Cheers!
user37309
8

Si vous avez ES9

(Cela signifie que si votre système: Chrome, Node.js, Firefox, etc. prend en charge Ecmascript 2019 ou version ultérieure)

Utilisez le nouveau yourString.matchAll( /your-regex/ ).

Si vous n'avez pas ES9

Si vous avez un système plus ancien, voici une fonction pour copier et coller facilement

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
}

exemple d'utilisation:

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

les sorties:

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

Voici ma fonction pour obtenir les correspondances:

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
la source
Cette solution évite les boucles infinies lorsque vous oubliez d'ajouter l'indicateur global.
user68311
2

Depuis ES9, il existe désormais un moyen plus simple et meilleur d'obtenir toutes les correspondances, ainsi que des informations sur les groupes de capture et leur index:

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

// ["souris", index: 0, entrée: "souris aiment couper le riz en dés", groupes: indéfini]

// ["dés", index: 13, entrée: "les souris aiment couper le riz en dés", groupes: indéfini]

// ["riz", index: 18, entrée: "les souris aiment couper le riz en dés", groupes: indéfini]

Il est actuellement pris en charge dans Chrome, Firefox, Opera. En fonction du moment où vous lisez ceci, consultez ce lien pour voir son support actuel.

iuliu.net
la source
Superbe! Mais il est toujours important de garder à l'esprit que l'expression régulière doit avoir un indicateur get qu'elle lastIndexdoit être réinitialisée à 0 avant l'invocation de matchAll.
N.Kudryavtsev
1

Utilisez ceci...

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

Il renverra un tableau de toutes les correspondances ... Cela fonctionnerait très bien ... Mais rappelez-vous que cela ne prendra pas en compte les groupes ... Il ne fera que renvoyer les correspondances complètes ...

Subham Debnath
la source
0

Je recommanderais définitivement d'utiliser la fonction String.match () et de créer un RegEx pertinent pour cela. Mon exemple est avec une liste de chaînes, ce qui est souvent nécessaire lors de l'analyse des entrées utilisateur à la recherche de mots-clés et de phrases.

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

J'espère que cela t'aides!

Sebastian Scholl
la source
0

Cela n'aidera pas vraiment à résoudre votre problème plus complexe, mais je le publie quand même car c'est une solution simple pour les personnes qui ne font pas de recherche globale comme vous.

J'ai simplifié l'expression régulière dans la réponse pour être plus claire (ce n'est pas une solution à votre problème exact).

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

Cela semble plus verbeux qu'à cause des commentaires, voici à quoi cela ressemble sans commentaires

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

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

Notez que tous les groupes qui ne correspondent pas seront répertoriés dans le tableau en tant que undefinedvaleurs.

Cette solution utilise l'opérateur de propagation ES6 pour purifier le tableau de valeurs spécifiques à l'expression régulière. Vous devrez exécuter votre code via Babel si vous souhaitez prendre en charge IE11.

Daniel Tonon
la source
0

Voici une solution en une ligne sans boucle while .

L'ordre est conservé dans la liste résultante.

Les inconvénients potentiels sont

  1. Il clone le regex pour chaque match.
  2. Le résultat est sous une forme différente des solutions attendues. Vous devrez les traiter une fois de plus.
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
la source
0

Je suppose que s'il y avait des cas extrêmes tels que des espaces supplémentaires ou manquants, cette expression avec moins de limites pourrait également être une option:

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

Si vous souhaitez explorer / simplifier / modifier l'expression, cela a été expliqué dans le panneau supérieur droit de regex101.com . Si vous le souhaitez, vous pouvez également regarder dans ce lien , comment il correspondrait à certains exemples d'entrées.


Tester

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

Circuit RegEx

jex.im visualise les expressions régulières:

entrez la description de l'image ici

Emma
la source
-5

Voici ma réponse:

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
la source
3
Votre chaîne d'entrée ( str) a un format incorrect (trop de crochets). Vous ne capturez que la clé, pas la valeur. Votre code a une erreur de syntaxe et ne s'exécute pas (les dernières parenthèses). Si vous répondez à une «ancienne» question avec une réponse déjà acceptée, assurez-vous d'ajouter plus de connaissances et une meilleure réponse que celle déjà acceptée. Je ne pense pas que votre réponse fasse cela.
Terminé le