如何在JavaScript正则表达式中访问匹配的组?


1368

我想使用正则表达式匹配字符串的一部分,然后访问带括号的子字符串:

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”未定义似乎是一个非常奇怪的行为,但与该问题没有直接关系,因此我打开了一个新的代码,为什么匹配的子字符串在JavaScript中返回“未定义”?


问题在于console.log它的参数就像一条printf语句一样,并且由于我正在记录的字符串("%A")具有特殊值,因此它试图查找下一个参数的值。

Answers:


1673

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

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

如果有多个匹配项,则可以对其进行迭代:

var myString = "something format_abc";
var myRegexp = /(?:^|\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规范中提供。它为我们提供了一个简洁的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]);
}

同时,尽管该提案获得了更广泛的支持,但您可以使用官方的shim软件包

而且,该方法的内部工作很简单。使用生成器功能的等效实现如下所示:

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在进行多次匹配时由于属性突变而产生的副作用。

另外,我们需要确保regexp具有全局标志,以避免无限循环。

我也很高兴看到在提案讨论中甚至提到了这个StackOverflow问题。


114
+1请注意,在第二个示例中,应使用RegExp对象(不仅是“ / myregexp /”),因为它会将lastIndex值保留在对象中。如果不使用Regexp对象,它将无限迭代
ianaz

7
@ianaz:我不相信这是真的吗?http://jsfiddle.net/weEg9/似乎至少可以在Chrome上运行。
spinningarrow

16
为什么上述代替:var match = myString.match(myRegexp); // alert(match[1])
JohnAllen 2013年

29
不需要显式的“新RegExp”,但是除非指定了/ g,否则将发生无限循环
George C

4
另一种不陷入无限循环的方法是显式更新字符串,例如string = string.substring(match.index + match[0].length)
Olga

185

这是您可以用来获取每个比赛的第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);


12
这比其他方法好得多,因为它可以正确显示所有匹配项的迭代,而不是仅显示一个。
罗布·埃文斯

13
mnn是对的。如果不存在'g'标志,这将产生无限循环。使用此功能时要非常小心。
德鲁斯卡

4
我对此进行了改进,使其类似于python的re.findall()。它将所有匹配项分组为一个数组数组。它还解决了全局修饰符无限循环的问题。 jsfiddle.net/ravishi/MbwpV
ravishi

5
@MichaelMikowski现在您已经隐藏了无限循环,但是您的代码将运行缓慢。我认为最好以一种不好的方式来破坏代码,以便在开发中抓住它。放入一些最大的迭代次数是草率的。隐藏问题而不是解决问题的根本原因并不是答案。
wallacer 2014年

4
@MichaelMikowski在您未达到执行限制时并不会明显变慢。当您在时,速度显然要慢得多。我并不是说您的代码行不通,而是在实践中我认为它将带来的弊大于利。尽管在开发环境中工作的人们对某些代码块进行了10,000次不必要的执行,但它们仍会在无负载的情况下正常工作。然后,他们会将其推向生产环境,并想知道为什么他们的应用程序在负载下会关闭。以我的经验,最好是在开发周期的早期以明显的方式破坏事物。
wallacer 2014年

58

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

\b不是一回事。(它适用于--format_foo/,但不适用于format_a_b),但我想展示一种替代您的表情的方法,这很好。当然,match通话是重要的。


2
恰恰相反。'\ b'分隔单词。字='\ w'= [a-zA-Z0-9_]。“ format_a_b”是一个词。
2015年

1
@BF坦白说,我format_a_b在6年前就添加了“不从事” 工作,我想不起来我的意思了... :-)我想它的意思是“不仅仅从事捕获工作a”,即。之后的第一个字母部分format_
PhiLho 2015年

1
我想说\ b(-format_foo /} \ b不会返回“ --format_foo /”,因为“-”和“ /”不是\ word字符。但是\ b(format_a_b)\ b确实会返回“ format_a_b” “。对吗?我用圆括号括住您的文本声明。(没有否决权!)
2015年

31

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

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()了。


26

最后但并非最不重要的一点是,我发现了一段对我来说很好用的代码(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']

1
繁荣!这是这里最优雅的解决方案。我发现此replace方法比Alexz 的全面方法更好,因为这种方法不那么前瞻,而且对于多种结果而言更优雅。塞巴斯蒂安·H.这个好工作
科迪

效果如此之好,以至于肯定会进入我的实用程序中:)
Cody

1
@Cody哈哈,谢谢!
Sebastien H.

19

此答案中使用的术语:

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

描述

要访问匹配的组,在每个匹配的模式中,您都需要一个函数或类似的东西来迭代匹配。正如许多其他答案所示,您可以通过多种方式来执行此操作。大多数其他答案都使用while循环迭代所有匹配的模式,但是我认为我们都知道该方法的潜在危险。有必要与a匹配,new RegExp()而不仅仅是模式本身,后者仅在注释中提及。这是因为该.exec()方法的行为类似于生成器函数 - 每次有匹配项时它都会停止,但是.lastIndex在下一次.exec()调用时会继续从那里继续。

代码示例

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

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

这些性能较低,因为它们基本上实现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

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


19

String#matchAll(请参阅第3阶段草案/ 2018年12月7日提案),简化了比赛对象中所有小组的出入率(请注意,小组0是整个比赛,而其他小组则对应于模式中的捕获小组):

使用matchAllavailable可以避免while循环,exec/g... 可以通过使用matchAll取回一个迭代器,该迭代器可以与更方便的for...ofarray spreadArray.from()构造一起使用

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

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

注意:请参阅浏览器兼容性详细信息。


键值对的完美示例。简洁易读,使用非常简单。同样,更好的错误处理,扩展将返回空数组而不是null,因此不再有“错误,没有null的属性“长度””
Jarrod McGuire

17

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

16

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

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的第二个匹配项用括号括起来。嵌套括号很棘手,所以要当心!


4
没有全局标志,它将返回所有匹配项,只有一个很大的匹配项,因此请当心。
Shadymilkman18年


7

使用您的代码:

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

编辑:Safari 3,如果有关系。


7

使用es2018,您现在可以String.match()使用命名组,使您的正则表达式更加明确地说明其尝试执行的操作。

const url =
  '/programming/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-javascript-regular-expression”,查询字符串:“ some = parameter“}


6

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


3

即使我同意PhiLo的正则表达式可能是,您的代码也对我有效(在Mac上为FF3)

/\bformat_(.*?)\b/

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


1
这是一个用空格分隔的列表,所以我认为\ s会很好。奇怪的是该代码对我不起作用(FF3 Vista)
尼克

1
是的,真的很奇怪。您是否在Firebug控制台中尝试过它?我的意思是从一个空白的页面开始。
PEZ,2009年

2
/*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));

2

您实际上并不需要显式循环来解析多个匹配项,而是将替换函数作为第二个参数传递,如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是找到匹配组的字符串中的起始索引-在这种情况下未使用。


1

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

/([a-z])\1/

在代表第一组([az])的代码\ 1中


1

一线解决方案:

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

因此,您可以使用这种方式(必须使用/ g):

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

结果:

[" format_abc"]


0

我就像我一样,希望正则表达式返回这样的Object:

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


0

只需使用RegExp。$ 1 ... $ n组即可,例如:

1.匹配第一组RegExp。$ 1

  1. 匹配第二组RegExp。$ 2

如果在正则表达式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)
 
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.