Come si accede ai gruppi corrispondenti in un'espressione regolare JavaScript?


1369

Voglio abbinare una parte di una stringa usando un'espressione regolare e quindi accedere a quella sottostringa tra parentesi:

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

Che cosa sto facendo di sbagliato?


Ho scoperto che non c'era nulla di sbagliato nel codice dell'espressione regolare sopra: la stringa effettiva su cui stavo testando era questa:

"date format_%A"

Segnalare che "% A" è indefinito sembra un comportamento molto strano, ma non è direttamente correlato a questa domanda, quindi ne ho aperto uno nuovo, Perché una sottostringa abbinata restituisce "indefinito" in JavaScript? .


Il problema era che console.logprende i suoi parametri come printfun'istruzione e poiché la stringa che stavo registrando ( "%A") aveva un valore speciale, stava cercando di trovare il valore del parametro successivo.

Risposte:


1676

Puoi accedere a gruppi di acquisizione come questo:

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

E se ci sono più corrispondenze puoi scorrere su di esse:

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

Modifica: 2019-09-10

Come puoi vedere, il modo di scorrere più partite non è stato molto intuitivo. Ciò ha portato alla proposta del String.prototype.matchAllmetodo. Si prevede che questo nuovo metodo verrà fornito nelle specifiche ECMAScript 2020 . Ci fornisce un'API pulita e risolve numerosi problemi. È stato iniziato ad approdare sui principali browser e motori JS come Chrome 73+ / Node 12+ e Firefox 67+.

Il metodo restituisce un iteratore e viene utilizzato come segue:

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

Poiché restituisce un iteratore, possiamo dire che è pigro, questo è utile quando si gestiscono un numero particolarmente elevato di gruppi di acquisizione o stringhe molto grandi. Ma se necessario, il risultato può essere facilmente trasformato in una matrice usando la sintassi di diffusione o il Array.frommetodo:

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

Nel frattempo, mentre questa proposta ottiene un supporto più ampio, è possibile utilizzare il pacchetto shim ufficiale .

Inoltre, i meccanismi interni del metodo sono semplici. Un'implementazione equivalente che utilizza una funzione di generatore sarebbe la seguente:

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

Viene creata una copia del regexp originale; questo per evitare effetti collaterali dovuti alla mutazione della lastIndexproprietà quando si affrontano le partite multiple.

Inoltre, dobbiamo garantire che regexp abbia la bandiera globale per evitare un ciclo infinito.

Sono anche felice di vedere che anche questa domanda StackOverflow è stata citata nelle discussioni sulla proposta .


114
+1 Si noti che nel secondo esempio è necessario utilizzare l'oggetto RegExp (non solo "/ myregexp /"), poiché mantiene il valore lastIndex nell'oggetto. Senza usare l'oggetto Regexp,
ripeterà

7
@ianaz: non credo sia vero? http://jsfiddle.net/weEg9/ sembra funzionare su Chrome, almeno.
Spinningarrow,

16
Perché fare quanto sopra anziché var match = myString.match(myRegexp); // alert(match[1]):?
John Allen,

29
Non è necessario un "nuovo RegExp" esplicito, tuttavia si verificherà il ciclo infinito a meno che non sia specificato / g
George C

4
Un altro modo per non correre in un ciclo infinito è aggiornare esplicitamente la stringa, ad esempiostring = string.substring(match.index + match[0].length)
Olga,

186

Ecco un metodo che è possibile utilizzare per ottenere il n ° gruppo di cattura per ogni partita:

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
Questa è una risposta di gran lunga superiore agli altri perché mostra correttamente l'iterazione su tutte le partite invece di ottenerne solo una.
Rob Evans,

13
mnn ha ragione. Questo produrrà un ciclo infinito se il flag 'g' non è presente. Stai molto attento con questa funzione.
Druska,

4
L'ho migliorato per renderlo simile al re.findall () di Python. Raggruppa tutte le partite in una matrice di array. Risolve anche il problema del ciclo infinito modificatore globale. jsfiddle.net/ravishi/MbwpV
ravishi

5
@MichaelMikowski ora hai appena nascosto il tuo ciclo infinito, ma il tuo codice funzionerà lentamente. Direi che è meglio avere una violazione del codice in modo negativo in modo da catturarlo nello sviluppo. L'inserimento di alcune iterazioni massime in bs è scorretto. Nascondere i problemi invece di correggere la causa principale non è la risposta.
wallacer,

4
@MichaelMikowski che non è significativamente più lento quando non si raggiunge il limite di esecuzione. Quando lo sei, è chiaramente molto più lento. Non sto dicendo che il tuo codice non funziona, sto dicendo che in pratica penso che causerà più danni che benefici. Le persone che lavorano in un ambiente di sviluppo vedranno il codice funzionare bene senza carico nonostante facciano 10.000 esecuzioni inutili di un pezzo di codice. Quindi lo spingeranno in un ambiente di produzione e si chiederanno perché la loro app sia sotto carico. Nella mia esperienza è meglio se le cose si rompono in modo ovvio e prima nel ciclo di sviluppo.
wallacer,

