RegEx per estrarre tutte le corrispondenze dalla stringa utilizzando RegExp.exec


175

Sto cercando di analizzare il seguente tipo di stringa:

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

dove ci sono chiavi arbitrarie: coppie "val" all'interno. Voglio prendere il nome della chiave e il valore. Per i curiosi sto cercando di analizzare il formato del database di task warrior.

Ecco la mia stringa di test:

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

che ha lo scopo di evidenziare che qualsiasi cosa può essere in una chiave o in un valore a parte lo spazio, nessuno spazio attorno ai due punti e che i valori sono sempre tra virgolette.

Nel nodo, questo è il mio output:

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

Ma description:"aoeu"corrisponde anche a questo modello. Come posso recuperare tutte le partite?


Potrebbe essere che il mio regex sia sbagliato e / o che sto semplicemente usando le strutture di regex in JavaScript in modo errato. Questo sembra funzionare:> var s = "Quindici è 15 e otto è 8"; > var re = / \ d + / g; > var m = s.match (re); m = ['15', '8']
gatlin

6
Javascript ora ha una funzione .match (): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Usato in questo modo:"some string".match(/regex/g)
Stefnotch

Risposte:


237

Continua a chiamare re.exec(s)in loop per ottenere tutte le partite:

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

Provalo con questo JSFiddle: https://jsfiddle.net/7yS2V/


8
Perché non whileinvece di do … while?
Gumbo,

15
L'uso di un ciclo while rende leggermente imbarazzante l'inizializzazione di m. Devi scrivere while(m = re.exec(s)), che è un IMO anti-pattern, oppure devi scrivere m = re.exec(s); while (m) { ... m = re.exec(s); }. Preferisco il do ... if ... whilelinguaggio, ma funzionerebbero anche altre tecniche.
prato verde

14
farlo con il cromo ha provocato il crash della mia scheda.
EdgeCaseBerg,

47
@EdgeCaseBerg È necessario avere il gflag impostato, altrimenti il ​​puntatore interno non viene spostato in avanti. Doc .
Tim

12
Un altro punto è che se il regex può corrispondere a una stringa vuota sarà un ciclo infinito
FabioCosta

139

str.match(pattern), se patternha il flag globale g, restituirà tutte le partite come un array.

Per esempio:

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


15
Attenzione: le partite non sono oggetti di corrispondenza, ma le stringhe corrispondenti. Ad esempio, non c'è accesso ai gruppi in "All of us except @Emran:emran26, @Raju:raju13 and @Noman:noman42".match(/@(\w+):(\w+)/g)(che tornerà ["@Emran:emran26", "@Raju:raju13", "@Noman:noman42"])
madprog,

4
@madprog, giusto, è il modo più semplice ma non adatto quando i valori di gruppo sono essenziali.
Anis,

1
Questo non funziona per me. Ho solo la prima partita.
Anthony Roberts,

7
@AnthonyRoberts devi aggiungere il flag "g". /@\w/goppurenew RegExp("@\\w", "g")
Aruna Herath,

88

Per scorrere tutte le partite, puoi usare la replacefunzione:

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

s.replace(re, function(match, g1, g2) { console.log(g1, g2); });

Penso che sia troppo complicato. Tuttavia, è bello conoscere diversi modi di fare una cosa semplice (voto positivo sulla tua risposta).
Arashsoft,

24
È un codice controintuitivo. Non stai "sostituendo" nulla in alcun senso significativo. Sta solo sfruttando la funzione some per uno scopo diverso.
Luke Maurer,

6
@dudewad se gli ingegneri stessero solo seguendo le regole senza pensare fuori dagli schemi, non staremmo nemmeno pensando di visitare altri pianeti in questo momento ;-)
Christophe

1
@dudewad mi dispiace, non riesco a vedere la parte pigra qui. Se lo stesso metodo esatto fosse chiamato "process" anziché "sostituisci", andresti bene. Temo che tu sia solo bloccato sulla terminologia.
Christophe,

