Cum puteți accesa grupurile potrivite într-o expresie regulată JavaScript? (Programare, Javascript, Regex)

nickf a intrebat.

Vreau să potrivesc o porțiune dintr-un șir de caractere folosind o expresie expresie regulată și apoi să accesez acel subșir între paranteze:

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

Ce fac greșit?


Am descoperit că nu era nimic în neregulă cu codul expresiei regulate de mai sus: șirul real pe care îl testam era acesta:

"date format_%A"

Raportarea faptului că „%A” este nedefinit pare un comportament foarte ciudat, dar nu are legătură directă cu această întrebare, așa că am deschis una nouă, De ce o subșiră de caractere corespunzătoare returnează „nedefinit” în JavaScript?.


Problema a fost că console.log își ia parametrii ca un printf și, din moment ce șirul pe care îl înregistram ("%A") avea o valoare specială, încerca să găsească valoarea următorului parametru.

22 răspunsuri
Christian C. Salvadó

Poți accesa grupurile de captură astfel:

Iar dacă există mai multe potriviri poți itera peste ele:

Editare: 2019-09-10

După cum puteți vedea, modul de a itera peste mai multe potriviri nu era foarte intuitiv. Acest lucru a dus la propunerea de String.prototype.matchAll metodă. Se așteaptă ca această nouă metodă să fie livrată în versiunea specificația ECMAScript 2020. Ea ne oferă un API curat și rezolvă mai multe probleme. A început să aterizeze pe principalele browsere și motoare JS ca Chrome 73+ / Node 12+. și Firefox 67+.

Metoda returnează un iterator și este utilizată după cum urmează:

Deoarece returnează un iterator, putem spune că este leneșă, acest lucru este util atunci când se gestionează un număr deosebit de mare de grupuri de captură sau șiruri foarte mari. Dar, dacă aveți nevoie, rezultatul poate fi transformat cu ușurință într-un Array prin utilizarea metodei sintaxa spread sau a funcției Array.from metodă:

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

Între timp, în timp ce această propunere primește un sprijin mai larg, puteți utiliza metoda pachetul oficial shim.

De asemenea, funcționarea internă a metodei este simplă. O implementare echivalentă folosind o funcție generatoare ar fi următoarea:

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

Se creează o copie a regexp-ului original; acest lucru este pentru a evita efectele secundare datorate mutației din lastIndex proprietății la parcurgerea mai multor corespondențe.

De asemenea, trebuie să ne asigurăm că regexp-ul are proprietatea global pentru a evita o buclă infinită.

De asemenea, mă bucur să văd că până și această întrebare de pe StackOverflow a fost menționată în discuțiile privind propunerea.

Comentarii

    114

  • +1 Vă rugăm să rețineți că în al doilea exemplu ar trebui să utilizați obiectul RegExp (nu doar „/myregexp/”), deoarece păstrează valoarea lastIndex în obiect. Fără utilizarea obiectului Regexp se va itera la infinit –  > Por ianaz.
  • @ianaz: Nu cred că ‘tis este adevărat? http://jsfiddle.net/weEg9/ pare să funcționeze pe Chrome, cel puțin. –  > Por spinningarrow.
  • 17

  • De ce să faci cele de mai sus în loc de: var match = myString.match(myRegexp); // alert(match[1])? –  > Por JohnAllen.
  • 29

  • Nu este nevoie de un „new RegExp” explicit, însă bucla infinită va apărea dacă nu se specifică /g –  > Por George C.
  • O altă modalitate de a nu intra în buclă infinită este de a actualiza în mod explicit șirul de caractere, de ex. string = string.substring(match.index + match[0].length) –  > Por Olga.
Mathias Bynens

Iată o metodă pe care o puteți utiliza pentru a obține nn-lea grup de captură pentru fiecare potrivire:

