如何在JavaScript Regexp中捕获任意数量的组?

e3bfsja2  于 2023-10-22  发布在  Java
关注(0)|答案(6)|浏览(106)

我希望看到下面这行JavaScript:

"foo bar baz".match(/^(\s*\w+)+$/)

返回如下内容:

["foo bar baz", "foo", " bar", " baz"]

而是只返回最后捕获的匹配:

["foo bar baz", " baz"]

有没有一种方法可以让所有捕获的比赛?

v6ylcynt

v6ylcynt1#

当你重复一个捕获组时,在大多数情况下,只保留最后一个捕获;覆盖任何先前的捕获。在某些方面,例如.NET,你可以得到所有的中间捕获,但这不是JavaScript的情况。
也就是说,在JavaScript中,如果你有一个包含 N 个捕获组的模式,那么你只能在每个匹配中捕获 N 个字符串,即使其中一些组是重复的。
所以一般来说,这取决于你需要做什么:

  • 如果可以选择,则使用分隔符进行拆分
  • 不匹配/(pattern)+/,而可能匹配/pattern/g,可能在exec循环中
  • 请注意,这两个并不完全等同,但它可能是一个选项
  • 执行多级匹配:
  • 在一场比赛中捕获重复组
  • 然后运行另一个正则表达式将该匹配分开

参考资料

示例

下面是一个在文本中匹配<some;words;here>的例子,使用exec循环,然后在;上拆分以获得单个单词(see also on ideone.com):

var text = "a;b;<c;d;e;f>;g;h;i;<no no no>;j;k;<xx;yy;zz>";

var r = /<(\w+(;\w+)*)>/g;

var match;
while ((match = r.exec(text)) != null) {
  print(match[1].split(";"));
}
// c,d,e,f
// xx,yy,zz

使用的模式是:

_2__
     /    \
<(\w+(;\w+)*)>
 \__________/
      1

这与<word><word;another><word;another;please>等匹配。组2重复捕获任意数量的单词,但它只能保留最后一次捕获。整个单词列表由组1捕获;这个字符串在split上是split

相关问题

sqougxex

sqougxex2#

不如这样吧"foo bar baz".match(/(\w+)+/g)

ldioqlga

ldioqlga3#

除非你对如何拆分字符串有更复杂的要求,否则你可以拆分它们,然后返回初始字符串:

var data = "foo bar baz";
var pieces = data.split(' ');
pieces.unshift(data);
nbewdwxp

nbewdwxp4#

试着用“g”:

"foo bar baz".match(/\w+/g)
4xy9mtcn

4xy9mtcn5#

您可以使用LAZY评估。所以,不要用 (GREEDY),试试用?(懒惰)
正则表达式:(\s
\w+)?
结果:
匹配1:foo
匹配2:酒吧
匹配3:巴兹

mzillmmw

mzillmmw6#

我已经通读了这个问题和所有的答案,我觉得他们留下了很多模棱两可的地方。所以,为了澄清事实:

1. String.prototype.match

我希望看到下面这行JavaScript:
"foo bar baz".match(/^(\s*\w+)+$/)返回如下内容:["foo bar baz", "foo", " bar", " baz"]
为了获得所需的输出,需要显式地捕获所有三个组(特别是因为您使用^...$将模式括起来,表明您希望整个字符串在一个匹配中通过测试)。String.prototype.match将返回该格式([FULL_MATCH, CAP1, CAP2, CAP3, ... CAPn])-它是“exec”响应(之所以这么叫是因为String.prototype.match是RegExp.prototype.exec的一个实现)-当表达式匹配目标字符串 * 而没有设置/g匹配标志 * 时。考虑:

// EX.1
"foo bar baz".match(/^(\w+) (\w+) (\w+)$/)

...这就产生了...

// RES.1
['foo bar baz', 'foo', 'bar', 'baz']

**为什么?**因为你声明性地捕获了3个不同的 * 组 *。比较一下:

// EX.2 (Note the /g lobal flag and absence of the `^` and `$`)
"foo bar baz".match(/(\w+)/g)

...这就产生了...

// RES.2
['foo', ' bar', ' baz']

这是因为.match方法具有双重功能。在一个非/g捕获匹配中,它将返回match作为返回数组中的第一个元素,并返回each capture group作为同一数组中的一个附加节点。这是因为match()只是RegExp.prototype.exec的语法糖实现。
因此,Res.2中结果的格式是/g匹配标志的结果,它向编译器表明,“每次找到此模式的匹配时,返回它,并从该匹配的末尾继续,看看是否还有更多”。由于RegExp.prototype.exec返回第一次出现,但您提供了“/goahead and continue until we run out”标志,因此您会看到一个集合多次包含第一次出现的内容。

