Comment accédez-vous aux groupes correspondants dans une expression régulière JavaScript?


1369

Je veux faire correspondre une partie d'une chaîne à l'aide d'une expression régulière , puis accéder à cette sous-chaîne entre parenthèses:

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

Qu'est-ce que je fais mal?


J'ai découvert qu'il n'y avait rien de mal avec le code d'expression régulière ci-dessus: la chaîne réelle contre laquelle je testais était la suivante:

"date format_%A"

Signaler que "% A" n'est pas défini semble un comportement très étrange, mais ce n'est pas directement lié à cette question, donc j'en ai ouvert un nouveau, pourquoi une sous-chaîne correspondante renvoie-t-elle "non défini" en JavaScript? .


Le problème était que cela console.logprend ses paramètres comme une printfinstruction, et puisque la chaîne que je connectais ( "%A") avait une valeur spéciale, il essayait de trouver la valeur du paramètre suivant.

Réponses:


1676

Vous pouvez accéder à des groupes de capture comme celui-ci:

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

Et s'il y a plusieurs correspondances, vous pouvez les parcourir:

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

Modifier: 2019-09-10

Comme vous pouvez le voir, la manière d'itérer sur plusieurs correspondances n'était pas très intuitive. Cela a conduit à la proposition de la String.prototype.matchAllméthode. Cette nouvelle méthode devrait être livrée dans la spécification ECMAScript 2020 . Il nous donne une API propre et résout plusieurs problèmes. Il a commencé à atterrir sur les principaux navigateurs et moteurs JS comme Chrome 73+ / Node 12+ et Firefox 67+.

La méthode renvoie un itérateur et est utilisée comme suit:

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

Comme il retourne un itérateur, nous pouvons dire qu'il est paresseux, cela est utile lors de la manipulation d'un nombre particulièrement important de groupes de capture ou de très grandes chaînes. Mais si vous en avez besoin, le résultat peut être facilement transformé en tableau en utilisant la syntaxe étalée ou la Array.fromméthode:

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

En attendant, bien que cette proposition bénéficie d'un support plus large, vous pouvez utiliser le package shim officiel .

De plus, le fonctionnement interne de la méthode est simple. Une implémentation équivalente utilisant une fonction de générateur serait la suivante:

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

Une copie de l'expression rationnelle d'origine est créée; ceci afin d'éviter les effets secondaires dus à la mutation de la lastIndexpropriété lors du passage par les correspondances multiples.

De plus, nous devons nous assurer que l'expression rationnelle a le drapeau global pour éviter une boucle infinie.

Je suis également heureux de voir que même cette question StackOverflow a été référencée dans les discussions sur la proposition .


114
+1 Veuillez noter que dans le deuxième exemple, vous devez utiliser l'objet RegExp (pas seulement "/ myregexp /"), car il conserve la valeur lastIndex dans l'objet. Sans utiliser l'objet Regexp, il itérera à l'infini
ianaz

7
@ianaz: Je ne crois pas que ce soit vrai? http://jsfiddle.net/weEg9/ semble fonctionner au moins sur Chrome.
spinningarrow

16
Pourquoi faire ce qui précède au lieu de var match = myString.match(myRegexp); // alert(match[1]):?
JohnAllen

29
Pas besoin de "nouveau RegExp" explicite, cependant la boucle infinie se produira à moins que / g ne soit spécifié
George C

4
Une autre façon de ne pas courir en boucle infinie est de mettre à jour explicitement la chaîne, par exemplestring = string.substring(match.index + match[0].length)
Olga

186

Voici une méthode que vous pouvez utiliser pour obtenir le n ème groupe de capture pour chaque correspondance:

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


12
C'est une réponse bien supérieure aux autres, car elle montre correctement l'itération sur toutes les correspondances au lieu d'en obtenir une seule.
Rob Evans

13
mnn a raison. Cela produira une boucle infinie si le drapeau «g» n'est pas présent. Soyez très prudent avec cette fonction.
Druska

4
J'ai amélioré cela pour le rendre similaire à re.findall () de python. Il regroupe toutes les correspondances dans un tableau de tableaux. Il corrige également le problème de boucle infinie du modificateur global. jsfiddle.net/ravishi/MbwpV
ravishi

5
@MichaelMikowski vous venez de masquer votre boucle infinie, mais votre code s'exécutera lentement. Je dirais qu'il vaut mieux avoir un code cassé dans le mauvais sens afin de l'attraper en cours de développement. Mettre quelques bs au maximum dans les itérations est bâclé. Cacher les problèmes au lieu de réparer leur cause première n'est pas la réponse.
wallacer

4
@MichaelMikowski qui n'est pas significativement plus lent lorsque vous n'atteignez pas la limite d'exécution. Quand vous l'êtes, c'est clairement beaucoup plus lent. Je ne dis pas que votre code ne fonctionne pas, je dis qu'en pratique, je pense qu'il causera plus de mal que de bien. Les personnes travaillant dans un environnement de développement verront le code fonctionner correctement sous aucune charge malgré les 10 000 exécutions inutiles de certains morceaux de code. Ensuite, ils vont le pousser vers un environnement de production et se demander pourquoi leur application tombe en panne. D'après mon expérience, il vaut mieux que les choses se cassent de manière évidente, et plus tôt dans le cycle de développement.
wallacer