58

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

Non \bè esattamente la stessa cosa. (Funziona --format_foo/, ma non funziona format_a_b) Ma volevo mostrare un'alternativa alla tua espressione, il che va bene. Certo, la matchchiamata è la cosa importante.


2
È esattamente il contrario. '\ b' delimita le parole. word = '\ w' = [a-zA-Z0-9_]. "format_a_b" è una parola.
BF

1
@BFSolo, ho aggiunto "non funziona format_a_b" come ripensamento 6 anni fa, e non ricordo cosa intendevo lì ... :-) Suppongo che significasse "non funziona asolo per catturare ", vale a dire. la prima parte alfabetica dopo format_.
PhiLho,

1
Volevo dire che \ b (- format_foo /} \ b non restituisce "--format_foo /" perché "-" e "/" non sono caratteri \ word. Ma \ b (format_a_b) \ b restituisce "format_a_b ". Giusto? Mi riferisco alla sua dichiarazione di testo tra parentesi tonde. (Non ha votato per difetto!)
BF

31

Per quanto riguarda gli esempi di parentesi multi-match sopra, stavo cercando una risposta qui dopo non aver ottenuto quello che volevo da:

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

Dopo aver visto le chiamate di funzione leggermente contorte con while e .push () sopra, mi sono reso conto che il problema può essere risolto molto elegantemente con mystring.replace () invece (la sostituzione NON è il punto e non è nemmeno fatta , l'opzione di chiamata della funzione ricorsiva CLEAN incorporata per il secondo parametro è!):

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

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

Dopo questo, non penso che userò mai .match () per quasi mai più nulla.


26

Ultimo ma non meno importante, ho trovato una riga di codice che ha funzionato bene per me (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);

Questo restituirà:

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

1
BOOM! Questa è la soluzione più elegante qui. L'ho trovato migliore dell'approccio integrale replacedi Alexz perché questo è meno all'avanguardia e più elegante per più risultati. Ottimo lavoro su questo, Sebastien H.
Cody

Funziona così bene che sicuramente entrerà nei miei programmi di utilità :)
Cody

1
@Cody haha ​​grazie amico!
Sebastien H.

19

Terminologia utilizzata in questa risposta:

  • Partita indica il risultato di correre il vostro modello di RegEx contro la stringa in questo modo: someString.match(regexPattern).
  • I pattern corrispondenti indicano tutte le parti corrispondenti della stringa di input, che risiedono tutti all'interno della matrice di match . Queste sono tutte le istanze del tuo pattern all'interno della stringa di input.
  • I gruppi corrispondenti indicano tutti i gruppi da catturare, definiti nel modello RegEx. (I motivi tra parentesi, in questo modo:, /format_(.*?)/gdove (.*?)sarebbe un gruppo abbinato.) Questi risiedono all'interno di schemi abbinati .

Descrizione

Per ottenere l'accesso ai gruppi abbinati , in ciascuno dei modelli abbinati , è necessaria una funzione o qualcosa di simile per scorrere la corrispondenza . Ci sono molti modi per farlo, come mostrano molte altre risposte. La maggior parte delle altre risposte utilizza un ciclo while per scorrere tutti gli schemi corrispondenti , ma penso che tutti conosciamo i potenziali pericoli con questo approccio. È necessario abbinare contro un new RegExp()invece che solo il modello stesso, che è stato menzionato solo in un commento. Questo perché il .exec()metodo si comporta in modo simile a una funzione del generatore : si interrompe ogni volta che c'è una corrispondenza , ma continua .lastIndexa continuare da lì alla .exec()chiamata successiva .

Esempi di codice

Di seguito è riportato un esempio di una funzione searchStringche restituisce uno Arraydi tutti i modelli corrispondenti , dove ognuno matchè un Arraycon tutti i gruppi corrispondenti corrispondenti . Invece di usare un ciclo while, ho fornito esempi usando sia la Array.prototype.map()funzione sia un modo più performante - usando un semplice forciclo.

Versioni concise (meno codice, più zucchero sintattico)

Questi sono meno performanti poiché fondamentalmente implementano un forEach-loop anziché il for-loop più veloce .

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

Versioni performanti (più codice, meno zucchero sintattico)

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

Devo ancora confrontare queste alternative con quelle precedentemente menzionate nelle altre risposte, ma dubito che questo approccio sia meno performante e meno sicuro degli altri.


19

String#matchAll(vedi la bozza della Fase 3 / proposta del 7 dicembre 2018 ), semplifica l'accesso a tutti i gruppi nell'oggetto partita (ricorda che il Gruppo 0 è l'intera partita, mentre altri gruppi corrispondono ai gruppi di cattura nel modello):

