如何使用带有线锚的 C# 正则表达式 Lookbehind

Posted

技术标签:

【中文标题】如何使用带有线锚的 C# 正则表达式 Lookbehind【英文标题】:How to use C# Regular Expression Lookbehind with line anchors 【发布时间】:2018-08-21 02:08:45 【问题描述】:

当同时使用行开始和行结束锚时,我在 C# 的正则表达式匹配中遇到后视断言问题。 在下面的示例中,Regex B 的行为完全符合我的预期(并且如此处所述:https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference

我最初对 RegEx A 不匹配第 1 行感到惊讶。现在我想我明白为什么 RegEx A 不匹配第 1 行了。[因为断言是零宽度 - 表达式基本上是 ^\d2$,这显然与 4 位数年份不匹配 - 这就是它匹配第 6 行和第 7 行的原因。

我知道我可以像这样重写肯定断言(RegEx A):^19\d2$。

但我的最终目标是像 RegEx C 这样的正则表达式 - 使用否定断言来查找所有不以给定前缀开头的字符串。也就是说,我正在尝试创建一个带有否定断言的表达式,它对第 3 行和第 4 行而不是第 5-7 行返回 true。

RegEx D 是 C# 文档中类似的否定断言示例,但不使用开始/结束锚点,第 3 行和第 4 行以及第 5-7 行也是如此。

考虑到这一点,我怎样才能使否定断言(如 RegEx C)与 line-begin/-end 锚点一起工作,以便在验证输入是单行时它的功能类似于 RegEx D 中的示例?

我想知道这是否根本不可能使用断言。这意味着替代方案是表达所有评估为否定例外的积极案例(类似于在 Regex E 中使用 19),但是当目标是排除一个特定的单个(可能是复杂的)案例。

谢谢!

示例程序:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace RegExTest

    class Program
    
        static void Main(string[] args)
        
            string[] reList = new string[]
            
                @"^(?<=19)\d2$",   // RegEx A
                @"(?<=19)\d2",     // RegEx B
                @"^(?<!19)\d2$",   // RegEx C
                @"(?<!19)\d2\b",   // RegEx D
                @"^19\d2$",        // RegEx E
            ;

            string[] tests = new string[]
            
                "1999",                     // Line 1
                "1851 1999 1950 1905 2003", // Line 2
                "1895",                     // Line 3
                "2095",                     // Line 4
                "195",                      // Line 5
                "18",                       // Line 6
                "19",                       // Line 7
            ;
            foreach (var r in reList)
            
                var re = new Regex(r);
                Console.WriteLine("");
                Console.WriteLine($"r");
                Console.WriteLine("==========================");
                foreach (var s in tests)
                
                    Console.WriteLine($"s=re.IsMatch(s)");
                    if (re.IsMatch(s))
                    
                        foreach (Match m in re.Matches(s))
                        
                            Console.WriteLine($"Match @ (m.Index, m.Length)");
                        
                    
                
            
        
    

输出:

^(?<=19)\d2$
==========================
1999=False
1851 1999 1950 1905 2003=False
1895=False
2095=False
195=False
18=False
19=False

(?<=19)\d2
==========================
1999=True
Match @ (2, 2)
1851 1999 1950 1905 2003=True
Match @ (7, 2)
Match @ (12, 2)
Match @ (17, 2)
1895=False
2095=False
195=False
18=False
19=False

^(?<!19)\d2$
==========================
1999=False
1851 1999 1950 1905 2003=False
1895=False
2095=False
195=False
18=True
Match @ (0, 2)
19=True
Match @ (0, 2)

(?<!19)\d2\b
==========================
1999=False
1851 1999 1950 1905 2003=True
Match @ (2, 2)
Match @ (22, 2)
1895=True
Match @ (2, 2)
2095=True
Match @ (2, 2)
195=True
Match @ (1, 2)
18=True
Match @ (0, 2)
19=True
Match @ (0, 2)

^19\d2$
==========================
1999=True
Match @ (0, 4)
1851 1999 1950 1905 2003=False
1895=False
2095=False
195=False
18=False
19=False

【问题讨论】:

我不清楚你想要什么输出。正则表达式 D 没有行开始/结束标记,所以你是什么意思让 C 像 D 一样工作? C有什么问题? 这个问题确实需要澄清一下,但是您的意思是使用负前瞻功能吗?像:^(?!19)\d4$ ? @NetMage - 第 3 行和第 4 行为真,第 5-7 行为假。 ^(?&lt;!19)\d4$ 呢? @Brad 你试过我的建议了吗?断言它不是以 19 开头并且由 4 位数字组成,其中包含一个完整的行……是一个否定断言。听起来你需要什么,不是吗?你的意思是只专门使用lookbehind吗?这在我的书中会有点倒退...... 【参考方案1】:

您将环视断言与正常模式的默认行为混淆了。环视断言这意味着它不消耗字符。

它寻找一个条件,如果满足则将光标带回它开始的位置,否则它会使引擎回溯或立即失败。

正则表达式 A ^(?&lt;!19)\d2$ 不应匹配字符串 1 1999,因为引擎以这种方式工作:

    ^ 断言字符串的开头(我们在位置 0) (?&lt;!19) 检查前面的字符是否不是19(确定在 位置 0 我们没有前面的字符,所以这满足) \d2消耗两位数(我们在位置2) $ 断言字符串结尾(实际上我们还有 2 个字符要到达 字符串结束,因此引擎立即失败)

所以你必须这样做^\d2(?&lt;!19)\d2$^(?!19)\d4$第二个更合适。

【讨论】:

对断言的良好解释,因此被赞成,但没有回答完整的问题。 我在答案中添加了最后一行,它指向了正确的解决方案。 @布拉德 完美,谢谢。如果(例如)我只想捕获最后 2 位数字,则提供这两种替代方案会使行为一清二楚,并提供后视替代方案。 对于负前瞻,您也可以通过分隔它们来捕获最后两位数:^(?!19)\d2(\d2)$

以上是关于如何使用带有线锚的 C# 正则表达式 Lookbehind的主要内容,如果未能解决你的问题,请参考以下文章

需要一些带有(正则表达式)的 C# 代码,这将改变 url 域

用于自定义电子邮件和域的 C# 正则表达式

C#:更改正则表达式的 NumberDecimalSeparator

在正则表达式中使用引号,在 C# 中使用引号

如何在 C# 中使用正则表达式检索选定的文本?

使用 C# 正则表达式。问题是如何使用“=”