Comentarii

  • Acesta este un răspuns mult superior celorlalte, deoarece arată corect iterația peste toate meciurile în loc să obțină doar unul singur. –  > Por Rob Evans.
  • mnn are dreptate. Acest lucru va produce o buclă infinită dacă nu este prezent indicatorul „g”. Fiți foarte atenți cu această funcție. –  > Por Druska.
  • Am îmbunătățit această funcție pentru a o face similară cu re.findall() din python. Aceasta grupează toate corespondențele într-o matrice de matrice. De asemenea, rezolvă problema buclei infinite a modificatorului global. jsfiddle.net/ravishi/MbwpV –  > Por ravishi.
  • @MichaelMikowski acum tocmai ai ascuns bucla infinită, dar codul tău va rula lent. Aș spune că este mai bine ca codul să se întrerupă într-un mod greșit, astfel încât să îl prindeți în timpul dezvoltării. A pune o pauză de iterații maxime de bs bs este neglijent. Ascunderea problemelor în loc de a le rezolva cauza principală nu este soluția. –  > Por wallacer.
  • @MichaelMikowski că nu este semnificativ mai lent atunci când nu atingi limita de execuție. Atunci când o faci, este în mod clar mult mai lent. Nu spun că codul tău nu funcționează, spun că în practică cred că va face mai mult rău decât bine. Oamenii care lucrează într-un mediu de dezvoltare vor vedea codul funcționând bine în lipsa de sarcină, în ciuda efectuării a 10.000 de execuții inutile ale unei bucăți de cod. Apoi, îl vor transfera într-un mediu de producție și se vor întreba de ce aplicația lor nu funcționează în condiții de încărcare. Din experiența mea, este mai bine ca lucrurile să se strice într-un mod evident și mai devreme în ciclul de dezvoltare. –  > Por wallacer.
PhiLho

The b nu este exact același lucru. (Funcționează pe --format_foo/, , dar nu funcționează pe format_a_b) Dar am vrut să arăt o alternativă la expresia ta, care este în regulă. Desigur, expresia match apelul este lucrul important.

Comentarii

  • Este exact invers. ‘b’ delimitează cuvintele. word= ‘w’ = [a-zA-Z0-9_] . „format_a_b” este un cuvânt. –  > Por B.F..
  • @B.F.Sincer, am adăugat „nu funcționează pe format_a_b” ca un gând de după, acum 6 ani, și nu-mi amintesc ce am vrut să spun acolo… 🙂 Presupun că a însemnat „nu funcționează pentru a captura a numai”, adică prima parte alfabetică după format_. –  > Por PhiLho.
  • Am vrut să spun că b(–format_foo/}b nu returnează „–format_foo/” deoarece „-” și „/” nu sunt caractere de word. Dar b(format_a_b)b returnează „format_a_b”. Nu-i așa? Mă refer la declarația dvs. de text în paranteze rotunde. (Nu am votat în jos!) –  > Por B.F..
Sebastien H.

Nu în ultimul rând, am găsit o linie de cod care a funcționat bine pentru mine (JS ES6):

Aceasta va returna:

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

Comentarii

  • BOOM! Aceasta este cea mai elegantă soluție aici. Mi s-a părut mai bună decât soluția completă replace de Alexz, deoarece aceasta este mai puțin anticipată și mai elegantă pentru rezultate multiple. Bună treabă, Sebastien H. –  > Por Cody.
  • Acest lucru funcționează atât de bine încât cu siguranță merge în utilitățile mele 🙂 –  > Por Cody.
  • Acest lucru este mult mai bun decât celelalte răspunsuri! Vă mulțumesc! –  > Por Owen Versteeg.
Alexz

În ceea ce privește exemplele de paranteze multi-match de mai sus, am căutat un răspuns aici după ce nu am obținut ceea ce am vrut de la:

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

După ce m-am uitat la apelurile de funcție ușor alambicate cu while și .push() de mai sus, mi-am dat seama că problema poate fi rezolvată foarte elegant cu mystring.replace() în loc de aceasta (înlocuirea NU este scopul și nici măcar nu este făcută, ci opțiunea CLEAN, built-in de apelare a funcției recursive pentru al doilea parametru este!):

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

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

După acest lucru, nu cred că voi mai folosi niciodată .match() pentru aproape nimic.

Wiktor Stribiżew

