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

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

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

где внутри есть произвольные пары ключ: "val". Я хочу получить имя ключа и значение. Для любопытных я пытаюсь разобрать формат базы данных Task Warrior.

Вот моя тестовая строка:

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


person gatlin    schedule 12.06.2011    source источник
comment
Возможно, мое регулярное выражение неверно и / или я просто неправильно использую средства регулярного выражения в JavaScript. Кажется, это работает: ›var s = Пятнадцать равно 15, а восемь равно 8; ›Var re = / \ d + / g; ›Var m = s.match (re); m = ['15', '8']   -  person gatlin    schedule 12.06.2011
comment
В Javascript теперь есть функция .match (): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ Используется так: "some string".match(/regex/g)   -  person Stefnotch    schedule 05.03.2016


Ответы (18)


Продолжайте вызывать 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/

person lawnsea    schedule 12.06.2011
comment
Почему не while вместо do … while? - person Gumbo; 12.06.2011
comment
Использование цикла while делает инициализацию m немного неудобной. Вы должны либо написать while(m = re.exec(s)), что является IMO антипаттерном, либо вы должны написать m = re.exec(s); while (m) { ... m = re.exec(s); }. Я предпочитаю идиому do ... if ... while, но подойдут и другие методы. - person lawnsea; 12.06.2011
comment
выполнение этого в хроме привело к сбою вкладки. - person EdgeCaseBerg; 16.12.2014
comment
@EdgeCaseBerg У вас должен быть установлен флаг g, иначе внутренний указатель не будет перемещен вперед. Документы. - person Tim; 25.07.2015
comment
@lawnsea Как насчет цикла for? - person ; 22.03.2016
comment
Кроме того, регулярное выражение должно быть в переменной, чтобы указатель увеличивал ее позицию. наличие регулярного выражения времени выполнения приведет к бесконечному циклу. - person satin; 06.08.2016
comment
Разве вы не можете просто сделать петлю while(m = re.exec(a)) { console.log(m[1], m[2]); }? - person Jenna Sloan; 28.12.2016
comment
Другой момент заключается в том, что если регулярное выражение может соответствовать пустой строке, это будет бесконечный цикл. - person FabioCosta; 08.06.2017
comment
@lawnsea, пожалуйста, обновите свой ответ на: let m; while (m = re.exec(s)) { console.log(m[1], m[2]); } - person Offenso; 26.12.2018
comment
@FabioCosta Только что столкнулся с проблемой с бесконечным циклом. Это настолько неприятно, что, чтобы избежать этой ошибки, каждый должен вручную проверять равенство текущего и предыдущего совпадений и, если они совпадают, увеличивать RegExps lastIndex. Ответ обязательно должен быть дополнен этой информацией. Хорошо, что новый метод matchAll для строк выполняет эту проверку сам по себе, и теперь мы можем вообще отказаться от использования циклов (спасибо @ iuliu.net за информацию). Последний неприятный момент заключается в том, что RegExps содержат состояние и поэтому должны копироваться вручную, чтобы избежать нежелательных мутаций. - person N. Kudryavtsev; 07.11.2019

str.match(pattern), если pattern имеет глобальный флаг g, вернет все совпадения в виде массива.

Например:

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

person Anis    schedule 25.04.2017
comment
Осторожно: совпадения не совпадают с объектами, а с совпадающими строками. Например, нет доступа к группам в "All of us except @Emran:emran26, @Raju:raju13 and @Noman:noman42".match(/@(\w+):(\w+)/g) (что вернет ["@Emran:emran26", "@Raju:raju13", "@Noman:noman42"]) - person madprog; 18.08.2017
comment
@madprog, правильно, это самый простой способ но не годится, когда групповые значения существенны. - person Anis; 13.09.2017
comment
У меня это не работает. Я получаю только первое совпадение. - person Anthony Roberts; 31.12.2018
comment
@AnthonyRoberts, вы должны добавить флаг g. /@\w/g или new RegExp("@\\w", "g") - person Aruna Herath; 28.01.2019

Чтобы просмотреть все совпадения, вы можете использовать функцию replace:

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

