【问题标题】:RegEx to extract all matches from string using RegExp.execRegEx 使用 RegExp.exec 从字符串中提取所有匹配项
【发布时间】:2011-09-13 11:37:15
【问题描述】:

我正在尝试解析以下类型的字符串:

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

里面有任意 key:"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 是 15,8 是 8"; > var re = /\d+/g; > var m = s.match(re); m = ['15', '8']
  • Javascript 现在有一个 .match() 函数:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 像这样使用:"some string".match(/regex/g)

标签: javascript regex regex-group taskwarrior


【解决方案1】:

继续循环调用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/

【讨论】:

  • 为什么不用while 而不是do … while
  • 使用 while 循环会使初始化 m 有点尴尬。你要么必须写while(m = re.exec(s)),这是一个反模式IMO,或者你必须写m = re.exec(s); while (m) { ... m = re.exec(s); }。我更喜欢 do ... if ... while 成语,但其他技术也可以。
  • 在 chromium 中执行此操作导致我的标签崩溃。
  • @EdgeCaseBerg 您需要设置g 标志,否则内部指针不会向前移动。 Docs.
  • 还有一点是,如果正则表达式可以匹配空字符串,那将是一个无限循环
【解决方案2】:

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

【讨论】:

  • 注意:匹配不是匹配对象,而是匹配字符串。例如,无法访问"All of us except @Emran:emran26, @Raju:raju13 and @Noman:noman42".match(/@(\w+):(\w+)/g) 中的组(将返回["@Emran:emran26", "@Raju:raju13", "@Noman:noman42"]
  • @madprog,对,这是最简单的方法,但不适用于组值必不可少的情况。
  • 这对我不起作用。我只得到第一场比赛。
  • @AnthonyRoberts 您必须添加“g”标志。 /@\w/gnew RegExp("@\\w", "g")
【解决方案3】:

要遍历所有匹配,您可以使用replace 函数:

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

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

【讨论】:

  • 我认为这太复杂了。但是,很高兴了解做一件简单事情的不同方式(我赞成你的回答)。
  • 这是违反直觉的代码。你没有在任何有意义的意义上“替换”任何东西。它只是将某些功能用于不同的目的。
  • @dudewad 如果工程师只是遵循规则而不跳出框框思考,我们现在甚至不会考虑访问其他行星 ;-)
  • @dudewad 抱歉,我没有看到这里的懒惰部分。如果将完全相同的方法称为“处理”而不是“替换”,那么您就可以了。恐怕你只是停留在术语上。
  • @Christophe 我绝对不会拘泥于术语。我被困在干净的代码上。出于某种原因,将用于一个目的的东西用于不同的目的被称为“hacky”。它会创建令人困惑的代码,难以理解,而且往往会在性能方面受到影响。事实上,您在没有正则表达式的情况下回答了这个问题,这使得它成为一个无效的答案,因为 OP 正在询问如何使用正则表达式来做到这一点。然而,我认为让这个社区保持高标准很重要,这就是为什么我坚持我上面所说的。
【解决方案4】:

这是一个解决方案

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

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

这是基于草坪的答案,但更短。

请注意,必须设置“g”标志以跨调用向前移动内部指针。