58

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

Ce \bn'est pas exactement la même chose. (Cela fonctionne --format_foo/, mais ne fonctionne pas format_a_b) Mais je voulais montrer une alternative à votre expression, ce qui est bien. Bien sûr, l' matchappel est la chose importante.


2
C'est exactement l'inverse. '\ b' délimite les mots. word = '\ w' = [a-zA-Z0-9_]. "format_a_b" est un mot.
BF

1
@BFHonnêtement, j'ai ajouté "ne fonctionne pas format_a_b" comme une réflexion après il y a 6 ans, et je ne me souviens pas de ce que je voulais dire là-bas ... :-) Je suppose que cela signifiait "ne fonctionne pas pour capturer auniquement", c'est à dire. la première partie alphabétique après format_.
PhiLho

1
Je voulais dire que \ b (- format_foo /} \ b ne renvoie pas "--format_foo /" car "-" et "/" ne sont pas des caractères \ word. Mais \ b (format_a_b) \ b renvoie "format_a_b ". D'accord? Je me réfère à votre déclaration textuelle entre crochets. (N'a pas voté contre!)
BF

31

En ce qui concerne les exemples de parenthèses multi-correspondance ci-dessus, je cherchais une réponse ici après ne pas avoir obtenu ce que je voulais:

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

Après avoir regardé les appels de fonction légèrement alambiqués avec while et .push () ci-dessus, il m'est apparu que le problème peut être résolu très élégamment avec mystring.replace () à la place (le remplacement n'est PAS le point, et n'est même pas fait , l'option d'appel de fonction récursive CLEAN intégrée pour le deuxième paramètre est!):

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

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

Après cela, je ne pense pas que j'utiliserai jamais .match () pour presque rien.


26

Enfin, j'ai trouvé une ligne de code qui fonctionnait bien pour moi (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);

Cela reviendra:

['fiestasdefindeaño', 'PadreHijo', 'buenosmomentos', 'france', 'paris']

1
BOOM! C'est la solution la plus élégante ici. J'ai trouvé cela meilleur que l' replaceapproche complète d'Alexz parce que celle-ci est moins prospective et plus élégante pour plusieurs résultats. Bon travail là-dessus, Sébastien H.
Cody

Cela fonctionne si bien que son va définitivement dans mes utils :)
Cody

1
@Cody haha ​​merci mec!
Sébastien H.

19

Terminologie utilisée dans cette réponse:

  • Match indique le résultat de l' exécution de votre modèle RegEx contre votre chaîne comme ceci: someString.match(regexPattern).
  • Les modèles correspondants indiquent toutes les parties correspondantes de la chaîne d'entrée, qui résident toutes à l'intérieur du tableau de correspondance . Ce sont toutes des instances de votre modèle à l'intérieur de la chaîne d'entrée.
  • Les groupes appariés indiquent tous les groupes à attraper, définis dans le modèle RegEx. (Les modèles entre parenthèses, comme ceci:, /format_(.*?)/g(.*?)serait un groupe correspondant.) Ceux-ci résident dans des modèles correspondants .

La description

Pour accéder aux groupes correspondants , dans chacun des modèles correspondants , vous avez besoin d'une fonction ou quelque chose de similaire pour itérer sur la correspondance . Il existe plusieurs façons de procéder, comme le montrent la plupart des autres réponses. La plupart des autres réponses utilisent une boucle while pour parcourir tous les modèles correspondants , mais je pense que nous connaissons tous les dangers potentiels de cette approche. Il est nécessaire de faire correspondre un new RegExp()au lieu d'un simple motif lui-même, qui n'a été mentionné que dans un commentaire. Cela est dû au fait que la .exec()méthode se comporte de manière similaire à une fonction de générateur - elle s'arrête à chaque fois qu'il y a une correspondance , mais continue .lastIndexde continuer à partir de là lors du prochain .exec()appel.

Exemples de code

Ci-dessous est un exemple de fonction searchStringqui renvoie un Arrayde tous les modèles correspondants , où chacun matchest un Arrayavec tous les groupes correspondants contenant . Au lieu d'utiliser une boucle while, j'ai fourni des exemples utilisant à la fois la Array.prototype.map()fonction et une manière plus performante - en utilisant une forboucle simple .

Versions concises (moins de code, plus de sucre syntaxique)

Celles-ci sont moins performantes car elles implémentent fondamentalement un forEach-loop au lieu du for-loop plus rapide.

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

Versions performantes (plus de code, moins de sucre syntaxique)

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

Je n'ai pas encore comparé ces alternatives à celles mentionnées précédemment dans les autres réponses, mais je doute que cette approche soit moins performante et moins sûre que les autres.


19

String#matchAll(voir la proposition de l' étape 3 / proposition du 7 décembre 2018 ), simplifie l'accès à tous les groupes dans l'objet de correspondance (sachez que le groupe 0 est la correspondance complète, tandis que d'autres groupes correspondent aux groupes de capture dans le modèle):

