【问题标题】:How do you access the matched groups in a JavaScript regular expression?如何访问 JavaScript 正则表达式中匹配的组?
【发布时间】:2020-03-24 10:30:17
【问题描述】:

我想使用regular expression 匹配字符串的一部分,然后访问带括号的子字符串:

    var myString = "something format_abc"; // I want "abc"

    var arr = /(?:^|\s)format_(.*?)(?:\s|$)/.exec(myString);

    console.log(arr);     // Prints: [" format_abc", "abc"] .. so far so good.
    console.log(arr[1]);  // Prints: undefined  (???)
    console.log(arr[0]);  // Prints: format_undefined (!!!)

我做错了什么?


我发现上面的正则表达式代码没有任何问题:我正在测试的实际字符串是这样的:

"date format_%A"

报告“%A”未定义似乎是一种很奇怪的行为,但它与这个问题没有直接关系,所以我开了一个新问题,Why is a matched substring returning "undefined" in JavaScript?


问题是console.logprintf 语句一样接受它的参数,并且由于我正在记录的字符串("%A")有一个特殊值,它试图找到下一个参数的值。

【问题讨论】:

    标签: javascript regex


    【解决方案1】:

    您可以像这样访问捕获组:

    var myString = "something format_abc";
    var myRegexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
    var myRegexp = new RegExp("(?:^|\s)format_(.*?)(?:\s|$)", "g");
    var match = myRegexp.exec(myString);
    console.log(match[1]); // abc

    如果有多个匹配项,您可以遍历它们:

    var myString = "something format_abc";
    var myRegexp = new RegExp("(?:^|\s)format_(.*?)(?:\s|$)", "g");
    match = myRegexp.exec(myString);
    while (match != null) {
      // matched text: match[0]
      // match start: match.index
      // capturing group n: match[n]
      console.log(match[0])
      match = myRegexp.exec(myString);
    }

    编辑:2019-09-10

    正如您所见,迭代多个匹配项的方式不是很直观。这导致了String.prototype.matchAll 方法的提议。这种新方法预计将在ECMAScript 2020 specification 中发布。它为我们提供了一个干净的 API 并解决了多个问题。它已开始登陆主流浏览器和 JS 引擎,如 Chrome 73+ / Node 12+ 和 Firefox 67+。

    该方法返回一个迭代器,用法如下:

    const string = "something format_abc";
    const regexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
    const matches = string.matchAll(regexp);
        
    for (const match of matches) {
      console.log(match);
      console.log(match.index)
    }

    由于它返回一个迭代器,我们可以说它是惰性的,这在处理特别大量的捕获组或非常大的字符串时很有用。但如果您需要,可以使用 spread syntaxArray.from 方法轻松地将结果转换为数组:

    function getFirstGroup(regexp, str) {
      const array = [...str.matchAll(regexp)];
      return array.map(m => m[1]);
    }
    
    // or:
    function getFirstGroup(regexp, str) {
      return Array.from(str.matchAll(regexp), m => m[1]);
    }
    

    与此同时,虽然此提案得到了更广泛的支持,但您可以使用official shim package

    此外,该方法的内部工作原理也很简单。使用生成器函数的等效实现如下:

    function* matchAll(str, regexp) {
      const flags = regexp.global ? regexp.flags : regexp.flags + "g";
      const re = new RegExp(regexp, flags);
      let match;
      while (match = re.exec(str)) {
        yield match;
      }
    }
    

    创建原始正则表达式的副本;这是为了避免在进行多个匹配时由于lastIndex 属性的突变而产生副作用。

    此外,我们需要确保正则表达式具有 global 标志以避免无限循环。

    我也很高兴看到 discussions of the proposal 中甚至引用了这个 StackOverflow 问题。

    【讨论】:

    • +1 请注意,在第二个示例中,您应该使用 RegExp 对象(不仅是“/myregexp/”),因为它保留了对象中的 lastIndex 值。如果不使用 Regexp 对象,它将无限迭代
    • @ianaz:我不相信这是真的? http://jsfiddle.net/weEg9/ 似乎至少可以在 Chrome 上运行。
    • 为什么用上面的代替:var match = myString.match(myRegexp); // alert(match[1])?
    • 不需要显式的“new RegExp”,但是除非指定/g,否则将发生无限循环
    • 重要的是要注意第 0 个索引是整个匹配项。所以const [_, group1, group2] = myRegex.exec(myStr); 是我的模式。
    【解决方案2】:

    使用您的代码:

    console.log(arr[1]);  // prints: abc
    console.log(arr[0]);  // prints:  format_abc
    

    编辑:Safari 3,如果重要的话。

    【讨论】:

      【解决方案3】:

      var myString = "something format_abc";
      var arr = myString.match(/\bformat_(.*?)\b/);
      console.log(arr[0] + " " + arr[1]);

      \b 不完全相同。 (它适用于--format_foo/,但不适用于format_a_b)但我想展示一个替代你的表达方式,这很好。当然,match 调用很重要。

      【讨论】:

      • 完全相反。 '\b' 分隔单词。 word= '\w' = [a-zA-Z0-9_] 。 “format_a_b”是一个词。
      • @BF老实说,我在 6 年前添加了“不适用于 format_a_b”作为事后的想法,我不记得我在那里的意思...... :-) 我想这意味着“仅捕获a 不起作用”,即。 format_ 之后的第一个字母部分。
      • 我想说的是 \b(--format_foo/}\b 不返回 "--format_foo/" 因为 "-" 和 "/" 不是 \word 字符。但是 \b( format_a_b)\b 确实返回“format_a_b”。对吗?我指的是圆括号中的文本声明。(没有反对!)
      • 请注意,g 标志在这里很重要。如果将g 标志添加到模式中,您将获得一个匹配数组,而忽略捕获组。 "a b c d".match(/(\w) (\w)/g); => ["a b", "c d"]"a b c d".match(/(\w) (\w)/); => ["a b", "a", "b", index: 0, input: "a b c d", groups: undefined]
      【解决方案4】:

      即使我同意PhiLo 的正则表达式可能应该是,您的代码也适用于我(Mac 上的 FF3):

      /\bformat_(.*?)\b/
      

      (但是,当然,我不确定,因为我不知道正则表达式的上下文。)

      【讨论】:

      • 这是一个以空格分隔的列表,所以我认为 \s 会很好。奇怪的是该代码对我不起作用(FF3 Vista)
      • 是的,真的很奇怪。您是否在 Firebug 控制台中单独尝试过它?我的意思是从一个原本空白的页面。
      【解决方案5】:

      您的语法可能不是最好的保留。 FF/Gecko 将 RegExp 定义为 Function 的扩展。
      (FF2 到了typeof(/pattern/) == 'function'

      这似乎是 FF 特有的——IE、Opera 和 Chrome 都会为它抛出异常。

      改为使用其他人之前提到的任一方法:RegExp#execString#match
      它们提供相同的结果:

      var regex = /(?:^|\s)format_(.*?)(?:\s|$)/;
      var input = "something format_abc";
      
      regex(input);        //=> [" format_abc", "abc"]
      regex.exec(input);   //=> [" format_abc", "abc"]
      input.match(regex);  //=> [" format_abc", "abc"]
      

      【讨论】:

        【解决方案6】:

        您可以使用以下方法获取每场比赛的第 n 个捕获组:

        function getMatches(string, regex, index) {
          index || (index = 1); // default to the first capturing group
          var matches = [];
          var match;
          while (match = regex.exec(string)) {
            matches.push(match[index]);
          }
          return matches;
        }
        
        
        // Example :
        var myString = 'something format_abc something format_def something format_ghi';
        var myRegEx = /(?:^|\s)format_(.*?)(?:\s|$)/g;
        
        // Get an array containing the first capturing group for every match
        var matches = getMatches(myString, myRegEx, 1);
        
        // Log results
        document.write(matches.length + ' matches found: ' + JSON.stringify(matches))
        console.log(matches);

        【讨论】:

        • 这是一个远远优于其他答案的答案,因为它正确地显示了所有匹配项的迭代,而不是只得到一个。
        【解决方案7】:

        仅当您有一对括号时才实用的单行:

        while ( ( match = myRegex.exec( myStr ) ) && matches.push( match[1] ) ) {};
        

        【讨论】:

        • 为什么不while (match = myRegex.exec(myStr)) matches.push(match[1])
        【解决方案8】:

        关于上面的多匹配括号示例,我在没有得到我想要的结果后在这里寻找答案:

        var matches = mystring.match(/(?:neededToMatchButNotWantedInResult)(matchWanted)/igm);
        

        在查看了上面带有 while 和 .push() 的稍微复杂的函数调用之后,我突然意识到可以用 mystring.replace() 非常优雅地解决这个问题(替换不是重点,而是'甚至完成了,第二个参数的 CLEAN 内置递归函数调用选项是!):

        var yourstring = 'something format_abc something format_def something format_ghi';
        
        var matches = [];
        yourstring.replace(/format_([^\s]+)/igm, function(m, p1){ matches.push(p1); } );
        

        在这之后,我想我再也不会用 .match() 来做任何事情了。

        【讨论】:

          【解决方案9】:
          /*Regex function for extracting object from "window.location.search" string.
           */
          
          var search = "?a=3&b=4&c=7"; // Example search string
          
          var getSearchObj = function (searchString) {
          
              var match, key, value, obj = {};
              var pattern = /(\w+)=(\w+)/g;
              var search = searchString.substr(1); // Remove '?'
          
              while (match = pattern.exec(search)) {
                  obj[match[0].split('=')[0]] = match[0].split('=')[1];
              }
          
              return obj;
          
          };
          
          console.log(getSearchObj(search));
          

          【讨论】:

            【解决方案10】:

            function getMatches(string, regex, index) {
              index || (index = 1); // default to the first capturing group
              var matches = [];
              var match;
              while (match = regex.exec(string)) {
                matches.push(match[index]);
              }
              return matches;
            }
            
            
            // Example :
            var myString = 'Rs.200 is Debited to A/c ...2031 on 02-12-14 20:05:49 (Clear Bal Rs.66248.77) AT ATM. TollFree 1800223344 18001024455 (6am-10pm)';
            var myRegEx = /clear bal.+?(\d+\.?\d{2})/gi;
            
            // Get an array containing the first capturing group for every match
            var matches = getMatches(myString, myRegEx, 1);
            
            // Log results
            document.write(matches.length + ' matches found: ' + JSON.stringify(matches))
            console.log(matches);

            function getMatches(string, regex, index) {
              index || (index = 1); // default to the first capturing group
              var matches = [];
              var match;
              while (match = regex.exec(string)) {
                matches.push(match[index]);
              }
              return matches;
            }
            
            
            // Example :
            var myString = 'something format_abc something format_def something format_ghi';
            var myRegEx = /(?:^|\s)format_(.*?)(?:\s|$)/g;
            
            // Get an array containing the first capturing group for every match
            var matches = getMatches(myString, myRegEx, 1);
            
            // Log results
            document.write(matches.length + ' matches found: ' + JSON.stringify(matches))
            console.log(matches);

            【讨论】:

              【解决方案11】:

              最后但同样重要的是,我发现了一行代码对我来说很好用(JS ES6):

              let reg = /#([\S]+)/igm; // Get hashtags.
              let string = 'mi alegría es total! ✌?\n#fiestasdefindeaño #PadreHijo #buenosmomentos #france #paris';
              
              let matches = (string.match(reg) || []).map(e => e.replace(reg, '$1'));
              console.log(matches);

              这将返回:

              ['fiestasdefindeaño', 'PadreHijo', 'buenosmomentos', 'france', 'paris']
              

              【讨论】:

                【解决方案12】:

                无需调用exec 方法!您可以直接在字符串上使用“匹配”方法。只是不要忘记括号。

                var str = "This is cool";
                var matches = str.match(/(This is)( cool)$/);
                console.log( JSON.stringify(matches) ); // will print ["This is cool","This is"," cool"] or something like that...
                

                位置 0 有一个包含所有结果的字符串。位置 1 的第一个匹配项用括号表示,位置 2 的第二个匹配项用括号表示。嵌套括号很棘手,所以要小心!

                【讨论】:

                • 如果没有全局标志,这将返回所有匹配项,使用它,你只会得到一个大的,所以要小心。
                【解决方案13】:

                此答案中使用的术语:

                • Match 表示针对字符串运行 RegEx 模式的结果,如下所示:someString.match(regexPattern)
                • 匹配模式表示输入字符串的所有匹配部分,它们都位于 ma​​tch 数组中。这些都是输入字符串中模式的所有实例。
                • 匹配组表示要捕获的所有组,在 RegEx 模式中定义。 (括号内的模式,如下所示:/format_(.*?)/g,其中(.*?) 将是一个匹配组。)它们位于匹配模式中。

                说明

                要访问匹配的组,在每个匹配的模式中,您需要一个函数或类似的东西来迭代匹配。正如许多其他答案所示,有多种方法可以做到这一点。大多数其他答案使用 while 循环来迭代所有 匹配的模式,但我认为我们都知道这种方法的潜在危险。有必要匹配 new RegExp() 而不仅仅是模式本身,它只在评论中提到。这是因为 .exec() 方法的行为类似于 生成器函数it stops every time there is a match,但会保留其 .lastIndex 以在下一次 .exec() 调用时从那里继续。

                代码示例

                下面是函数searchString 的示例,它返回所有匹配模式Array,其中每个match 是一个Array,其中包含所有匹配组。我没有使用 while 循环,而是提供了使用 Array.prototype.map() 函数的示例以及更高效的方式 - 使用普通的 for-loop。

                简洁的版本(更少的代码,更多的语法糖)

                这些性能较差,因为它们基本上实现了forEach-loop,而不是更快的for-loop。

                // Concise ES6/ES2015 syntax
                const searchString = 
                    (string, pattern) => 
                        string
                        .match(new RegExp(pattern.source, pattern.flags))
                        .map(match => 
                            new RegExp(pattern.source, pattern.flags)
                            .exec(match));
                
                // Or if you will, with ES5 syntax
                function searchString(string, pattern) {
                    return string
                        .match(new RegExp(pattern.source, pattern.flags))
                        .map(match =>
                            new RegExp(pattern.source, pattern.flags)
                            .exec(match));
                }
                
                let string = "something format_abc",
                    pattern = /(?:^|\s)format_(.*?)(?:\s|$)/;
                
                let result = searchString(string, pattern);
                // [[" format_abc", "abc"], null]
                // The trailing `null` disappears if you add the `global` flag
                

                高性能版本(更多代码,更少语法糖)

                // Performant ES6/ES2015 syntax
                const searchString = (string, pattern) => {
                    let result = [];
                
                    const matches = string.match(new RegExp(pattern.source, pattern.flags));
                
                    for (let i = 0; i < matches.length; i++) {
                        result.push(new RegExp(pattern.source, pattern.flags).exec(matches[i]));
                    }
                
                    return result;
                };
                
                // Same thing, but with ES5 syntax
                function searchString(string, pattern) {
                    var result = [];
                
                    var matches = string.match(new RegExp(pattern.source, pattern.flags));
                
                    for (var i = 0; i < matches.length; i++) {
                        result.push(new RegExp(pattern.source, pattern.flags).exec(matches[i]));
                    }
                
                    return result;
                }
                
                let string = "something format_abc",
                    pattern = /(?:^|\s)format_(.*?)(?:\s|$)/;
                
                let result = searchString(string, pattern);
                // [[" format_abc", "abc"], null]
                // The trailing `null` disappears if you add the `global` flag
                

                我尚未将这些替代方案与之前在其他答案中提到的替代方案进行比较,但我怀疑这种方法的性能和故障安全性都低于其他方法。

                【讨论】:

                  【解决方案14】:

                  使用 es2018,您现在可以 String.match() 使用命名组,让您的正则表达式更明确地表明它试图做什么。

                  const url =
                    'https://stackoverflow.com/questions/432493/how-do-you-access-the-matched-groups-in-a-javascript-regular-expression?some=parameter';
                  const regex = /(?<protocol>https?):\/\/(?<hostname>[\w-\.]*)\/(?<pathname>[\w-\./]+)\??(?<querystring>.*?)?$/;
                  const { groups: segments } = url.match(regex);
                  console.log(segments);
                  

                  你会得到类似的东西

                  {协议:“https”,主机名:“stackoverflow.com”,路径名:“questions/432493/how-do-you-access-the-matched-groups-in-a-javascript-regular-expression”,查询字符串:“一些=参数”}

                  【讨论】:

                    【解决方案15】:

                    您实际上并不需要显式循环来解析多个匹配项 - 将替换函数作为第二个参数传递,如:String.prototype.replace(regex, func):

                    var str = "Our chief weapon is {1}, {0} and {2}!"; 
                    var params= ['surprise', 'fear', 'ruthless efficiency'];
                    var patt = /{([^}]+)}/g;
                    
                    str=str.replace(patt, function(m0, m1, position){return params[parseInt(m1)];});
                    
                    document.write(str);

                    m0 参数表示完全匹配的子字符串{0}{1} 等。m1 表示第一个匹配组,即正则表达式中括号括起来的部分,即0 表示第一个匹配. position 是找到匹配组的字符串中的起始索引 - 在这种情况下未使用。

                    【讨论】:

                      【解决方案16】:

                      String#matchAll(参见Stage 3 Draft / December 7, 2018 proposal),简化了对匹配对象中所有组的访问(请注意,第 0 组是整个匹配项,而其他组对应于模式中的捕获组):

                      如果matchAll 可用,您可以避免while 循环和exec/g... 相反,通过使用matchAll,您可以返回一个迭代器,您可以使用更方便的@ 987654323@、array spreadArray.from() 构造

                      此方法产生的输出与 C# 中的 Regex.Matches、Python 中的 re.finditer、PHP 中的 preg_match_all 类似。

                      查看 JS 演示(在 Google Chrome 73.0.3683.67(官方构建),测试版(64 位)中测试):

                      var myString = "key1:value1, key2-value2!!@key3=value3";
                      var matches = myString.matchAll(/(\w+)[:=-](\w+)/g);
                      console.log([...matches]); // All match with capturing group values

                      console.log([...matches]) 节目

                      您还可以使用获取匹配值或特定组值

                      let matchData = "key1:value1, key2-value2!!@key3=value3".matchAll(/(\w+)[:=-](\w+)/g)
                      var matches = [...matchData]; // Note matchAll result is not re-iterable
                      
                      console.log(Array.from(matches, m => m[0])); // All match (Group 0) values
                      // => [ "key1:value1", "key2-value2", "key3=value3" ]
                      console.log(Array.from(matches, m => m[1])); // All match (Group 1) values
                      // => [ "key1", "key2", "key3" ]

                      注意:请参阅browser compatibility 详细信息。

                      【讨论】:

                      • 键值对的完美示例。简洁易读,使用非常简单。此外,更好的错误处理,spread 将返回一个空数组而不是 null,因此不再有 'error, no property "length" of null'
                      【解决方案17】:

                      我们可以在正则表达式中使用反斜杠后跟匹配组的编号来访问匹配组:

                      /([a-z])\1/
                      

                      第一组([a-z])匹配的代码中\1表示

                      【讨论】:

                        【解决方案18】:

                        获取所有组的出现

                        let m=[], s = "something format_abc  format_def  format_ghi";
                        
                        s.replace(/(?:^|\s)format_(.*?)(?:\s|$)/g, (x,y)=> m.push(y));
                        
                        console.log(m);

                        【讨论】:

                          【解决方案19】:

                          我和我一样,希望正则表达式返回一个像这样的对象:

                          {
                              match: '...',
                              matchAtIndex: 0,
                              capturedGroups: [ '...', '...' ]
                          }
                          

                          然后从下面截取函数

                          /**
                           * @param {string | number} input
                           *          The input string to match
                           * @param {regex | string}  expression
                           *          Regular expression 
                           * @param {string} flags
                           *          Optional Flags
                           * 
                           * @returns {array}
                           * [{
                              match: '...',
                              matchAtIndex: 0,
                              capturedGroups: [ '...', '...' ]
                            }]     
                           */
                          function regexMatch(input, expression, flags = "g") {
                            let regex = expression instanceof RegExp ? expression : new RegExp(expression, flags)
                            let matches = input.matchAll(regex)
                            matches = [...matches]
                            return matches.map(item => {
                              return {
                                match: item[0],
                                matchAtIndex: item.index,
                                capturedGroups: item.length > 1 ? item.slice(1) : undefined
                              }
                            })
                          }
                          
                          let input = "key1:value1, key2:value2 "
                          let regex = /(\w+):(\w+)/g
                          
                          let matches = regexMatch(input, regex)
                          
                          console.log(matches)

                          【讨论】:

                            【解决方案20】:

                            一线解决方案:

                            const matches = (text,regex) => [...text.matchAll(regex)].map(([match])=>match)
                            

                            所以可以这样使用(必须使用/g):

                            matches("something format_abc", /(?:^|\s)format_(.*?)(?:\s|$)/g)
                            

                            结果:

                            [" format_abc"]
                            

                            【讨论】:

                              【解决方案21】:

                              只需使用 RegExp.$1...$n 组 例如:

                              1.匹配第一组RegExp.$1

                              1. 匹配第二组 RegExp.$2

                              如果你在 regex likey 中使用 3 组(注意在 string.match(regex) 之后使用)

                              正则表达式.$1 正则表达式.$2 正则表达式.$3

                               var str = "The rain in ${india} stays safe"; 
                                var res = str.match(/\${(.*?)\}/ig);
                                //i used only one group in above example so RegExp.$1
                              console.log(RegExp.$1)

                              //easiest way is use RegExp.$1 1st group in regex and 2nd grounp like
                               //RegExp.$2 if exist use after match
                              
                              var regex=/\${(.*?)\}/ig;
                              var str = "The rain in ${SPAIN} stays ${mainly} in the plain"; 
                                var res = str.match(regex);
                              for (const match of res) {
                                var res = match.match(regex);
                                console.log(match);
                                console.log(RegExp.$1)
                               
                              }

                              【讨论】:

                                【解决方案22】:

                                正如 @cms 在 ECMAScript (ECMA-262) 中所说,您可以使用 matchAll。它返回一个迭代器,并将其放入[... ](扩展运算符)中,它转换为一个数组。(这个正则表达式提取文件名的url)

                                let text = `<a href="http://myhost.com/myfile_01.mp4">File1</a> <a href="http://myhost.com/myfile_02.mp4">File2</a>`;
                                
                                let fileUrls = [...text.matchAll(/href="(http\:\/\/[^"]+\.\w{3})\"/g)].map(r => r[1]);
                                
                                console.log(fileUrls);

                                【讨论】:

                                【解决方案23】:

                                我以为你只想获取所有包含 abc 子字符串的单词并 store ma​​tched 组/条目,所以我做了这个脚本:

                                s = 'something format_abc another word abc abc_somestring'
                                    console.log(s.match(/\b\w*abc\w*\b/igm));
                                • \b - 单词边界
                                • \w* - 0+ 个单词字符
                                • abc - 完全匹配
                                • \w* - 0+ 个单词字符
                                • \b - 单词边界

                                参考:Regex: Match all the words that contains some word https://javascript.info/regexp-introduction

                                【讨论】:

                                  猜你喜欢
                                  • 2021-08-07
                                  • 2014-07-12
                                  • 2011-11-29
                                  • 2012-09-22
                                  • 1970-01-01
                                  相关资源
                                  最近更新 更多