RegEx, aby wyodrębnić wszystkie dopasowania z ciągu przy użyciu RegExp.exec

175

Próbuję przeanalizować następujący rodzaj ciągu:

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

gdzie w środku znajduje się dowolny klucz: pary „val”. Chcę pobrać nazwę klucza i wartość. Dla ciekawskich próbuję przeanalizować format bazy danych wojownika zadaniowego.

Oto mój ciąg testowy:

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

co ma na celu podkreślenie, że w kluczu lub wartości może znajdować się wszystko poza spacją, bez spacji wokół dwukropków, a wartości są zawsze w cudzysłowach.

W węźle to jest moje wyjście:

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

Ale description:"aoeu"również pasuje do tego wzorca. Jak mogę odzyskać wszystkie mecze?

gatlin
źródło
Może się zdarzyć, że moje wyrażenie regularne jest błędne i / lub po prostu niepoprawnie używam funkcji wyrażeń regularnych w JavaScript. To wydaje się działać:> var s = "Piętnaście to 15, a osiem to 8"; > var re = / \ d + / g; > var m = s.match (ponownie); m = ['15', '8']
gatlin
6
Javascript ma teraz funkcję .match (): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ ... Używane w ten sposób:"some string".match(/regex/g)
Stefnotch

Odpowiedzi:

237

Kontynuuj sprawdzanie re.exec(s)w pętli, aby uzyskać wszystkie dopasowania:

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

Wypróbuj z tym JSFiddle: https://jsfiddle.net/7yS2V/

trawnik
źródło
8
Dlaczego nie whilezamiast do … while?
Gumbo
15
Używanie pętli while sprawia, że ​​inicjalizacja m jest nieco niewygodna. Albo musisz napisać while(m = re.exec(s)), co jest anty-wzorcową IMO, albo musisz napisać m = re.exec(s); while (m) { ... m = re.exec(s); }. Wolę do ... if ... whileidiom, ale inne techniki też się sprawdzą.
trawnik
14
robienie tego na chromie spowodowało awarię karty.
EdgeCaseBerg
47
@EdgeCaseBerg Musisz mieć gustawioną flagę, w przeciwnym razie wewnętrzny wskaźnik nie zostanie przesunięty do przodu. Dokumenty .
Tim
12
Inną kwestią jest to, że jeśli wyrażenie regularne może dopasować pusty ciąg, będzie to nieskończona pętla
FabioCosta
139

str.match(pattern), jeśli patternma flagę globalną g, zwróci wszystkie dopasowania jako tablicę.

Na przykład:

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

Anis
źródło
15
Uwaga: dopasowania nie pasują do obiektów, ale pasujące ciągi. Na przykład nie ma dostępu do grup w "All of us except @Emran:emran26, @Raju:raju13 and @Noman:noman42".match(/@(\w+):(\w+)/g)(które powrócą ["@Emran:emran26", "@Raju:raju13", "@Noman:noman42"])
madprog
4
@madprog, racja, to najłatwiejszy sposób, ale nieodpowiedni, gdy wartości grupy są niezbędne.
Anis
1
To nie działa na mnie. Dostaję tylko pierwszy mecz.
Anthony Roberts
7
@AnthonyRoberts musisz dodać flagę „g”. /@\w/glubnew RegExp("@\\w", "g")
Aruna Herath
88

Aby zapętlić wszystkie dopasowania, możesz użyć replacefunkcji:

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

s.replace(re, function(match, g1, g2) { console.log(g1, g2); });
Christophe
źródło
Myślę, że to jest po prostu zbyt skomplikowane. Jednak miło jest wiedzieć o różnych sposobach zrobienia prostej rzeczy (głosuję za twoją odpowiedzią).
Arashsoft
24
To sprzeczny z intuicją kod. Nie „zastępujesz” niczego w żadnym sensownym sensie. Po prostu wykorzystuje jakąś funkcję do innego celu.
Luke Maurer
6
@dudewad, gdyby inżynierowie po prostu przestrzegali zasad, nie myśląc nieszablonowo, nie myślelibyśmy teraz o odwiedzeniu innych planet ;-)
Christophe
1
@dudewad przepraszam, nie widzę tutaj leniwej części. Jeśli dokładnie ta sama metoda została nazwana „procesem” zamiast „zamień”, to nie przeszkadza. Obawiam się, że utknąłeś na terminologii.
Christophe,
1
@Christophe Zdecydowanie nie utknąłem na terminologii. Utknąłem na czystym kodzie. Używanie rzeczy, które są przeznaczone do jednego celu w innym celu, nie bez powodu nazywa się „hacky”. Tworzy zagmatwany kod, który jest trudny do zrozumienia i często cierpi z powodu wydajności. Fakt, że odpowiedziałeś na to pytanie bez wyrażenia regularnego, sprawia, że ​​jest to nieprawidłowa odpowiedź, ponieważ OP pyta, jak to zrobić za pomocą wyrażenia regularnego. Uważam jednak, że ważne jest, aby utrzymywać tę społeczność na wysokim poziomie, dlatego podtrzymuję to, co powiedziałem powyżej.
dudewad
56