String#matchAll (a se vedea Proiectul din etapa 3 / propunerea din 7 decembrie 2018), simplifică accesul la toate grupurile din obiectul match (rețineți că Grupul 0 este întregul meci, în timp ce grupurile ulterioare corespund grupurilor de captură din model):

Cu matchAll disponibil, puteți evita utilizarea while buclă și exec cu /g… În schimb, prin utilizarea matchAll, , veți obține înapoi un iterator pe care îl puteți utiliza cu mai convenabilul for...of, , răspândirea matricei, , sau Array.from() construiește

Această metodă produce un rezultat similar cu Regex.Matches în C#, re.finditer în Python, preg_match_all în PHP.

Vedeți o demonstrație JS (testată în Google Chrome 73.0.3683.67 (official build), beta (64 de biți)):

The console.log([...matches]) arată

Puteți obține, de asemenea, valoarea de potrivire sau valorile specifice ale grupului utilizând

NOTĂ: Consultați pagina compatibilitatea browserului detalii.

Comentarii

  • Exemplu perfect pentru perechi cheie-valoare. Concis și ușor de citit, foarte simplu de utilizat. De asemenea, o mai bună gestionare a erorilor, răspândirea va returna o matrice goală mai degrabă decât nulă, deci nu mai există „eroare, nu există o proprietate „length” de nul” –  > Por Jarrod McGuire.
Daniel Hallgren

Terminologia utilizată în acest răspuns:

  • Potrivire indică rezultatul rulării modelului RegEx în raport cu șirul de caractere, astfel: someString.match(regexPattern).
  • Modele potrivite indică toate porțiunile potrivite din șirul de intrare, care se află toate în interiorul șirului de caractere match array. Acestea sunt toate instanțele modelului dvs. în interiorul șirului de intrare.
  • Grupuri corespunzătoare indică toate grupurile care trebuie capturate, definite în modelul RegEx. (Modelele din interiorul parantezelor, ca de exemplu: /format_(.*?)/g, , unde (.*?) ar fi un grup compatibil). Acestea se află în Matched patterns.

Descriere

Pentru a avea acces la grupuri potrivite, , în fiecare dintre tipare potrivite, aveți nevoie de o funcție sau ceva similar pentru a itera peste potrivire. Există o serie de moduri în care puteți face acest lucru, după cum arată multe dintre celelalte răspunsuri. Cele mai multe răspunsuri folosesc o buclă while pentru a parcurge toate răspunsurile. modelele care corespund, , dar cred că știm cu toții potențialele pericole ale acestei abordări. Este necesar să se compare cu un new RegExp() în loc de doar modelul în sine, ceea ce a fost menționat doar într-un comentariu. Acest lucru se datorează faptului că .exec() se comportă în mod similar cu o metodă funcție de generatorse oprește de fiecare dată când există o potrivire, , dar își păstrează funcția .lastIndex pentru a continua de acolo la următoarea .exec() apel.

Exemple de cod

Mai jos este un exemplu de funcție searchString care returnează un Array din toate modelele potrivite, , unde fiecare match este un Array cu toate șabloanele care îl conțin grupuri potrivite. În loc să folosesc o buclă while, am furnizat exemple care utilizează atât Array.prototype.map() cât și un mod mai performant – folosind o simplă funcție for-buclă.

Versiuni concise (mai puțin cod, mai mult zahăr sintactic)

Acestea sunt mai puțin performante, deoarece implementează practic o funcție forEach-în loc de o buclă mai rapidă for-mai rapid.

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

Versiuni performante (mai mult cod, mai puțin zahăr sintactic)

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

Încă nu am comparat aceste alternative cu cele menționate anterior în celelalte răspunsuri, dar mă îndoiesc că această abordare este mai puțin performantă și mai puțin sigură decât celelalte.

Jonathan Lonowski

Probabil că sintaxa dvs. nu este cea mai bună de păstrat. FF/Gecko definește RegExp ca o extensie a funcției.
(FF2 a mers până la typeof(/pattern/) == 'function')

Se pare că acest lucru este specific pentru FF – IE, Opera și Chrome toate aruncă excepții pentru aceasta.

