RegEx, um alle Übereinstimmungen mit RegExp.exec aus der Zeichenfolge zu extrahieren

175

Ich versuche, die folgende Art von Zeichenfolge zu analysieren:

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

wo es beliebige Schlüssel gibt: "val" Paare im Inneren. Ich möchte den Schlüsselnamen und den Wert erfassen. Für diejenigen, die neugierig sind, versuche ich, das Datenbankformat von Task Warrior zu analysieren.

Hier ist meine Testzeichenfolge:

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

Dies soll hervorheben, dass sich alles in einem Schlüssel oder Wert befinden kann, außer Leerzeichen, keine Leerzeichen um die Doppelpunkte und Werte immer in doppelten Anführungszeichen.

Im Knoten ist dies meine Ausgabe:

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

Passt aber description:"aoeu"auch zu diesem Muster. Wie kann ich alle Spiele zurückbekommen?

Gatlin
quelle
Es kann sein, dass mein Regex falsch ist und / oder dass ich die Regex-Funktionen in JavaScript einfach falsch verwende. Dies scheint zu funktionieren:> var s = "Fünfzehn ist 15 und acht ist 8"; > var re = / \ d + / g; > var m = s.match (re); m = ['15', '8']
Gatlin
6
Javascript hat jetzt eine .match () -Funktion: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… So verwendet:"some string".match(/regex/g)
Stefnotch

Antworten:

237

Rufen Sie re.exec(s)in einer Schleife weiter auf, um alle Übereinstimmungen zu erhalten:

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

Probieren Sie es mit dieser JSFiddle aus: https://jsfiddle.net/7yS2V/

Rasensee
quelle
8
Warum nicht whilestatt do … while?
Gumbo
15
Die Verwendung einer while-Schleife macht es etwas umständlich, m zu initialisieren. Sie müssen entweder schreiben while(m = re.exec(s)), was eine Anti-Pattern-IMO ist, oder Sie müssen schreiben m = re.exec(s); while (m) { ... m = re.exec(s); }. Ich bevorzuge die do ... if ... whileRedewendung, aber andere Techniken würden auch funktionieren.
Rasen
14
Dies in Chrom zu tun, führte dazu, dass mein Tab abstürzte.
EdgeCaseBerg
47
@EdgeCaseBerg Sie müssen das gFlag setzen, sonst wird der interne Zeiger nicht vorwärts bewegt. Docs .
Tim
12
Ein weiterer Punkt ist, dass, wenn der
reguläre Ausdruck
139

str.match(pattern)Wenn patterndas globale Flag vorhanden ist g, werden alle Übereinstimmungen als Array zurückgegeben.

Beispielsweise:

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

Anis
quelle
15
Achtung: Die Übereinstimmungen sind keine Übereinstimmungsobjekte, sondern die übereinstimmenden Zeichenfolgen. Zum Beispiel gibt es keinen Zugang zu den Gruppen in "All of us except @Emran:emran26, @Raju:raju13 and @Noman:noman42".match(/@(\w+):(\w+)/g)(die zurückkehren werden ["@Emran:emran26", "@Raju:raju13", "@Noman:noman42"])
madprog
4
@madprog, Richtig, es ist der einfachste Weg, aber nicht geeignet, wenn die Gruppenwerte wesentlich sind.
Anis
1
Das funktioniert bei mir nicht. Ich bekomme nur das erste Match.
Anthony Roberts
7
@AnthonyRoberts Sie müssen das Flag "g" hinzufügen. /@\w/godernew RegExp("@\\w", "g")
Aruna Herath
88

Um alle Übereinstimmungen zu durchlaufen, können Sie die folgende replaceFunktion verwenden:

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

s.replace(re, function(match, g1, g2) { console.log(g1, g2); });
Christophe
quelle
Ich finde es einfach zu kompliziert. Es ist jedoch schön zu wissen, wie man eine einfache Sache auf verschiedene Weise macht (ich stimme Ihrer Antwort zu).
Arashsoft
24
Es ist nicht intuitiver Code. Sie "ersetzen" nichts in einem sinnvollen Sinne. Es nutzt nur einige Funktionen für einen anderen Zweck.
Luke Maurer
6
@dudewad Wenn Ingenieure nur die Regeln befolgen würden, ohne über den Tellerrand hinaus zu denken, würden wir jetzt nicht einmal daran denken, andere Planeten zu besuchen ;-)
Christophe
1
@dudewad Entschuldigung, ich sehe den faulen Teil hier nicht. Wenn genau die gleiche Methode "Prozess" anstelle von "Ersetzen" genannt würde, wären Sie damit einverstanden. Ich fürchte, Sie halten sich nur an die Terminologie.
Christophe
1
@Christophe Ich bin definitiv nicht auf Terminologie fixiert. Ich bin auf sauberen Code fixiert. Die Verwendung von Dingen, die für einen Zweck für einen anderen Zweck bestimmt sind, wird aus einem bestimmten Grund als "hacky" bezeichnet. Es entsteht verwirrender Code, der schwer zu verstehen ist und häufig unter der Leistung leidet. Die Tatsache, dass Sie diese Frage ohne Regex an und für sich beantwortet haben, macht sie zu einer ungültigen Antwort, da das OP fragt, wie dies mit Regex geschehen soll. Ich finde es jedoch wichtig, diese Community auf einem hohen Niveau zu halten, weshalb ich zu dem stehe, was ich oben gesagt habe.
Dudewad
56

