跳过捕获组中的字符的正则表达式

50

在正则表达式中,是否可以跳过捕获组中的几个字符?我正在使用.NET正则表达式,但这不重要。

基本上,我需要的是:

[随机文本]AB-123[随机文本]

我需要捕获 'AB123',没有连字符。

我知道AB是2或3个大写字母,而123是2或3个数字,但这不难。对我来说难的部分(至少是对我来说)是跳过连字符。

我猜我可以分别捕获它们然后在代码中连接它们,但我希望有一个更优雅的、仅使用正则表达式的解决方案。

有什么建议吗?


1
在 JavaScript 中,您可以这样做:/(AB)-(123))/.exec("[随机文本]AB-123[随机文本]"); 现在它会返回数组 [1] 和 [2] ^^ - hanshenrik
使用正向前瞻(?=)和正向后顾(?<=)怎么样呢? 基本上这个:(?<=')([A-Z]{2}-[0-9]{3})(?=')应该可以工作。 - It's me ... Alex
很不幸,那个瞬间被捕捉住了 - Hicsy
6个回答

55
简而言之:您不能。即使它包含零宽度断言,匹配始终是连续的,如果您想要到达其后面的字符,那么无论如何都必须匹配下一个字符。

你可以使用正向后顾和正向先行。 - It's me ... Alex
6
没错。但是lookaround并不会“匹配”任何内容。正则引擎在字符串中的位置不会改变。 - Tomalak

21

实际上,没有一种方法可以创建一个表达式,使得匹配的文本与源文本不同。您需要单独删除连字符,可以通过分别匹配第一部分和第二部分,并将两个组合并来完成:

match = Regex.Match( text, "([A-B]{2,3})-([0-9]{2,3})" );
matchedText = string.Format( "{0}{1}", 
    match.Groups.Item(1).Value, 
    match.Groups.Item(2).Value );

或者在匹配过程之外的步骤中去除连字符:

match = Regex.Match( text, "[A-B]{2,3}-[0-9]{2,3}" );
matchedText = match.Value.Replace( "-", "" );

4
还有一个 match.Result("$1$2") - Alan Moore

4
您可以使用嵌套的捕获组,像这样:

((AB)-(123))

第一个捕获组是AB-123,第二个是AB,第三个是123。然后你只需要用空格连接第二个和第三个组即可。

4
您的断言,即没有子组合和连接是不可能的,是正确的。
您也可以像Jeff-Hillman那样,在事后仅剥离错误字符。
重要的是要注意,您“不要为所有事情使用正则表达式”。
正则表达式旨在为非平凡问题提供较简单的解决方案,您不应该对“我们将使用正则表达式”做出反应,并且不应该养成认为可以通过一步正则表达式解决问题的习惯。
如果有可行的简单方法可用,请务必使用它。
另一个替代方法,如果您需要在代码体中返回多个匹配项,则可以查找语言的“回调”基于正则表达式,这允许将任何匹配/找到的组传递给可以进行内联替换的函数调用。 (特别是在执行regexp替换时非常方便)。
不确定它在.Net中如何工作,但在php中,您会执行类似以下内容的操作(不是精确代码)
  function strip_reverse( $a )
  {
     $a = preg_replace("/-/", "", $a );
     return reverse($a);
  }
  $b = preg_replace_callback( "/(AB[-]?cde)/" , 'strip_reverse' , "Hello World AB-cde" ; 

1
常见的误解是正则表达式仅适用于“不太复杂的情况”。实际上,正则表达式非常强大,可以解决非常复杂的问题。但是,正则表达式并不适用于不规则的事物。简单来说:有些事情可以使用正则表达式解决,而有些则不能。 - Tomalak
1
是的,但在使用火器打孔纸张的情况下,正则表达式被过度使用。它能够工作,但存在一些复杂性,在更简单的解决方案中不存在。关键是要知道何时使用正则表达式 ;) - Kent Fredric
知道何时使用哪个工具始终是关键。当有其他方法时(比如说,“indexOf”加上一点数学),我可能会避免在长循环中使用正则表达式。 - Tomalak
针对这种情况,有“学习正则表达式”优化方法,可以制作一个内存树来提高正则表达式匹配的效率。 ;) - Kent Fredric

0

有点晚了,但我想我解决了这个问题。至少有一种方法可以做到。

我使用了正向预查来停在文本中的#号处。我不想要空格或#号,所以我必须想出一个方法来“跳过”它们。因此,当我被迫再次匹配它们时,我将它们倒入一个我不打算使用的垃圾组(即一个位桶),在代码中是.。现在,我的位置指针在#号的后面一个字符位置(我想要跳过空格和#号的位置)。然后我只需匹配到文件名的结尾处的.并忽略文件扩展名。

(?i)English\\(?<Series>[^ ]+) - (?<Title>.+(?= #))(?<garb1>..)(?<Number>[^.]+)(?-i)

这个被使用的文件名是

F:\Downloads\Downloads\500 Comics CCC CBR English\Isukani - Great Girl #01.cbr

我觉得这个返回了两个不同的捕获结果:$Match.Title$Match.Number,而不仅仅是跳过不需要的字符。 - Hicsy
我觉得这个返回了两个不同的捕获结果:$Match.Title$Match.Number,而不仅仅是跳过不需要的字符。 - undefined

0

我对这方面还比较新,但您可以使用竖杠符号 | 作为或运算符。

这在 .NET 中可能适用:

((?<=[A-Z]{2}-)\d\d\d)|([A-Z]{2}(?=-\d\d\d))

这是一个与VIM语法文件相关的有效方法:
\(\([A-Z]\{2}-\)\@<=\d\d\d\)\|\([A-Z]\{2}\(-\d\d\d\)\@=\)

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