RegEx для вилучення всіх збігів із рядка за допомогою RegExp.exec


175

Я намагаюся проаналізувати такий тип рядка:

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

де всередині є довільний ключ: пари "val". Я хочу схопити ім'я ключа та значення. Для тих, хто цікавиться, я намагаюся розібрати формат бази даних воїна.

Ось мій тестовий рядок:

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

що покликане підкреслити, що все, що може бути в ключі чи значенні, окрім простору, немає пробілів навколо колонок і значень завжди в подвійних лапках.

У вузлі це мій вихід:

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

Але description:"aoeu"також відповідає цій схемі. Як я можу отримати всі матчі назад?


Можливо, мій регекс неправильний і / або що я просто використовую засоби regex у JavaScript неправильно. Здається, це працює:> var s = "П'ятнадцять - це 15, а вісім - 8"; > var re = / \ d + / g; > var m = s.match (re); m = ['15', '8']
Гатлін

6
Тепер у Javascript є функція .match (): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Використовується так:"some string".match(/regex/g)
Stefnotch

Відповіді:


237

Продовжуйте дзвонити re.exec(s)в циклі, щоб отримати всі збіги:

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

Спробуйте з цим JSFiddle: https://jsfiddle.net/7yS2V/


8
Чому б не whileзамість цього do … while?
Gumbo

15
Використання циклу в той час робить злегка незручне ініціалізацію m. Вам або потрібно писати while(m = re.exec(s)), що є анти-візерунком IMO, або вам потрібно писати m = re.exec(s); while (m) { ... m = re.exec(s); }. Я віддаю перевагу do ... if ... whileідіомі, але також працювали б інші методи.
lawnsea

14
якщо це зробити в хромі, це призвело до збою моєї вкладки.
EdgeCaseBerg

47
@EdgeCaseBerg Потрібно gвстановити прапор, інакше внутрішній покажчик не буде переміщений вперед. Docs .
Тім

12
Інший момент полягає в тому, що якщо регулярний вираз може відповідати порожній рядку, це буде нескінченна петля
FabioCosta

139

str.match(pattern), якщо patternмає глобальний прапор g, поверне всі збіги як масив.

Наприклад:

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


15
Остерігайтеся: збіги - це не відповідні об’єкти, а відповідні рядки. Наприклад, немає доступу до груп у "All of us except @Emran:emran26, @Raju:raju13 and @Noman:noman42".match(/@(\w+):(\w+)/g)(які повернуться ["@Emran:emran26", "@Raju:raju13", "@Noman:noman42"])
madprog

4
@madprog, Правильно, це найпростіший спосіб, але не підходить, коли значення групи важливі.
Аніс

1
Це не працює для мене. Я отримую лише перший матч.
Ентоні Робертс

7
@AnthonyRoberts потрібно додати прапор "g". /@\w/gабоnew RegExp("@\\w", "g")
Аруна Герат

88

Для перегляду всіх збігів можна скористатися replaceфункцією:

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

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

Я думаю, що це занадто складно. Однак приємно знати про різні способи зробити просту річ (я голосую за вашу відповідь).
Arashsoft

24
Це контрінтуїтивний код. Ви нічого не «замінюєте» у жодному змістовному сенсі. Це просто використання якоїсь функції з іншою метою.
Лука Маурер

6
@dudewad якби інженери просто дотримувались правил, не думаючи поза межами коробки, ми б навіть не думали про відвідування інших планет прямо зараз ;-)
Крістоф

1
@dudewad вибачте, я не бачу тут ледачої частини. Якби точно такий же метод називався «процес» замість «заміни», ви б з ним все нормально. Боюся, що ви просто затрималися на термінології.
Крістоф