Con matchAlldisponibile, puoi evitare il whileciclo e execcon /g... Invece, usando matchAll, ottieni un iteratore che puoi usare con il più conveniente for...of, array array o Array.from()costrutti

Questo metodo produce un output simile a Regex.Matchesin C #, re.finditerin Python, preg_match_allin PHP.

Guarda una demo di JS (testata su Google Chrome 73.0.3683.67 (build ufficiale), beta (64 bit)):

var myString = "key1:value1, key2-value2!!@key3=value3";
var matches = myString.matchAll(/(\w+)[:=-](\w+)/g);
console.log([...matches]); // All match with capturing group values

Gli console.log([...matches])spettacoli

inserisci qui la descrizione dell'immagine

Puoi anche ottenere il valore di corrispondenza o valori di gruppo specifici utilizzando

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

NOTA : vedere i dettagli di compatibilità del browser .


Esempio perfetto per coppie chiave-valore. Conciso e facile da leggere, molto semplice da usare. Inoltre, una migliore gestione degli errori, lo spread restituirà un array vuoto anziché null, quindi niente più 'errore, nessuna proprietà "lunghezza" di null'
Jarrod McGuire

17

La tua sintassi probabilmente non è la migliore da mantenere. FF / Gecko definisce RegExp come un'estensione della funzione.
(FF2 è andato fino al punto typeof(/pattern/) == 'function')

Sembra che questo sia specifico per FF: IE, Opera e Chrome generano eccezioni.

Invece, utilizzare uno dei metodi precedentemente menzionati da altri: RegExp#execo String#match.
Offrono gli stessi risultati:

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

Non è necessario invocare il execmetodo! È possibile utilizzare il metodo "match" direttamente sulla stringa. Basta non dimenticare le parentesi.

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 posizione 0 ha una stringa con tutti i risultati. La posizione 1 ha la prima corrispondenza rappresentata tra parentesi e la posizione 2 ha la seconda corrispondenza isolata tra parentesi. Le parentesi annidate sono difficili, quindi attenzione!


4
Senza la bandiera globale questo restituisce tutte le partite, con esso ne otterrai solo una grande, quindi fai attenzione.
Shadymilkman01,

8

Una fodera che è pratica solo se hai una sola coppia di parentesi:

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

4
Perché nowhile (match = myRegex.exec(myStr)) matches.push(match[1])
willlma,

7

Usando il tuo codice:

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

Modifica: Safari 3, se è importante.


7

Con es2018 puoi ora String.match()con gruppi nominati, rendere la tua regex più esplicita di ciò che stava cercando di fare.

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

e otterrai qualcosa del genere

{protocollo: "https", nome host: "stackoverflow.com", nome percorso: "questions / 432493 / how-do-you-access-the-match-groups-in-a-a-javascript-regular-expression", querystring: " alcuni = parametro "}


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

Il tuo codice funziona per me (FF3 su Mac) anche se sono d'accordo con PhiLo che probabilmente il regex dovrebbe essere:

/\bformat_(.*?)\b/

(Ma, naturalmente, non sono sicuro perché non conosco il contesto della regex.)


1
è un elenco separato da spazi quindi ho pensato che sarebbe andato tutto bene. strano che quel codice non funzionasse per me (FF3 Vista)
nickf

1
Sì, davvero strano. L'hai provato da solo nella console di Firebug? Da una pagina altrimenti vuota intendo.
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

Non hai davvero bisogno di un ciclo esplicito per analizzare più corrispondenze - passa una funzione di sostituzione come secondo argomento come descritto in 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' m0argomento rappresenta la sottostringa con corrispondenza completa{0} , {1}ecc m1rappresenta il primo gruppo corrispondente, cioè la parte racchiusa tra parentesi nella regex che è 0la prima corrispondenza. Ed positionè l'indice iniziale all'interno della stringa in cui è stato trovato il gruppo corrispondente - inutilizzato in questo caso.


1

Possiamo accedere al gruppo corrispondente in espressioni regolari usando la barra rovesciata seguita dal numero del gruppo corrispondente:

/([a-z])\1/

Nel codice \ 1 rappresentato abbinato dal primo gruppo ([az])


1

Una soluzione di linea:

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

Quindi puoi usare in questo modo (devi usare / g):

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

risultato:

[" format_abc"]

0

Ottieni tutte le occorrenze di gruppo

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

Io sei come me e vorrei che regex restituisse un oggetto come questo:

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

quindi tagliare la funzione dal basso

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

UTILIZZARE SOLO RegExp. $ 1 ... $ n es. Gruppo:

1.Per abbinare RegExp. $ 1 1 ° gruppo

  1. Per abbinare il 2o gruppo RegExp. $ 2

se usi 3 gruppi in regex likey (nota usa after 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)
 
}

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.