正则表达式:重复捕获组

13

我需要从一个ASCII文本文件中解析一些表格数据。以下是部分示例:

QSMDRYCELL   11.00   11.10   11.00   11.00    -.90      11     11000     1.212
RECKITTBEN  192.50  209.00  192.50  201.80    5.21      34      2850     5.707
RUPALIINS   150.00  159.00  150.00  156.25    6.29       4        80      .125
SALAMCRST   164.00  164.75  163.00  163.25    -.45      80      8250    13.505
SINGERBD    779.75  779.75  770.00  773.00    -.89       8        95      .735
SONARBAINS   68.00   69.00   67.50   68.00     .74      11      3050     2.077
表格由1列文本和8列浮点数组成。我想通过正则表达式捕获每个列。
我对正则表达式还不太熟悉。这是我想出的有问题的正则表达式模式:
(\S+)\s+(\s+[\d\.\-]+){8}

但是这个模式只捕获第一列和最后一列。RegexBuddy还发出以下警告:

  

您重复了捕获组本身。该组将仅捕获最后一次迭代。在重复的组周围放置一个捕获组,以捕获所有迭代。

我查阅了他们的帮助文件,但我不知道如何解决这个问题。

如何分别捕获每一列?


你使用哪种编程语言?在.NET中很容易。 - Tim Pietzcker
@Tim:是的,我打算用 C# 来编写程序。但目前我正在使用 Python 进行原型设计。 - invarbrass
参见:https://dev59.com/fU7Sa4cB1Zd3GeqP3GWp - polygenelubricants
1
它可以通过组捕获进行检索。请参阅https://dev59.com/GWXWa4cB1Zd3GeqPIwVO - Marko Kukovec
3个回答

17

以下是修改自此示例的C#代码:

string input = "QSMDRYCELL   11.00   11.10   11.00   11.00    -.90      11     11000     1.212";
string pattern = @"^(\S+)\s+(\s+[\d.-]+){8}$";
Match match = Regex.Match(input, pattern, RegexOptions.MultiLine);
if (match.Success) {
   Console.WriteLine("Matched text: {0}", match.Value);
   for (int ctr = 1; ctr < match.Groups.Count; ctr++) {
      Console.WriteLine("   Group {0}:  {1}", ctr, match.Groups[ctr].Value);
      int captureCtr = 0;
      foreach (Capture capture in match.Groups[ctr].Captures) {
         Console.WriteLine("      Capture {0}: {1}", 
                           captureCtr, capture.Value);
         captureCtr++; 
      }
   }
}

输出:

Matched text: QSMDRYCELL   11.00   11.10   11.00   11.00    -.90      11     11000     1.212
...
    Group 2:      1.212
         Capture 0:  11.00
         Capture 1:    11.10
         Capture 2:    11.00
...etc.

谢谢提醒。我正在研究Group.Captures属性。 - invarbrass
3
“Captures”是一个很好的功能,但它在这里似乎有些过头了。为什么不只是根据空格将每一行分割开呢?即使你使用正则表达式来验证行的格式,这样做也会更简单。 - Alan Moore

5
如果你想知道警告是为什么出现的,那是因为你的捕获组匹配了多次(如你所指定的8次),但是捕获变量只能有一个值。它被赋予最后匹配的值。
正如在问题1313332中所述,使用正则表达式通常无法检索这些多个匹配,尽管.NET和Perl 6对此有一些支持。
警告建议你可以在整个集合周围再放置另一个组,就像这样:
(\S+)\s+((\s+[\d\.\-]+){8})

您将能够看到所有列,但当然它们不会被分开。因为通常无法单独捕获它们,更常见的意图是捕获所有内容,并且警告有助于提醒您。


4
很遗憾,您需要重复(...)8次才能分别获取每一列。
^(\S+)\s+([-.\d]+)\s+([-.\d]+)\s+([-.\d]+)\s+([-.\d]+)\s+([-.\d]+)\s+([-.\d]+)\s+([-.\d]+)\s+([-.\d]+)$

如果可能的话,您可以首先将那些数字列作为一个整体进行匹配。

>>> rx1 = re.compile(r'^(\S+)\s+((?:[-.\d]+\s+){7}[-.\d]+)$', re.M)
>>> allres = rx1.findall(theAsciiText)

然后按空格将列分开。
>>> [[p] + q.split() for p, q in allres]

1
Kenny,感谢您的及时回复!我现在正在使用那个模式。但是我想知道是否有更好的解决方案,可以使用重复捕获组。 - invarbrass
@invarbrass:就我所知,没有使用重复捕获组的方法。如果你不试图一次性完成所有操作,正则表达式通常会更好地发挥作用。 - Owen S.
KennyTM:谢谢!你的解决方案可行 - 我之前也在做类似的事情,只不过没有你的方案那么优雅。 - invarbrass
3
.NET在保留中间捕获方面是与众不同的!请参阅Tim的回答和https://dev59.com/fU7Sa4cB1Zd3GeqP3GWp。 - polygenelubricants

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接