1
@Christophe Я точно не застряг у термінології. Я затримався на чистому коді. Використання речей, які призначені для однієї цілі для іншої мети, називається причиною "хакі". Це створює заплутаний код, який важко зрозуміти і частіше за все страждає від продуктивності. Той факт, що ви відповіли на це запитання без перенаправлення, саме по собі робить його недійсною відповіддю, оскільки ОП запитує, як це зробити з регулярним виразом. Однак я вважаю важливим підтримувати цю спільноту на високому рівні, тому я відстоюю те, що я говорив вище.
dudewad

56

Це рішення

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

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

Це ґрунтується на відповіді газону, але коротше.

Зауважте, що прапор `g 'повинен бути встановлений для переміщення внутрішнього вказівника вперед через виклики.


17
str.match(/regex/g)

повертає всі збіги як масив.

Якщо з якихось загадкових причин вам потрібна додаткова інформація exec, яка є альтернативою попереднім відповідям, ви можете зробити це за допомогою рекурсивної функції замість циклу, як описано нижче (що також виглядає крутіше).

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)

як зазначено в коментарях раніше, важливо мати gв кінці визначення регулярного вираження переміщення вказівника вперед при кожному виконанні.


1
так. рекурсивно виглядає елегантно і крутіше. Ітераційні петлі прямі вперед, простіше в обслуговуванні та налагодження.
Енді N

11

Ми нарешті починаємо бачити вбудовану matchAllфункцію, дивіться тут опис та таблицю сумісності . Схоже, станом на травень 2020 року підтримуються Chrome, Edge, Firefox та Node.js (12+), але не IE, Safari та Opera. Здається, він був складений у грудні 2018 року тому дайте йому трохи часу, щоб охопити всі браузери, але я вірю, що він туди потрапить.

Вбудована matchAllфункція приємна тим, що вона повертає ітерабельний . Він також повертає захоплюючі групи для кожного матчу! Таким чином, ви можете робити такі речі

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

Також здається, що кожен об'єкт відповідності використовує той самий формат, що і match(). Таким чином , кожен об'єкт являє собою масив з сірникових і захоплення груп, поряд з трьома додатковими властивостями index, inputі groups. Так виглядає:

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

Для отримання додаткової інформації про сторінку matchAllтакож існує розробник Google . Також є поліфіли / прокладки .


Мені це дуже подобається, але він ще не зовсім приземлився у Firefox 66.0.3. У Каніуса ще немає списку підтримки щодо цього. Я з нетерпінням чекаю цього. Я бачу, як це працює в Chromium 74.0.3729.108.
Lonnie Best

1
@LonnieBest Так, ви можете побачити розділ сумісності сторінки MDN, яку я пов’язав. Схоже, Firefox почав підтримувати його у версії 67. Все ж не рекомендував би використовувати його, якщо ви намагаєтеся поставити товар. Є поліфіли / прокладки, на які я додав свою відповідь
woojoo666

10

Виходячи з функції Agus, але я віддаю перевагу повернути лише значення відповідності:

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

Інтерабелі приємніші:

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

Використання в циклі:

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

Або якщо ви хочете масив:

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

1
Друкарська помилка: if (m)має бутиif (match)
Botje

Масиви вже ітерабельні, тому всі, хто повертає масив збігів, також повертають ітерабелі. Що краще, якщо консольний журнал масиву браузера може насправді роздрукувати вміст. Але консольний запис загального ітерабельного просто отримує вас [об'єкт] {...}
StJohn3D

Усі масиви є ітерабельними, але не всі ітерабелі є масивами. Ітерабельний варіант є кращим, якщо ви не знаєте, що потрібно буде робити абоненту. Наприклад, якщо ви просто хочете, щоб перший матч ітерабельний був більш ефективним.
sdgfsdh

Ваша мрія стає реальністю, браузери впроваджують підтримку вбудованого matchAllмодуля, який повертає ітерабельний : D
woojoo666

1
Я натрапив на цю відповідь після виконання матчуВсе реалізація. Я написав код для JS браузера, який його підтримував, але Node насправді цього не зробив. Це поводиться так само, як matchAll, тому мені не довелося переписувати речі - Ура!
користувач37309

8

Якщо у вас ES9

(Значить, якщо ваша система: Chrome, Node.js, Firefox тощо підтримує Ecmascript 2019 або новіші версії)

Використовуйте нову yourString.matchAll( /your-regex/ ).

Якщо у вас немає ES9

Якщо у вас старша система, ось функція для легкої копіювання та вставки

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
}

Приклад використання:

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

Виходи:

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

5

Ось моя функція отримувати відповідність:

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

Це рішення запобігає нескінченним циклам, коли ви забудете додати глобальний прапор.
user68311

2

Оскільки ES9, тепер існує простіший, кращий спосіб отримати всі збіги разом з інформацією про групи захоплення та їх індекс:

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

// ["миші", індекс: 0, вхід: "миші люблять нарізати рисом", групи: невизначено]

// ["кістки", індекс: 13, вхід: "миші люблять нарізати рисом", групи: невизначено]

// ["рис", індекс: 18, введення: "миші люблять нарізати рис", групи: невизначено]

Зараз він підтримується в Chrome, Firefox, Opera. Залежно від того, коли ви читаєте це, перевірте це посилання, щоб побачити його поточну підтримку.


Чудово! Але все ж важливо пам’ятати, що у регулярному виразі має бути прапор, gі його lastIndexслід скинути до 0 перед викликом matchAll.
Н. Кудрявцев

1

Використовуй це...

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

Він поверне масив усіх матчів ... Це спрацювало б чудово .... Але пам’ятайте, що групи не братимуть до уваги .. Він просто поверне повну відповідність ...


0

Я б напевно рекомендував використовувати функцію String.match () та створити для неї відповідний RegEx. Мій приклад - зі списком рядків, який часто необхідний під час сканування введень користувачів на ключові слова та фрази.

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

Сподіваюся, це допомагає!


0

Це насправді не допоможе у вашій складнішій проблемі, але я все-таки публікую це, оскільки це просте рішення для людей, які не роблять глобального пошуку, як ви.

Я спростив регулярний вираз у відповіді, щоб бути зрозумілішим (це не є вирішенням вашої точної проблеми).

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

Це виглядає більш багатослівно, ніж це через коментарі, саме так виглядає без коментарів

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

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

Зауважте, що будь-які групи, які не відповідають, будуть вказані в масиві як undefinedзначення.

Це рішення використовує оператор розповсюдження ES6 для очищення масиву специфічних значень регулярних виразів. Вам потрібно буде запустити свій код через Babel, якщо ви хочете підтримку IE11.


0

Ось однорядне рішення без циклу .

Порядок зберігається в отриманому списку.

Потенційні мінуси є

  1. Він клонує регекс для кожного матчу.
  2. Результат - у іншій формі, ніж очікувані рішення. Вам потрібно буде обробити їх ще раз.
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

Я здогадуюсь, що якщо будуть крайові регістри, такі як додаткові чи відсутні пробіли, це вираз із меншими межами також може бути варіантом:

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

Якщо ви хочете вивчити / спростити / змінити вираз, це було пояснено на верхній правій панелі regex101.com . Якщо ви хочете, ви також можете подивитися за цим посиланням , як це буде відповідати деяким зразкам даних.


Тест

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 візуалізує регулярні вирази:

введіть тут опис зображення


-5

Ось моя відповідь:

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
Ваш вхідний рядок ( str) має неправильний формат (занадто багато жорстких дужок). Ви захоплюєте лише ключ, а не значення. Ваш код має синтаксичну помилку і не виконується (останні дужки). Якщо ви відповідаєте на "старе" питання вже прийнятою відповіддю, переконайтеся, що ви додали більше знань і кращу відповідь, ніж уже прийняте. Я не думаю, що ваша відповідь це робить.
Очищено
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.