RegEx для извлечения всех совпадений из строки, используя RegExp.exec

176

Я пытаюсь разобрать следующий вид строки:

[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"также соответствует этой модели. Как я могу вернуть все спички?

Гэтлин
источник
Возможно, мое регулярное выражение неверно и / или я просто неправильно использую средства регулярного выражения в 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/

lawnsea
источник
8
Почему не whileвместо do … while?
Гамбо
15
Использование цикла while делает его немного неловким для инициализации m. Вы должны либо написать while(m = re.exec(s)), что является анти-паттерном IMO, либо писать m = re.exec(s); while (m) { ... m = re.exec(s); }. Я предпочитаю do ... if ... whileидиому, но другие методы тоже подойдут.
газон
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"]

Anis
источник
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); });
Christophe
источник
Я думаю, что это слишком сложно. Тем не менее, приятно знать о различных способах выполнения простой вещи (я голосую за ваш ответ).
Arashsoft
24
Это нелогичный код. Вы не «заменяете» что-либо в каком-либо значимом смысле. Это просто использование какой-то функции для другой цели.
Люк Маурер
6
@dudewad, если бы инженеры просто следовали правилам, не думая нестандартно, мы бы даже не думали о посещении других планет прямо сейчас ;-)
Кристоф
1
@ dudewad извините, я не вижу здесь ленивую часть. Если бы тот же самый метод назывался «процесс», а не «заменить», все было бы в порядке. Боюсь, вы просто застряли в терминологии.
Кристоф
1
@ Кристоф Я определенно не застрял в терминологии. Я застрял на чистом коде. Использование вещей, которые предназначены для одной цели для другой цели, называется «хаки» по причине. Это создает запутанный код, который трудно понять и чаще всего страдает от производительности. Тот факт, что вы ответили на этот вопрос без регулярного выражения, сам по себе делает его недействительным, поскольку ОП спрашивает, как это сделать с помощью регулярного выражения. Однако я считаю важным поддерживать это сообщество на высоком уровне, поэтому я придерживаюсь того, что я сказал выше.
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]);
}

Это основано на ответе Lawnsea, но короче.

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

lovasoa
источник
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в конце определения регулярного выражения перемещение указателя вперед при каждом выполнении.

noego
источник
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 . Также доступны полифилы / прокладки .

woojoo666
источник
Мне действительно это нравится, но он еще не совсем попал в Firefox 66.0.3. У Caniuse пока нет списка поддержки. Я с нетерпением жду этого. Я вижу, что это работает в Chromium 74.0.3729.108.
Лонни Бест
1
@LonnieBest да, вы можете увидеть раздел совместимости на странице MDN, на которую я ссылался. Похоже, что Firefox начал поддерживать его в версии 67. Тем не менее, не рекомендуется использовать его, если вы пытаетесь отправить продукт. Доступны полифилы / прокладки, которые я добавил к своему ответу
woojoo666
10

Основано на функции Агуса, но я предпочитаю возвращать только значения совпадений:

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) ]
sdgfsdh
источник
1
Опечатка: if (m)должно бытьif (match)
Ботье
Массивы уже итерируемы, поэтому каждый, кто возвращает массив совпадений, также возвращает итерацию. Что будет лучше, если вы сохраните консольный журнал массива, браузер сможет распечатать содержимое. Но консольная регистрация универсальной итерации просто дает вам [объект объекта] {...}
StJohn3D
Все массивы являются итеративными, но не все итерируемые являются массивами. Итерация лучше, если вы не знаете, что нужно делать вызывающей стороне. Например, если вы просто хотите, чтобы первое совпадение было более эффективным, итеративный.
sdgfsdh
Ваша мечта становится реальностью, браузеры развертывают поддержку встроенного, matchAllкоторый возвращает итерацию : D
woojoo666
1
Я наткнулся на этот ответ после матча все реализации. Я написал код для браузера JS, который его поддерживал, но на самом деле Node этого не сделал. Это ведет себя одинаково, чтобы соответствовать всем, поэтому мне не пришлось переписывать вещи - ура!
user37309
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, ввод: "мыши любят нарезать рис", группы: не определено]

// ["dice", index: 13, input: "мыши любят нарезать рис", группы: undefined]

// ["rice", index: 18, input: "мыши любят нарезать рис", группы: undefined]

В настоящее время поддерживается в Chrome, Firefox, Opera. В зависимости от того, когда вы читаете это, проверьте эту ссылку, чтобы увидеть ее текущую поддержку.

iuliu.net
источник
Superb! Но все же важно помнить, что регулярное выражение должно иметь флаг, 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

Вот решение в одну строку без цикла while .

Порядок сохраняется в результирующем списке.

Потенциальные недостатки

  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 ] ]
Jae Won Jang
источник
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(','));
Daguang
источник
3
Ваша входная строка ( str) имеет неправильный формат (слишком много жестких скобок). Вы только захватите ключ, а не значение. Ваш код имеет синтаксическую ошибку и не выполняется (последние скобки). Если вы отвечаете на «старый» вопрос уже полученным ответом, убедитесь, что вы добавили больше знаний и лучший ответ, чем уже принятый. Я не думаю, что ваш ответ делает это.
Очищенные