În schimb, utilizați oricare dintre metodele menționate anterior de alții: RegExp#exec sau String#match.
Acestea oferă aceleași rezultate:

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

Andre Carneiro

Nu este necesar să se invoce metoda exec metoda! Puteți utiliza metoda „match” direct pe șirul de caractere. Doar nu uitați de paranteze.

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

Poziția 0 are un șir cu toate rezultatele. Poziția 1 are prima potrivire reprezentată de paranteze, iar poziția 2 are a doua potrivire izolată în parantezele dumneavoastră. Parantezele imbricate sunt înșelătoare, așa că aveți grijă!

Comentarii

  • Fără steagul global, acest lucru returnează toate corespondențele, cu el, veți obține doar una mare, așa că aveți grijă la asta. –  > Por Shadymilkman01.
Nabil Kadimi

O singură linie care este practică doar dacă aveți o singură pereche de paranteze:

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

Comentarii

  • De ce nu while (match = myRegex.exec(myStr)) matches.push(match[1]) –  > Por willlma.
David Cheung

Cu es2018 puteți acum String.match() cu grupuri numite, face ca regex-ul dvs. să fie mai explicit cu privire la ceea ce a încercat să facă.

const url =
  'https://stackoverflow.com/questions/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);

și veți obține ceva de genul

{protocol: „https”, hostname: „stackoverflow.com”, pathname: „questions/432493/how-do-you-access-the-matched-groups-in-a-javascript-regular-expression”, querystring: „some=parameter”}

eyelidlessness

Folosind codul dvs:

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

Editare: Safari 3, dacă contează.

Jack

PEZ

Codul dvs. funcționează pentru mine (FF3 pe Mac), chiar dacă sunt de acord cu PhiLo că regex-ul ar trebui să fie probabil:

/bformat_(.*?)b/

(Dar, bineînțeles, nu sunt sigur pentru că nu cunosc contextul regex-ului).

Comentarii

  • este o listă separată prin spații, așa că m-am gândit că s ar fi bine. ciudat că acel cod nu a funcționat pentru mine (FF3 Vista) –  > Por nickf.
  • Da, cu adevărat ciudat. Ați încercat de unul singur în consola Firebug? Dintr-o pagină de altfel goală vreau să spun. –  > Por PEZ.
Pawel Kwiecien
/*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));

ccpizza

Nu aveți cu adevărat nevoie de o buclă explicită pentru a analiza mai multe potriviri – treceți o funcție de înlocuire ca al doilea argument, așa cum este descris în: String.prototype.replace(regex, func):

The m0 reprezintă subșirul complet al șirului de caractere care se potrivește {0}, , {1}, , etc. m1 reprezintă primul grup de potrivire, adică partea cuprinsă între paranteze din regex care este 0 pentru prima potrivire. Și position reprezintă indexul de început în interiorul șirului în care a fost găsit grupul de potrivire – neutilizat în acest caz.

Md. A. Barik

Putem accesa grupul de corespondență într-o expresie regulată folosind backslash urmat de numărul grupului de corespondență:

/([a-z])1/

În codul 1 reprezentat de primul grup ([a-z]).

Caio Santos

Soluție pe o singură linie:

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

Deci, puteți folosi acest mod (trebuie să folosiți /g):

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

rezultat:

[" format_abc"]

ßãlãjî

JUST USE RegExp.$1…$n th groupeg:

1.Pentru a se potrivi cu primul grup RegExp.$1

  1. Pentru a se potrivi cu al 2-lea grup RegExp.$2

dacă folosiți 3 grupuri în regex (atenție, folosiți după string.match(regex))

RegExp.$1 RegExp.$2 RegExp.$3

Kamil Kiełczewski

Obțineți toate aparițiile grupului

Delcon

Dacă ești ca mine și îți dorești ca regex să returneze un obiect ca acesta:

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

apoi tăiați funcția de mai jos

MSS

După cum a spus @cms, în ECMAScript (ECMA-262) puteți folosi matchAll. Acesta returnează un iterator și punându-l în [... ] (operator de răspândire) se convertește într-o matrice (acest regex extrage urile de nume de fișiere).