1
@Christophe Non sono assolutamente bloccato sulla terminologia. Sono bloccato su codice pulito. L'uso di cose intese per uno scopo per uno scopo diverso è chiamato "hacky" per una ragione. Crea un codice confuso che è difficile da capire e il più delle volte soffre in termini di prestazioni. Il fatto che tu abbia risposto a questa domanda senza una regex in sé e per sé la rende una risposta non valida, poiché l'OP chiede come farlo con regex. Trovo importante, tuttavia, mantenere questa comunità di alto livello, motivo per cui sostengo ciò che ho detto sopra.
Dudewad,

56

Questa è una soluzione

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

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

Questo si basa sulla risposta di lawnsea, ma più breve.

Si noti che il flag `g 'deve essere impostato per spostare il puntatore interno in avanti attraverso le invocazioni.


17
str.match(/regex/g)

restituisce tutte le partite come un array.

Se, per qualche motivo misterioso, hai bisogno delle informazioni aggiuntive fornite exec, in alternativa alle risposte precedenti, potresti farlo con una funzione ricorsiva invece di un ciclo come segue (che sembra anche più interessante).

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)

come affermato nei commenti prima, è importante avere galla fine della definizione regex per spostare il puntatore in avanti in ogni esecuzione.


1
sì. ricorsivo sembra elegante e più fresco. I loop iterativi sono semplici, più facili da mantenere e da eseguire il debug.
Andy N,

11

Stiamo finalmente iniziando a vedere una matchAllfunzione integrata, vedi qui per la descrizione e la tabella di compatibilità . A partire da maggio 2020, Chrome, Edge, Firefox e Node.js (12+) sono supportati ma non IE, Safari e Opera. Sembra che sia stato redatto a dicembre 2018 quindi dagli un po 'di tempo per raggiungere tutti i browser, ma credo che ci arriverà.

La matchAllfunzione integrata è piacevole perché restituisce un iterabile . Restituisce anche gruppi di acquisizione per ogni partita! Quindi puoi fare cose del genere

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

Sembra anche che ogni oggetto match usi lo stesso formato di match(). Così ogni oggetto è un array di gruppi partita e cattura, insieme a tre proprietà aggiuntive index, input, e groups. Quindi sembra che:

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

Per ulteriori informazioni su matchAllc'è anche una pagina degli sviluppatori di Google . Ci sono anche polyfill / spessori disponibili.


Mi piace davvero molto, ma non è ancora arrivato su Firefox 66.0.3. Caniuse non ha ancora un elenco di supporto al riguardo. Non vedo l'ora di questo. Lo vedo funzionare in Chromium 74.0.3729.108.
Lonnie Best

1
@LonnieBest sì, puoi vedere la sezione di compatibilità della pagina MDN che ho collegato. Sembra che Firefox abbia iniziato a supportarlo nella versione 67. Ancora non consiglierei di usarlo se stai cercando di spedire un prodotto. Ci sono polyfill / spessori disponibili, che ho aggiunto alla mia risposta
woojoo666

10

Basato sulla funzione di Agus, ma preferisco restituire solo i valori di corrispondenza:

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

8

Iterables sono più belli:

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

Utilizzo in un ciclo:

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

O se vuoi un array:

[ ...matches('abcdefabcdef', /ab/g) ]

1
if (m)if (match)
Errore di battitura

Le matrici sono già iterabili, quindi anche tutti coloro che restituiscono una serie di partite restituiscono iterabili. La cosa migliore è che se si accede alla console in un array, il browser può effettivamente stampare il contenuto. Ma la console che registra un iterable generico ti dà solo [oggetto Object] {...}
StJohn3D

Tutti gli array sono iterabili ma non tutti gli iterabili sono array. Un iterabile è superiore se non sai cosa dovrà fare il chiamante. Ad esempio, se si desidera solo la prima corrispondenza, un iterabile è più efficiente.
sdgfsdh,

il tuo sogno sta diventando realtà, i browser stanno implementando il supporto per un built-in matchAllche restituisce un iterabile : D
woojoo666

1
Mi sono imbattuto in questa risposta post-partitaTutto l'implementazione. Ho scritto del codice per il browser JS che lo supportava, ma in realtà Node no. Questo si comporta in modo identico a MatchAll, quindi non ho dovuto riscrivere roba - Saluti!
user37309

8

Se hai ES9

(Significa che se il tuo sistema: Chrome, Node.js, Firefox, ecc. Supporta Ecmascript 2019 o successivo)

Usa il nuovo yourString.matchAll( /your-regex/ ).

Se non hai ES9

Se hai un sistema precedente, ecco una funzione per una facile copia e incolla

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
}

esempio di utilizzo:

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

uscite:

[ [ 'blah', index: 0 ], [ 'blah', index: 6 ] ]

5

Ecco la mia funzione per ottenere le partite:

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

Questa soluzione impedisce loop infiniti quando si dimentica di aggiungere la bandiera globale.
user68311

2

Da ES9, esiste ora un modo più semplice e migliore per ottenere tutte le partite, insieme alle informazioni sui gruppi di acquisizione e sul loro indice:

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

// ["topi", indice: 0, input: "ai topi piace tagliare il riso", gruppi: non definito]

// ["dice", indice: 13, input: "ai topi piace tagliare il riso", gruppi: non definito]

// ["riso", indice: 18, input: "ai topi piace tagliare il riso", gruppi: non definito]

Attualmente è supportato in Chrome, Firefox, Opera. A seconda di quando leggi questo, controlla questo link per vedere il suo attuale supporto.


Stupendo! Ma è ancora importante tenere presente che regex dovrebbe avere un flag ge lastIndexdovrebbe essere reimpostato su 0 prima dell'invocazione di matchAll.
N. Kudryavtsev,

1

Usa questo...

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

Restituirà una serie di tutte le partite ... Funzionerebbe perfettamente ... Ma ricorda che non terrà conto dei gruppi ... Restituirà solo le partite complete ...


0

Consiglio vivamente di utilizzare la funzione String.match () e di creare un RegEx pertinente per questo. Il mio esempio è con un elenco di stringhe, che è spesso necessario durante la scansione degli input dell'utente per parole chiave e frasi.

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

Spero che questo ti aiuti!


0

Questo non aiuterà davvero con il tuo problema più complesso, ma lo sto pubblicando comunque perché è una soluzione semplice per le persone che non stanno effettuando una ricerca globale come te.

Ho semplificato la regex nella risposta per essere più chiari (questa non è una soluzione al tuo esatto problema).

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

Sembra più prolisso di quanto non sia a causa dei commenti, questo è quello che sembra senza commenti

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

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

Si noti che tutti i gruppi che non corrispondono verranno elencati nell'array come undefinedvalori.

Questa soluzione utilizza l'operatore spread ES6 per purificare l'array di valori specifici di regex. Sarà necessario eseguire il codice tramite Babel se si desidera il supporto IE11.


0

Ecco una soluzione a una riga senza un ciclo while .

L'ordine viene conservato nell'elenco risultante.

I potenziali aspetti negativi sono

  1. Clona la regex per ogni partita.
  2. Il risultato è in una forma diversa rispetto alle soluzioni previste. Dovrai elaborarli ancora una volta.
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 ] ]

0

La mia ipotesi è che se ci fossero casi limite come spazi extra o mancanti, questa espressione con meno confini potrebbe anche essere un'opzione:

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

Se desideri esplorare / semplificare / modificare l'espressione, è stata spiegata nel pannello in alto a destra di regex101.com . Se lo desideri, puoi anche guardare in questo link , come corrisponderebbe ad alcuni input di esempio.


Test

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 visualizza le espressioni regolari:

inserisci qui la descrizione dell'immagine


-5

Ecco la mia risposta:

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(','));

3
La stringa di input ( str) ha un formato errato (troppe parentesi quadre). Catturi solo la chiave, non il valore. Il codice presenta un errore di sintassi e non viene eseguito (le ultime parentesi). Se rispondi a una "vecchia" domanda con una risposta già accettata, assicurati di aggiungere più conoscenza e una risposta migliore di quella già accettata. Non credo che la tua risposta lo faccia.
Completato il
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.