Question
I want to match a portion of a string using a regular expression and then access that parenthesized substring:
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 (!!!)
What am I doing wrong?
I've discovered that there was nothing wrong with the regular expression code above: the actual string which I was testing against was this:
"date format_%A"
Reporting that "%A" is undefined seems a very strange behaviour, but it is not directly related to this question, so I've opened a new one, [Why is a matched substring returning "undefined" in JavaScript?](https://stackoverflow.com/questions/432826/why-is-a-matched- substring-returning-undefined-in-javascript).
The issue was that console.log
takes its parameters like a printf
statement, and since the string I was logging ("%A"
) had a special value, it
was trying to find the value of the next parameter.
Answer
Update: 2019-09-10
The old way to iterate over multiple matches was not very intuitive. This lead
to the proposal of the
[String.prototype.matchAll
](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll) method. This
new method is in the ECMAScript 2020
specification. It
gives us a clean API and solves multiple problems. It is in major browsers and
JS engines since [Chrome 73+ / Node 12+](https://v8.dev/features/string-
matchall) and Firefox 67+.
The method returns an iterator and is used as follows:
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)
}
As it returns an iterator, we can say it's lazy, this is useful when handling
particularly large numbers of capturing groups, or very large strings. But if
you need, the result can be easily transformed into an Array by using the
spread syntax or the Array.from
method:
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]);
}
In the meantime, while this proposal gets more wide support, you can use the official shim package.
Also, the internal workings of the method are simple. An equivalent implementation using a generator function would be as follows:
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;
}
}
A copy of the original regexp is created; this is to avoid side-effects due to
the mutation of the lastIndex
property when going through the multple
matches.
Also, we need to ensure the regexp has the global flag to avoid an infinite loop.
I'm also happy to see that even this StackOverflow question was referenced in the [discussions of the proposal](https://github.com/tc39/proposal-string- matchall#previous-discussions).
original answer
You can access capturing groups like this:
var myString = "something format_abc";
var myRegexp = /(?:^|\s)format_(.*?)(?:\s|$)/g;
var myRegexp = new RegExp("(?:^|\\s)format_(.*?)(?:\\s|$)", "g");
var matches = myRegexp.exec(myString);
console.log(matches[1]); // abc
And if there are multiple matches you can iterate over them:
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);
}