Avec matchAllavailable, vous pouvez éviter la whileboucle et execavec /g... Au lieu de cela, en utilisant matchAll, vous récupérez un itérateur que vous pouvez utiliser avec la plus pratique for...of, la répartition du tableau ou les Array.from()constructions

Cette méthode donne une sortie similaire à Regex.MatchesC #, re.finditeren Python,preg_match_all en PHP.

Voir une démo JS (testée dans Google Chrome 73.0.3683.67 (version officielle), bêta (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

Les console.log([...matches])spectacles

entrez la description de l'image ici

Vous pouvez également obtenir une valeur de correspondance ou des valeurs de groupe spécifiques en utilisant

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

REMARQUE : voir les détails de compatibilité du navigateur .


Exemple parfait pour les paires de valeurs clés. Concis et facile à lire, très simple à utiliser. En outre, une meilleure gestion des erreurs, la propagation retournera un tableau vide plutôt que null, donc plus d'erreurs, pas de "longueur" de propriété de null '
Jarrod McGuire

17

Votre syntaxe n'est probablement pas la meilleure à conserver. FF / Gecko définit RegExp comme une extension de Function.
(FF2 est allé jusqu'à typeof(/pattern/) == 'function')

Il semble que cela soit spécifique à FF-IE, Opera et Chrome, tous lèvent des exceptions pour cela.

À la place, utilisez l'une des méthodes précédemment mentionnées par d'autres: RegExp#execou String#match.
Ils offrent les mêmes résultats:

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

16

Il n'est pas nécessaire d'invoquer la execméthode! Vous pouvez utiliser la méthode "match" directement sur la chaîne. N'oubliez pas les parenthèses.

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

La position 0 a une chaîne avec tous les résultats. La position 1 a la première correspondance représentée par des parenthèses, et la position 2 a la deuxième correspondance isolée entre vos parenthèses. Les parenthèses imbriquées sont délicates, alors méfiez-vous!


4
Sans le drapeau mondial, cela renvoie tous les matchs, avec lui, vous n'en obtiendrez qu'un seul, alors faites attention à cela.
Shadymilkman01

8

Une doublure pratique uniquement si vous avez une seule paire de parenthèses:

while ( ( match = myRegex.exec( myStr ) ) && matches.push( match[1] ) ) {};

4
Pourquoi paswhile (match = myRegex.exec(myStr)) matches.push(match[1])
willlma

7

En utilisant votre code:

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

Edit: Safari 3, si c'est important.


7

Avec es2018, vous pouvez désormais String.match()avec des groupes nommés, rendre votre expression rationnelle plus explicite de ce qu'elle essayait de faire.

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

et vous obtiendrez quelque chose comme

{protocole: "https", nom d'hôte: "stackoverflow.com", chemin d'accès: "questions / 432493 / comment-accéder-aux-groupes-correspondants-dans-une-expression-régulière-javascript", chaîne de requête: " certains = paramètre "}


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


3

Votre code fonctionne pour moi (FF3 sur Mac) même si je suis d'accord avec PhiLo que le regex devrait probablement être:

/\bformat_(.*?)\b/

(Mais, bien sûr, je ne suis pas sûr car je ne connais pas le contexte de l'expression régulière.)


1
c'est une liste séparée par des espaces donc j'ai pensé que ça irait. étrange que ce code ne fonctionnait pas pour moi (FF3 Vista)
nickf

1
Oui, vraiment étrange. L'avez-vous essayé seul dans la console Firebug? D'une page autrement vide je veux dire.
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));

2

Vous n'avez pas vraiment besoin d'une boucle explicite pour analyser plusieurs correspondances - passez une fonction de remplacement comme deuxième argument comme décrit dans 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);

L' m0argument représente la sous {0}- chaîne entièrement mise en correspondance {1}, etc. m1représente le premier groupe de correspondance, c'est-à-dire la partie entre crochets dans l'expression régulière qui correspond 0à la première correspondance. Et positionest l'index de départ dans la chaîne où le groupe correspondant a été trouvé - inutilisé dans ce cas.


1

Nous pouvons accéder au groupe correspondant dans une expression régulière en utilisant une barre oblique inverse suivie du numéro du groupe correspondant:

/([a-z])\1/

Dans le code \ 1 représenté correspond au premier groupe ([az])


1

Solution en une ligne:

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

Vous pouvez donc utiliser cette méthode (doit utiliser / g):

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

résultat:

[" format_abc"]

0

Obtenir toutes les occurrences de groupe

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

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

console.log(m);


0

Si vous êtes comme moi et que vous souhaitez que regex renvoie un objet comme celui-ci:

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

puis coupez la fonction d'en bas

/**
 * @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)


0

UTILISATION JUSTE RegExp. $ 1 ... $ n e groupe par exemple:

1.Pour correspondre au 1er groupe RegExp. $ 1

  1. Pour correspondre au 2e groupe RegExp. $ 2

si vous utilisez 3 groupes dans regex likey (notez l'utilisation aprè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)
 
}

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.