Dies ist eine Lösung

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

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

Dies basiert auf der Antwort von Rasen, aber kürzer.

Beachten Sie, dass das Flag "g" gesetzt sein muss, um den internen Zeiger über Aufrufe nach vorne zu bewegen.

Lovasoa
quelle
17
str.match(/regex/g)

Gibt alle Übereinstimmungen als Array zurück.

Wenn Sie aus einem mysteriösen Grund die zusätzlichen Informationen benötigen exec, die als Alternative zu früheren Antworten enthalten sind, können Sie dies mit einer rekursiven Funktion anstelle einer Schleife wie folgt tun (was auch cooler aussieht).

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)

Wie bereits in den Kommentaren erwähnt, ist es wichtig, gam Ende der Regex-Definition den Zeiger bei jeder Ausführung nach vorne zu bewegen.

noego
quelle
1
Ja. rekursiv sieht elegant und cooler aus. Iterative Schleifen sind unkompliziert, einfacher zu warten und zu debuggen.
Andy N
11

Endlich sehen wir eine eingebaute matchAllFunktion. Hier finden Sie die Beschreibung und die Kompatibilitätstabelle . Es sieht so aus, als ob ab Mai 2020 Chrome, Edge, Firefox und Node.js (12+) unterstützt werden, jedoch nicht IE, Safari und Opera. Scheint, als ob es im Dezember 2018 entworfen wurde Geben Sie ihm also etwas Zeit, um alle Browser zu erreichen, aber ich vertraue darauf, dass es dort ankommt.

Die eingebaute matchAllFunktion ist nett, weil sie eine iterable zurückgibt . Es werden auch Erfassungsgruppen für jedes Spiel zurückgegeben! So können Sie Dinge wie tun

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

Es scheint auch, dass jedes Übereinstimmungsobjekt dasselbe Format wie verwendet match(). Also jedes Objekt ist ein Array der Partie und zwischengespeicherten Gruppen zusammen mit drei zusätzlichen Eigenschaften index, inputund groups. So sieht es aus:

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

Für weitere Informationen matchAllgibt es auch eine Google-Entwicklerseite . Es sind auch Polyfills / Shims erhältlich.

woojoo666
quelle
Ich mag das wirklich, aber es ist noch nicht ganz in Firefox 66.0.3 gelandet. Caniuse hat auch noch keine Support-Liste. Ich freue mich auf diesen. Ich sehe es in Chrom 74.0.3729.108.
Lonnie Best
1
@LonnieBest Ja, Sie können den Kompatibilitätsabschnitt der MDN-Seite sehen , die ich verlinkt habe. Es scheint, dass Firefox damit begonnen hat, es in Version 67 zu unterstützen. Ich würde es dennoch nicht empfehlen, es zu verwenden, wenn Sie versuchen, ein Produkt zu versenden. Es sind Polyfills / Shims verfügbar, die ich meiner Antwort hinzugefügt habe
woojoo666
10

Basierend auf Agus 'Funktion, aber ich bevorzuge es, nur die Übereinstimmungswerte zurückzugeben:

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
quelle
8

Iterables sind schöner:

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

Verwendung in einer Schleife:

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

Oder wenn Sie ein Array möchten:

[ ...matches('abcdefabcdef', /ab/g) ]
sdgfsdh
quelle
1
Tippfehler: if (m)sollte seinif (match)
Botje
Arrays sind bereits iterierbar, sodass jeder, der ein Array von Übereinstimmungen zurückgibt, auch iterable zurückgibt. Was besser ist, wenn Sie ein Array in der Konsole protokollieren, kann der Browser den Inhalt tatsächlich ausdrucken. Aber die Konsolenprotokollierung einer generischen Iterable bringt Ihnen nur [Objekt Objekt] {...}
StJohn3D
Alle Arrays sind iterierbar, aber nicht alle iterablen sind Arrays. Ein iterable ist überlegen, wenn Sie nicht wissen, was der Anrufer tun muss. Wenn Sie beispielsweise nur die erste Übereinstimmung wünschen, ist eine Iterable effizienter.
SDGFSDH
Ihr Traum wird Wirklichkeit, Browser rollen Unterstützung für ein eingebautes matchAll, das eine
iterable
1
Ich bin auf diese Antwort nach der Implementierung von MatchAll gestoßen. Ich habe Code für Browser JS geschrieben, der ihn unterstützt, Node jedoch nicht. Dies verhält sich identisch mit matchAll, sodass ich nichts umschreiben musste - Prost!
user37309
8

Wenn Sie ES9 haben