2. String.prototype.matchAll

如果你确实需要完整的“exec match”语法,并希望捕获所有 n 个匹配,并且你愿意使用/g匹配标志(你必须这样做),你需要matchAll。它返回一个RegExpStringIterator,这是一种特殊类型的集合,需要使用/g类型。让我们在EX.2中使用matchAll重新运行相同的查询:

// EX.3a
"foo bar baz".matchAll(/(\w+)/g)
// RES.3a
RegExpStringIterator

因为它返回了一个迭代器,为了真正获得数据,我们将使用一个扩展运算符(...<ITERATOR>)。因为我们需要一些东西来将它扩展到其中,所以我们将整个数组封装在Array构造函数简写([...<ITERATOR>])中。

// EX.3b
[..."foo bar baz".matchAll(/(\w+)/g)]
// RES.3b
[
   ['foo', 'foo'], ['bar', 'bar'], ['baz', 'baz']
]

正如您所看到的,有3个匹配,每个都是[<MATCH>, <CAPGROUP>]数组。
实际上,这一切都归结为match返回的是匹配,而不是捕获。如果一个匹配碰巧是一个捕获(或包含多个),当你把它们放在括号里时,把它们分开是很有帮助的。实际上,"foo bar baz".match(/\w+/g)(注意/g的存在和捕获括号的缺失)仍然会产生['foo', 'bar', 'baz']。它找到了3个匹配项,您没有指定想要的组,因此它执行了查找它们的方式。
我相信,所有这些在很大程度上都是由于对RegExp如何返回结果的巨大误解。即

3. GROUP不是MATCH

这里的部分歧义是术语。同一个match中可以 * 包含多个捕获。一个组中不能有多个匹配项。想想维恩图:

这可能更容易使用语法可视化。假设我们使用正则表达式:

EX.4
"foo bar baz".match(/(?<GROUP1>\w+) (?<GROUP2>\w+) (?<GROUP3>\w+)/).groups

与Ex.1相比,我唯一的变化是为每个捕获组(由括号中包含的模式定义的匹配部分-(...))分配了一个名称(?<NAME>语法)。因为它们是命名的,所以out响应数组有一个额外的属性:.groups:

// RES.4
[
    "foo bar baz",
    "foo",
    "bar",
    "baz"
],
    groups: {
        "GROUP1": "foo",
        "GROUP2": "bar",
        "GROUP3": "baz"
    }

因为我们显式地命名了所有三个捕获组,所以我们可以看到我们有一个MATCH(包含完整匹配和所有3个捕获组内容的数组),沿着我们的命名捕获对象。
所以,最后,让我们用额外的信息来尝试你的第一次尝试:

// EX.5
"foo bar baz".match(/^(?<GROUPn>\s*\w+)+$/)
// RES.5
[
    "foo bar baz",
    " baz"
],
    groups: {
        "GROUPn": "baz"
    }

等等,怎么回事?
因为您只声明性地指定了一个捕获组(我将其标记为“GROUPn”),所以您只提供了一个“槽”供捕获进入。
简而言之:这并不是说你的表情没有捕捉到所有三个元素。那就是“slot”--当返回值在响应中到达时用来存储返回值的变量 * 被覆盖了两次 。所有这些都是在说: 不能在一个捕获组中存储多个捕获 *(至少在ECMA的RegExp引擎中是这样)。
你当然可以存储多个 * 匹配 *(如果这就是你所需要的,嘿,太好了),但是有时候你不能在应用结果集之前对其进行验证。
最后举一个例子:

// EX.6a
console.log("foo bar baz".replace(/(...) (...) (...)/, "The first word is '$1'.\nThe second word is '$2'.\nThe third word is '$3'."))
// RES.6a
"The first word is 'foo'.
The second word is 'bar'.
The third word is 'baz'."

在这个例子中,我们需要在replacement match中的所有三个捕获,所以我们可以在同一个替换操作中直接引用每个捕获。如果我们用你的表情来试一下:

// EX.6b
console.log("foo bar baz".replace(/^(\s*\w+)+$/, "The first word is '$1'.\nThe second word is '$2'.\nThe third word is '$3'."))

......我们最终会得到令人困惑的不准确的......

// RES.6b
The first word is ' baz'.
The second word is '$2'.
The third word is '$3'.

希望这对将来的人有所帮助。

相关问题