【问题标题】:How do you access the matched groups in a JavaScript regular expression?如何访问 JavaScript 正则表达式中的匹配组?
【发布时间】:2021-08-07 09:22:34
【问题描述】:

我想使用 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" is undefined 似乎是一个很奇怪的行为,但它与这个问题没有直接关系,所以我打开了一个新的,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)
    }

    由于它返回一个迭代器,我们可以说它是惰性的,这在处理特别大量的捕获组或非常大的字符串时很有用。但是,如果需要,可以使用传播句法或者 Array.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 属性的突变而产生的副作用。

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

    我也很高兴看到 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】:

    这是您可以用来获取的方法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);

    【讨论】:

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

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

    不是完全一样的东西。 (它适用于--format_foo/,但不适用于format_a_b)但我想展示您的表达方式的替代方案,这很好。当然,match 电话是最重要的。

    【讨论】:

    • 正好相反。 '' 分隔单词。 word= 'w' = [a-zA-Z0-9_] 。 “format_a_b”是一个词。
    • @B.F.老实说,我在 6 年前添加了“在format_a_b 上不起作用”作为事后的想法,我不记得我在那里的意思......:-)我想它的意思是“不起作用仅捕获a”,即。 format_ 之后的第一个字母部分。
    • 我想说的是 (--format_foo/} 不返回“--format_foo/”,因为“-”和“/”不是单词字符。但是 (format_a_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】:

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

    let reg = /#([S]+)/igm; // Get hashtags.
    let string = 'mi alegría es total! ✌?
    #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']
    

    【讨论】:

      【解决方案5】:

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

      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() 来做任何事情了。

      【讨论】:

        【解决方案6】:

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

        使用matchAll,您可以避免使用while循环和exec使用/g...相反,通过使用matchAll,您可以返回一个迭代器,您可以使用更方便的for...ofarray spread,或Array.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 详情。

        【讨论】:

        • 键值对的完美示例。简洁易读,使用起来非常简单。此外,更好的错误处理,传播将返回一个空数组而不是 null,因此不再有“错误,没有 null 的属性“长度””
        【解决方案7】:

        本回答中使用的术语:

        • 匹配表示针对字符串运行 RegEx 模式的结果,如下所示:someString.match(regexPattern)
        • 匹配的模式指示输入字符串的所有匹配部分,它们都位于匹配大批。这些都是输入字符串中模式的所有实例。
        • 配对组指示要捕获的所有组,在 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
        

        我还没有将这些替代方案与之前在其他答案中提到的替代方案进行比较,但我怀疑这种方法的性能和故障安全性不如其他方法。

        【讨论】:

          【解决方案8】:

          您的语法可能不是最好保留的。 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"]
          

          【讨论】:

            【解决方案9】:

            无需调用 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 的第二个匹配项用括号分隔。嵌套括号很棘手,所以要小心!

            【讨论】:

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

            使用 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”,查询字符串:“一些=参数"}

            【讨论】:

              【解决方案11】:

              只有一对括号才实用的单行代码:

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

              【讨论】:

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

              使用您的代码:

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

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

              【讨论】:

                【解决方案13】:

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

                【讨论】:

                  【解决方案14】:

                  你的代码对我有用(Mac 上的 FF3),即使我同意 PhiLo 正则表达式可能应该是:

                  /format_(.*?)/
                  

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

                  【讨论】:

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

                  正如 @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);

                  【讨论】:

                  【解决方案16】:
                  /*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));
                  

                  【讨论】:

                    【解决方案17】:

                    您实际上不需要显式循环来解析多个匹配项——将替换函数作为第二个参数传递,如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 表示第一个匹配组,即正则表达式中括在方括号中的部分,即第一个匹配的0position 是找到匹配组的字符串中的起始索引——在这种情况下未使用。

                    【讨论】:

                      【解决方案18】:

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

                      /([a-z])/
                      

                      在第一组([a-z])匹配的代码中

                      【讨论】:

                        【解决方案19】:

                        一线解决方案:

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

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

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

                        结果:

                        [" format_abc"]
                        

                        【讨论】:

                          【解决方案20】:

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

                          1.匹配第一组RegExp.$1

                          1. 匹配第二组RegExp.$2

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

                          RegExp.$1 RegExp.$2 RegExp.$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)
                           
                          }

                          【讨论】:

                            【解决方案21】:

                            获取所有组出现

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

                            【讨论】:

                              【解决方案22】:

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

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

                              【讨论】:

                                【解决方案23】:

                                我以为你只想抓取所有包含美国广播公司子串和店铺匹配的组/条目,所以我制作了这个脚本:

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

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

                                【讨论】:

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