s.replace(re, function(match, g1, g2) { console.log(g1, g2); });
person Christophe    schedule 12.07.2012
comment
Я думаю, это слишком сложно. Тем не менее, приятно знать о различных способах выполнения простых вещей (я голосую за ваш ответ). - person Arashsoft; 13.05.2016
comment
Это нелогичный код. Вы ничего не «заменяете» в каком-либо значимом смысле. Он просто использует некоторую функцию для другой цели. - person Luke Maurer; 27.07.2017
comment
@LukeMaurer прав. Это использует средство Javascript, предназначенное для замены строки для выполнения поиска строки. Конечно, это проще, но проложило ли НАСА «Новые горизонты» вокруг Плутона по простому маршруту или по правильному маршруту? Мы инженеры и должны уважать, что делать это легко - не всегда правильно. Уклонение от изучения регулярных выражений из-за того, что это сложно, - это просто ленивое программирование. Я не хочу быть насмешливым, скорее, честным. Это проявляется во многом, когда другие разработчики тратят полчаса, пытаясь выяснить, что делает третья строка этого примера. - person dudewad; 06.09.2018
comment
@dudewad, если бы инженеры просто следовали правилам, не думая нестандартно, мы бы даже не думали о посещении других планет прямо сейчас ;-) - person Christophe; 08.09.2018
comment
Да, но это не творческое мышление, это ленивое мышление. Вы можете сказать, когда есть лучшее решение, и это один из таких случаев. - person dudewad; 13.09.2018
comment
@dudewad извините, я не вижу здесь ленивого. Если бы тот же самый метод был вызван процессом вместо замены, вы бы согласились. Боюсь, вы просто зациклились на терминологии. - person Christophe; 17.09.2018
comment
@Christophe Я определенно не зацикливаюсь на терминологии. Я застрял на чистом коде. Использование вещей, предназначенных для одной цели, для другой цели, не зря называется хакерством. Это создает запутанный код, который трудно понять и часто страдает с точки зрения производительности. Тот факт, что вы ответили на этот вопрос без регулярного выражения, сам по себе делает его недопустимым, поскольку OP спрашивает, как это сделать с помощью регулярного выражения. Однако я считаю важным поддерживать это сообщество на высоком уровне, поэтому я придерживаюсь того, что сказал выше. - person dudewad; 19.09.2018
comment
@dudewad, что вы имеете в виду, без регулярного выражения? - person Christophe; 04.10.2018
comment
Sry - чтобы прояснить это, я имею в виду, что я не делаю этого с объектом регулярного выражения, скорее вы используете дополнительный уровень вместо использования самого регулярного выражения. - person dudewad; 04.10.2018
comment
@dudewad, ладно. Как это делает ответ недействительным? Я не против, чтобы вы не соглашались, даже голосовали против, но давайте сделаем это добросовестно. - person Christophe; 26.12.2018

Это решение

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

person lovasoa    schedule 05.06.2014

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

person eaorak    schedule 21.11.2018
comment
да. рекурсивный выглядит элегантнее и круче. Итерационные циклы просты, их легче поддерживать и отлаживать. - person Andy N; 06.04.2019
comment
Мне нравятся рекурсивные решения, потому что; Я люблю рекурсивные решения - person Ben Winding; 17.12.2020

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

person woojoo666    schedule 08.04.2019
comment
Мне это очень нравится, но в Firefox 66.0.3 он еще не реализован. У Caniuse еще нет списка поддержки по этому поводу. Я с нетерпением жду этого. Я вижу, что он работает в Chromium 74.0.3729.108. - person Lonnie Best; 08.05.2019
comment
@LonnieBest да, вы можете увидеть раздел совместимости Страницу MDN, на которую я указал. Похоже, Firefox начал поддерживать его в версии 67. По-прежнему не рекомендую использовать его, если вы пытаетесь отправить продукт. Доступны полифиллы / прокладки, которые я добавил в свой ответ - person woojoo666; 08.05.2019

Если у вас 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,[...new Set("g"+regexPattern.flags)].join(""))
    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 ] ]
person Jeff Hykin    schedule 21.01.2018

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

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;]
person bob    schedule 21.07.2015

Итерируемые объекты лучше:

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

Вот моя функция для получения совпадений:

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]);
});
person Agus Syahputra    schedule 02.05.2015
comment
Это решение предотвращает бесконечные циклы, когда вы забываете добавить глобальный флаг. - person user68311; 24.11.2019

Начиная с ES9, теперь есть более простой и лучший способ получить все совпадения вместе с информацией о группах захвата и их индексе:

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

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

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

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

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

person iuliu.net    schedule 09.05.2019
comment
Превосходно! Но все же важно помнить, что регулярное выражение должно иметь флаг g, и его lastIndex следует сбросить до 0 перед вызовом matchAll. - person N. Kudryavtsev; 07.11.2019

Использовать этот...

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

Он вернет массив всех совпадений ... Это будет работать нормально ... Но помните, что он не будет учитывать группы в учетной записи ... Он просто вернет полные совпадения ...

person Subham Debnath    schedule 11.03.2018

Я бы определенно рекомендовал использовать функцию 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"]

Надеюсь это поможет!

person Sebastian Scholl    schedule 06.06.2018

На самом деле это не поможет с вашей более сложной проблемой, но я все равно публикую это, потому что это простое решение для людей, которые не выполняют глобальный поиск, как вы.

Я упростил регулярное выражение в ответе, чтобы было понятнее (это не решение вашей точной проблемы).

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.

person Daniel Tonon    schedule 10.07.2018

Вот однострочное решение без цикла 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 ] ]
person Jae Won Jang    schedule 23.05.2019

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

^\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

jex.im визуализирует обычный выражения:

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

person Emma    schedule 11.08.2019

Если вы умеете использовать matchAll, вот трюк:

Array.From имеет параметр 'selector', поэтому вместо того, чтобы получить массив неудобных результатов 'совпадений', вы можете спроецировать его на то, что вам действительно нужно:

Array.from(str.matchAll(regexp), m => m[0]);

Если вы назвали группы, например. (/(?<firstname>[a-z][A-Z]+)/g) вы можете сделать это:

Array.from(str.matchAll(regexp), m => m.groups.firstName);
person Simon_Weaver    schedule 22.12.2020

Вот мой ответ:

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(','));
person daguang    schedule 03.07.2017
comment
Ваша входная строка (str) имеет неправильный формат (слишком много скобок). Вы фиксируете только ключ, а не значение. В вашем коде есть синтаксическая ошибка, и он не выполняется (последние скобки). Если вы отвечаете на старый вопрос уже принятым ответом, убедитесь, что вы добавили больше знаний и лучший ответ, чем уже принятый. Я не думаю, что ваш ответ на это. - person Cleared; 03.07.2017