(Das heißt, wenn Ihr System: Chrome, Node.js, Firefox usw. Ecmascript 2019 oder höher unterstützt)

Verwenden Sie das neueyourString.matchAll( /your-regex/ ) .

Wenn Sie kein ES9 haben

Wenn Sie ein älteres System haben, finden Sie hier eine Funktion zum einfachen Kopieren und Einfügen

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
}

Anwendungsbeispiel:

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

Ausgänge:

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

Hier ist meine Funktion, um die Übereinstimmungen zu erhalten:

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
quelle
Diese Lösung verhindert Endlosschleifen, wenn Sie vergessen, das globale Flag hinzuzufügen.
user68311
2

Seit ES9 gibt es jetzt eine einfachere und bessere Möglichkeit, alle Übereinstimmungen zusammen mit Informationen zu den Erfassungsgruppen und deren Index abzurufen:

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

// ["Mäuse", Index: 0, Eingabe: "Mäuse würfeln gern Reis", Gruppen: undefiniert]

// ["Würfel", Index: 13, Eingabe: "Mäuse würfeln gern Reis", Gruppen: undefiniert]

// ["Reis", Index: 18, Eingabe: "Mäuse würfeln gern Reis", Gruppen: undefiniert]

Es wird derzeit in Chrome, Firefox, Opera unterstützt. Je nachdem, wann Sie dies lesen, überprüfen Sie diesen Link , um die aktuelle Unterstützung anzuzeigen.

iuliu.net
quelle
Hervorragend! Es ist jedoch weiterhin wichtig zu beachten, dass der reguläre Ausdruck ein Flag haben gund lastIndexvor dem Aufruf von auf 0 zurückgesetzt werden sollte matchAll.
N. Kudryavtsev
1

Benutze das...

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

Es wird ein Array aller Übereinstimmungen zurückgegeben ... Das würde gut funktionieren ... Aber denken Sie daran, dass keine Gruppen berücksichtigt werden. Es werden nur die vollständigen Übereinstimmungen zurückgegeben ...

Subham Debnath
quelle
0

Ich würde definitiv empfehlen, die Funktion String.match () zu verwenden und eine relevante RegEx dafür zu erstellen. Mein Beispiel ist eine Liste von Zeichenfolgen, die häufig beim Scannen von Benutzereingaben nach Schlüsselwörtern und Phrasen erforderlich ist.

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

Hoffe das hilft!

Sebastian Scholl
quelle
0

Dies wird bei Ihrem komplexeren Problem nicht wirklich helfen, aber ich poste dies trotzdem, da es eine einfache Lösung für Personen ist, die nicht wie Sie eine globale Suche durchführen.

Ich habe den regulären Ausdruck in der Antwort vereinfacht, um klarer zu sein (dies ist keine Lösung für Ihr genaues Problem).

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

Das sieht aufgrund der Kommentare ausführlicher aus als es ist. So sieht es ohne Kommentare aus

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

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

Beachten Sie, dass alle nicht übereinstimmenden Gruppen im Array als aufgeführt werden undefined Werte aufgeführt werden.

Diese Lösung verwendet den ES6-Spread-Operator, um das Array von Regex-spezifischen Werten zu bereinigen. Sie müssen Ihren Code über Babel ausführen, wenn Sie IE11-Unterstützung wünschen.

Daniel Tonon
quelle
0

Hier ist eine einzeilige Lösung ohne while-Schleife .

Die Reihenfolge bleibt in der resultierenden Liste erhalten.

Die möglichen Nachteile sind

  1. Es klont die Regex für jedes Match.
  2. Das Ergebnis liegt in einer anderen Form als die erwarteten Lösungen vor. Sie müssen sie noch einmal verarbeiten.
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 hat Jang gewonnen
quelle
0

Ich vermute, wenn es Randfälle wie zusätzliche oder fehlende Leerzeichen geben würde, könnte dieser Ausdruck mit weniger Grenzen auch eine Option sein:

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

Wenn Sie den Ausdruck untersuchen / vereinfachen / ändern möchten, wurde dies im oberen rechten Bereich von regex101.com erläutert . Wenn Sie möchten , können Sie in diesem Link auch sehen , wie es mit einigen Beispieleingaben übereinstimmt.


Prüfung

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

RegEx Circuit

jex.im visualisiert reguläre Ausdrücke:

Geben Sie hier die Bildbeschreibung ein

Emma
quelle
-5

Hier ist meine Antwort:

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
quelle
3
Ihre Eingabezeichenfolge ( str) hat das falsche Format (zu viele harte Klammern). Sie erfassen nur den Schlüssel, nicht den Wert. Ihr Code hat einen Syntaxfehler und wird nicht ausgeführt (die letzten Klammern). Wenn Sie eine "alte" Frage mit einer bereits akzeptierten Antwort beantworten, stellen Sie sicher, dass Sie mehr Wissen und eine bessere Antwort als die bereits akzeptierte hinzufügen. Ich glaube nicht, dass Ihre Antwort das tut.
Gelöscht