To jest rozwiązanie

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

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

Jest to oparte na odpowiedzi trawnika, ale krócej.

Zauważ, że flaga `g 'musi być ustawiona, aby przesuwać wewnętrzny wskaźnik do przodu przez wywołania.

lovasoa
źródło
17
str.match(/regex/g)

zwraca wszystkie dopasowania jako tablicę.

Jeśli z jakiegoś tajemniczego powodu potrzebujesz dodatkowych informacji exec, jako alternatywa dla poprzednich odpowiedzi, możesz to zrobić za pomocą funkcji rekurencyjnej zamiast pętli w następujący sposób (która również wygląda fajniej).

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)

jak stwierdzono w poprzednich komentarzach, ważne jest, aby gna końcu definicji wyrażenia regularnego przesuwać wskaźnik do przodu w każdym wykonaniu.

noego
źródło
1
tak. rekurencyjny wygląda elegancko i fajniej. Pętle iteracyjne są proste, łatwiejsze w utrzymaniu i debugowaniu.
Andy N,
11

W końcu zaczynamy widzieć wbudowaną matchAllfunkcję, zobacz tutaj opis i tabelę zgodności . Wygląda na to, że od maja 2020 roku obsługiwane są przeglądarki Chrome, Edge, Firefox i Node.js (12+), ale nie przeglądarki IE, Safari i Opera. Wygląda na to, że został sporządzony w grudniu 2018 roku więc daj mu trochę czasu, aby dotrzeć do wszystkich przeglądarek, ale wierzę, że się tam dostanie.

Funkcja wbudowana matchAlljest fajna, ponieważ zwraca iterowalną . Zwraca również grupy przechwytywania dla każdego meczu! Możesz więc robić takie rzeczy jak

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

Wydaje się również, że każdy obiekt dopasowania używa tego samego formatu co match(). Zatem każdy obiekt jest tablicą grup dopasowywania i przechwytywania, wraz z trzema dodatkowymi właściwościamiindex , input, i groups. A więc wygląda to tak:

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

Więcej informacji na ten temat matchAllmożna znaleźć na stronie Google dla programistów . Dostępne są również wypełnienia / podkładki .

woojoo666
źródło
Bardzo mi się to podoba, ale nie wylądowało jeszcze w Firefoksie 66.0.3. Caniuse też nie ma jeszcze listy wsparcia na ten temat. Nie mogę się doczekać tego. Widzę, że działa w Chromium 74.0.3729.108.
Lonnie Best
1
@LonnieBest yeah, możesz zobaczyć sekcję dotyczącą kompatybilności na stronie MDN , którą połączyłem. Wygląda na to, że Firefox zaczął go obsługiwać w wersji 67. Nadal nie polecam korzystania z niego, jeśli próbujesz wysłać produkt. Są dostępne polyfills / shims, które dodałem do mojej odpowiedzi
woojoo666
10

Oparty na funkcji Agusa, ale wolę zwracać tylko wartości dopasowania:

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;]
pion
źródło
8

Iterables są ładniejsze:

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

Użycie w pętli:

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

Lub jeśli chcesz mieć tablicę:

[ ...matches('abcdefabcdef', /ab/g) ]
sdgfsdh
źródło
1
Literówka: if (m)powinno byćif (match)
Botje
Tablice są już iterowalne, więc każdy zwracający tablicę dopasowań również zwraca iterowalne. Co lepsze, jeśli zalogujesz się do tablicy, przeglądarka może faktycznie wydrukować zawartość. Ale rejestrowanie przez konsolę ogólnej iterowalnej wersji powoduje tylko otrzymanie [Object Object] {...}
StJohn3D
Wszystkie tablice są iterowalne, ale nie wszystkie iterowalne są tablicami. Iterowalność jest lepsza, jeśli nie wiesz, co będzie musiał zrobić dzwoniący. Na przykład, jeśli chcesz tylko pierwszego dopasowania, iteracja jest bardziej wydajna.
sdgfsdh
Twoje marzenie staje się rzeczywistością, przeglądarki matchAll
wdrażają
1
Natknąłem się na tę odpowiedź po wdrożeniu meczu. Napisałem kod dla JS przeglądarki, który to obsługiwał, ale Node w rzeczywistości nie. To zachowuje się identycznie, aby dopasować wszystko, więc nie musiałem przepisywać rzeczy - na zdrowie!
user37309
8

Jeśli masz ES9

(To znaczy, jeśli twój system: Chrome, Node.js, Firefox itp. Obsługuje Ecmascript 2019 lub nowszy)

Użyj nowego yourString.matchAll( /your-regex/ ).

Jeśli nie masz ES9

Jeśli masz starszy system, oto funkcja ułatwiająca kopiowanie i wklejanie

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
}

przykład użycia:

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

wyjścia:

[ [ 'blah', index: 0 ], [ 'blah', index: 6 ] ]
Jeff Hykin
źródło
5

Oto moja funkcja, aby uzyskać dopasowania:

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
źródło
To rozwiązanie zapobiega nieskończonym pętlom, gdy zapomnisz dodać flagę globalną.
user68311
2

Od wersji ES9 istnieje teraz prostszy i lepszy sposób na uzyskanie wszystkich dopasowań wraz z informacjami o grupach przechwytywania i ich indeksie:

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

// ["myszy", index: 0, input: "myszy lubią kroić ryż w kostkę", groups: undefined]

// ["kości", indeks: 13, input: "myszy lubią kroić ryż w kostkę", groups: undefined]

// ["ryż", indeks: 18, input: "myszy lubią ryż w kostkę", groups: undefined]

Obecnie jest obsługiwany w Chrome, Firefox, Opera. W zależności od tego, kiedy to czytasz, sprawdź ten link, aby zobaczyć aktualne wsparcie.

iuliu.net
źródło
Wspaniały! Ale nadal ważne jest, aby pamiętać, że wyrażenie regularne powinno mieć flagę gi lastIndexpowinno zostać zresetowane do 0 przed wywołaniem matchAll.
N. Kudryavtsev
1

Użyj tego...

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

Zwróci tablicę wszystkich dopasowań ... To będzie działać dobrze ... Ale pamiętaj, że nie będzie uwzględniać grup ... Zwróci tylko pełne dopasowania ...

Subham Debnath
źródło
0

Zdecydowanie poleciłbym użycie funkcji String.match () i utworzenie dla niej odpowiedniego RegEx. Mój przykład to lista ciągów, która jest często konieczna podczas skanowania danych wejściowych użytkownika pod kątem słów kluczowych i fraz.

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

Mam nadzieję że to pomoże!

Sebastian Scholl
źródło
0

To naprawdę nie pomoże w bardziej złożonym problemie, ale i tak to publikuję, ponieważ jest to proste rozwiązanie dla osób, które nie wykonują wyszukiwania globalnego, tak jak Ty.

Uprościłem wyrażenie regularne w odpowiedzi, aby było jaśniejsze (nie jest to rozwiązanie twojego dokładnego problemu).

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

To wygląda bardziej rozwlekle niż jest z powodu komentarzy, tak to wygląda bez komentarzy

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

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

Zwróć uwagę, że wszelkie grupy, które nie pasują, zostaną wymienione w tablicy jako undefined wartości.

To rozwiązanie wykorzystuje operator rozproszenia ES6 do oczyszczenia tablicy wartości specyficznych dla wyrażenia regularnego. Będziesz musiał uruchomić swój kod przez Babel, jeśli chcesz obsługiwać IE11.

Daniel Tonon
źródło
0

Oto rozwiązanie jednokreskowe bez pętli while .

Kolejność jest zachowywana na wynikowej liście.

Potencjalne wady są

  1. Klonuje wyrażenie regularne dla każdego dopasowania.
  2. Wynik jest w innej formie niż oczekiwane rozwiązania. Będziesz musiał je przetworzyć jeszcze raz.
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 Won Jang
źródło
0

Domyślam się, że gdyby istniały przypadki skrajne, takie jak dodatkowe lub brakujące spacje, to wyrażenie z mniejszymi granicami również może być opcją:

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

Jeśli chcesz zbadać / uprościć / zmodyfikować wyrażenie, zostało to wyjaśnione w prawym górnym panelu regex101.com . Jeśli chcesz, możesz również obejrzeć w tym linku , jak to będzie się zgadzać z niektórymi przykładowymi danymi wejściowymi.


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

RegEx Circuit

jex.im wizualizuje wyrażenia regularne:

wprowadź opis obrazu tutaj

Emma
źródło
-5

Oto moja odpowiedź:

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
źródło
3
Twój ciąg wejściowy ( str) ma nieprawidłowy format (za dużo ostrych nawiasów). Bierzesz tylko klucz, a nie wartość. Twój kod zawiera błąd składni i nie jest wykonywany (ostatnie nawiasy). Jeśli odpowiadasz na „stare” pytanie z już zaakceptowaną odpowiedzią, upewnij się, że dodałeś więcej wiedzy i lepszą odpowiedź niż już zaakceptowana. Nie sądzę, że twoja odpowiedź to robi.
Rozliczono