【讨论】:

    【解决方案5】:
    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)
    

    如之前的 cmets 所述,在正则表达式定义的末尾添加 g 以在每次执行中向前移动指针非常重要。

    【讨论】:

    • 是的。递归看起来优雅而酷。迭代循环很简单,更易于维护和调试。
    • 我喜欢递归解决方案,因为;我喜欢递归解决方案
    【解决方案6】:

    我们终于开始看到内置的matchAll 函数,请参阅here for the description and compatibility table。看起来截至 2020 年 5 月,Chrome、Edge、Firefox 和 Node.js(12+)受支持,但不支持 IE、Safari 和 Opera。好像是drafted in December 2018,所以给它一些时间来访问所有浏览器,但我相信它会到达那里。

    内置的matchAll 函数很好,因为它返回一个iterable。它还为每场比赛返回捕获组!所以你可以做类似的事情

    // 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() 相同的格式。因此,每个对象都是匹配组和捕获组的数组,以及三个附加属性indexinputgroups。所以它看起来像:

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

    有关matchAll 的更多信息,还有Google developers page。还有polyfills/shims可用。

    【讨论】:

    • 我真的很喜欢这个,但它还没有完全登陆 Firefox 66.0.3。 Caniuse 也没有关于它的支持列表。我很期待这个。我确实看到它在 Chromium 74.0.3729.108 中工作。
    • @LonnieBest 是的,您可以看到我链接的MDN page 的兼容性部分。 Firefox 似乎从 67 版开始支持它。如果您尝试发布产品,仍然不建议使用它。有可用的 polyfills/shims,我将其添加到我的答案中
    【解决方案7】:

    如果你有 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 ] ]
    

    【讨论】:

    • 大多数浏览器都支持str.matchAll,这个答案应该在顶部列表中
    【解决方案8】:

    基于 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;]
    

    【讨论】:

      【解决方案9】:

      Iterables 更好:

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

      【讨论】:

      • 错字:if (m) 应该是if (match)
      • 数组已经是可迭代的,所以每个返回匹配数组的人也都返回了可迭代。更好的是,如果您控制台记录一个数组,浏览器实际上可以打印出内容。但是控制台记录一个通用的迭代只会让你 [object Object] { ... }
      • 所有数组都是可迭代的,但并非所有可迭代的都是数组。如果您不知道调用者需要做什么,则可迭代对象会更好。例如,如果您只想要第一个匹配项,则可迭代的效率更高。
      • 你的梦想正在成为现实,浏览器正在推出对a built-in matchAll that returns an iterable的支持:D
      • 我在 matchAll 实现后遇到了这个答案。我为支持它的浏览器 JS 编写了一些代码,但 Node 实际上没有。这与 matchAll 的行为相同,所以我不必重写东西 - 干杯!
      【解决方案10】:

      这是我获取匹配项的函数:

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

      【讨论】:

      • 此解决方案可防止您忘记添加全局标志时出现无限循环。
      【解决方案11】:

      从 ES9 开始,现在有一种更简单、更好的方法来获取所有匹配项,以及有关捕获组及其索引的信息:

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

      // ["mice", index: 0, input: "mice like to dice rice", groups: 未定义]

      // ["dice", index: 13, input: "mice like to dice rice", 组:未定义]

      // ["rice", index: 18, input: "mice like to dice 大米”,组:未定义]

      目前在 Chrome、Firefox、Opera 中支持。根据您阅读本文的时间,请查看 this link 以查看其当前支持。

      【讨论】:

      • 太棒了!但仍然重要的是要记住,正则表达式应该有一个标志 g 并且它的 lastIndex 应该在调用 matchAll 之前重置为 0。
      【解决方案12】:

      如果你能够使用matchAll,这里有个技巧:

      Array.From 有一个“选择器”参数,因此您可以将其投影到您真正需要的位置,而不是最终得到一系列尴尬的“匹配”结果:

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

      如果您已命名组,例如。 (/(?&lt;firstname&gt;[a-z][A-Z]+)/g) 你可以这样做:

      Array.from(str.matchAll(regexp), m => m.groups.firstName);
      

      【讨论】:

        【解决方案13】:

        使用这个...

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

        它将返回一个包含所有匹配项的数组...这样就可以了... 但请记住,它不会考虑组......它只会返回完整的匹配......

        【讨论】:

          【解决方案14】:

          我肯定会推荐使用 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"]
          

          希望这会有所帮助!

          【讨论】:

            【解决方案15】:

            这并不能真正帮助您解决更复杂的问题,但无论如何我都会发布此内容,因为对于不像您那样进行全局搜索的人来说,这是一个简单的解决方案。

            我已将答案中的正则表达式简化为更清晰(这不是您确切问题的解决方案)。

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

            这看起来比因为有 cmets 更冗长,这就是没有 cmets 的样子

            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 扩展运算符来净化正则表达式特定值的数组。如果您想要 IE11 支持,则需要通过 Babel 运行您的代码。

            【讨论】:

              【解决方案16】:

              这是一个没有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 ] ]
              

              【讨论】:

                【解决方案17】:

                我的猜测是,如果存在诸如多余或缺少空格之类的边缘情况,则此边界较少的表达式也可能是一种选择:

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

                如果您想探索/简化/修改表达式,它已经 在右上角的面板上进行了解释 regex101.com。如果你愿意,你 也可以在this link看,怎么搭配 针对一些样本输入。


                测试

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

                正则表达式电路

                jex.im 可视化正则表达式:

                【讨论】:

                  【解决方案18】:

                  这是我的答案:

                  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(','));
                  

                  【讨论】:

                  • 您的输入字符串 (str) 格式错误(硬括号太多)。您只捕获键,而不是值。您的代码有语法错误并且不执行(最后一个括号)。如果您用已经接受的答案回答“旧”问题,请确保您添加更多知识和更好的答案,而不是已经接受的答案。我不认为你的回答能做到这一点。
                  猜你喜欢
                  • 1970-01-01
                  • 2019-03-14
                  • 1970-01-01
                  • 2020-11-13
                  • 1970-01-01
                  • 2013-04-29
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多