JavaRush /Java 博客 /Random-ZH /RegEx:掌握正则表达式的 20 个简短步骤。第 4 部分
Artur
第 40 级
Tallinn

RegEx:掌握正则表达式的 20 个简短步骤。第 4 部分

已在 Random-ZH 群组中发布
RegEx:掌握正则表达式的 20 个简短步骤。第 1 部分 RegEx:掌握正则表达式的 20 个简短步骤。第 2 部分 掌握正则表达式的 20 个简短步骤。第三部分 中间的最后一部分将涉及正则表达式大师主要使用的东西。但前面部分的材料对你来说很容易,对吧?这意味着您可以同样轻松地处理这种材料!原文 RegEx:掌握正则表达式的 20 个简短步骤。 第 4 - 1 部分 <h2>第 16 步:不捕获的分组(?:)</h2> RegEx:掌握正则表达式的 20 个简短步骤。 第 4 - 2 部分在上一步的两个示例中,我们捕获了并不真正需要的文本。在文件大小任务中,我们捕获了文件大小第一个数字之前的空格,在 CSV 任务中,我们捕获了每个标记之间的逗号。我们不需要捕获这些字符,但我们确实需要使用它们来构造我们的正则表达式。这些是使用组而不捕获的理想选择(?:)。非捕获组的作用正如它听起来的那样 - 它允许对字符进行分组并在正则表达式中使用,但不会将它们捕获在编号组中:
模式:(?:")([^"]+)(?:")
字符串:我只想要"这些引号内的文本"。
匹配:            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
组:                 1111111111111111111111111111    
示例)正则表达式现在匹配引用的文本以及引用字符本身,但捕获组仅捕获引用的文本。我们为什么要这样做?关键是大多数正则表达式引擎允许您从正则表达式中定义的捕获组恢复文本。如果我们可以修剪不需要的额外字符而不将它们包含在我们的捕获组中,那么稍后解析和操作文本将变得更加容易。以下是清理上一步中的 CSV 解析器的方法:
模式:(?:^|,)\s*(?:\"([^",]*)\"|([^", ]*))
字符串:   a , " b ", " cd ", e , f , " gh ", dfgi ,, k , "", l
匹配:^ ^ ^^^ ^ ^ ^^^ ^^^^ ^ ^
组:    2 1 111 2 2 111 2222 2 2    
示例)这里有一些事情需要<mark>注意:</mark>首先,自从我们将捕获组更改(^|,)为非捕获组以来,我们不再捕获逗号(?:^|,)。其次,我们将捕获组嵌套在非捕获组中。例如,当您需要一组字符以特定顺序出现,但您只关心这些字符的子集时,这非常有用。在我们的例子中,我们需要引号字符和逗号[^",]*出现在引号中,但我们实际上并不需要引号字符本身,因此不需要捕获它们。k最后,<mark>注意</mark>在上面的示例中,字符和之间也存在零长度匹配l。引号""是搜索到的子字符串,但引号之间没有字符,因此匹配的子字符串不包含任何字符(长度为零)。<h3>我们要巩固我们的知识吗?以下两个半任务将帮助我们完成此任务:</h3> 使用非捕获组(以及捕获组和字符类等),编写一个仅捕获格式正确的文件大小的正则表达式以下 :
图案:
字符串:   6.6KB 1..3KB 12KB 5G 3.3MB KB .6.2TB 9MB。
匹配: ^^^^^ ^^^^^ ^^^^^^ ^^^^
群组:    11111 1111 11111 111    
解决方案) HTML 开始标记以 开始<,以 结束>。HTML 结束标记以字符序列开始</并以字符结束>。标签名称包含在这些字符之间。您可以编写一个正则表达式来仅捕获以下标记中的名称吗?(您也许可以在不使用非捕获组的情况下解决此问题。尝试通过两种方式解决此问题!一次使用组,一次不使用组。)
图案:
字符串:   <p> </span> <div> </kbd> <link>
匹配:^^^ ^^^^^^ ^^^^^ ^^^^^^ ^^^^^^
组:    1 1111 111 111 1111    
(使用非捕获组的解决方案) (不使用非捕获组的解决方案) <h2>第 17 步:反向链接\N和命名捕获组</h2> RegEx:掌握正则表达式的 20 个简短步骤。 第 4 - 3 部分尽管我在简介中警告您,尝试使用正则表达式创建 HTML 解析器通常导致心痛,最后一个例子很好地介绍了大多数正则表达式的另一个(有时)有用的功能:反向引用。反向链接就像重复组,您可以尝试两次捕获相同的文本。但它们在一个重要方面有所不同——它们只会逐个字符地捕获相同的文本。虽然重复组可以让我们捕获如下内容:
模式:(he(?:[az])+)
字符串:   heyabcdefg hey heyo heyellow heyyyyyyyyy
匹配:^^^^^^^^^^^ ^^^ ^^^^ ^^^^^^^^ ^^^ ^^^^^^^^^
群:    1111111111 111 1111 11111111 11111111111    
示例)...那么反向链接将仅匹配以下内容:
模式:(he([az])(\2+))
字符串:heyabcdefg hey heyo heyellow heyyyyyyyyy
匹配:                             ^^^^^^^^^^^
组:                                 11233333333    
示例)当您想要重复匹配相同的模式时,重复捕获组非常有用,而当您想要匹配相同的文本时,反向链接则很有用。例如,我们可以使用反向链接来尝试查找匹配的开始和结束 HTML 标记:
模式:<(\w+)[^>]*>[^<]+<\/\1>
字符串:   <span style="color: red">hey</span>
匹配:^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
群组:    1111    
示例)<mark>请注意</mark>这是一个极其简化的示例,我强烈建议您不要尝试编写基于正则表达式的 HTML 解析器。这是非常复杂的语法,很可能会让您感到恶心。 命名捕获组与反向链接非常相​​似,因此我将在这里简要介绍它们。反向引用和命名捕获组之间的唯一区别是...命名捕获组有一个名称:
模式:<(?<tag>\w+)[^>]*>[^<]+<\/(?P=tag)></tag>
字符串:   <span style="color: red">嘿< /span>
匹配:^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
群组:    1111    
示例)您可以使用 (?<name>...) 或 (?'name'...) 语法(.NET 兼容正则表达式)或使用此语法 (?P<name>. ..) 或 (?P'name'...) (Python 兼容的正则表达式)。由于我们使用的是支持这两个版本的 PCRE(Perl 兼容正则表达式),因此我们可以在这里使用其中之一。(Java 7 复制了 .NET 语法,但仅复制了尖括号版本。译者注) 为了稍后在正则表达式中重复命名捕获组,我们使用 \<kname> 或 \k'name' (.NET) 或 (? P= 名称)(Python)。同样,PCRE 支持所有这些不同的选项。您可以在此处阅读有关命名捕获组的更多信息,但这就是您真正需要了解的有关它们的大部分内容。<h3>帮助我们的任务:</h3>使用反向链接帮助我记住...嗯...这个人的名字。
图案:
字符串:“嗨,我叫乔。” [后来]“那个人叫什么名字?乔?”
匹配:       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^
组:                  111    
解决方案) <h2>第 18 步:前瞻和后瞻</h2> RegEx:掌握正则表达式的 20 个简短步骤。 第 4 - 4 部分现在我们将深入探讨正则表达式的一些高级功能。我经常使用步骤 16 之前的所有内容。但最后几个步骤仅适用于非常认真地使用正则表达式来匹配非常复杂的表达式的人。换句话说,正则表达式大师。“展望未来”和“回顾过去”可能看起来很复杂,但实际上并不太复杂。它们允许您执行类似于我们之前对非捕获组所做的操作 - 检查我们要匹配的实际文本之前或之后是否有任何文本。例如,假设我们只想匹配人们喜欢的事物的名称,但前提是他们对此充满热情(前提是他们以感叹号结束句子)。我们可以做这样的事情:
模式:(\w+)(?=!)
字符串:我喜欢桌子。我很欣赏订书机。我爱!
匹配:                                          ^^^^
组:                                              1111    
示例)您可以看到上面的捕获组(\w+)通常与段落中的任何单词匹配,但仅与单词 lamp 匹配。正向前瞻(?=!)意味着我们只能匹配以 结尾的序列!,但实际上并不匹配感叹号字符本身。这是一个重要的区别,因为对于非捕获组,我们匹配字符但不捕获它。通过lookaheads和lookbehinds,我们使用一个字符来构建我们的正则表达式,但我们甚至不将它与自身进行匹配。我们可以稍后在正则表达式中匹配它。前瞻和后瞻有四种类型:正前瞻 (?=...)、负前瞻 (?!...)、正前瞻 (?<=...) 和负前瞻 (?<!. ..) 。它们的作用就像听起来一样 - 正向先行和后向允许正则表达式引擎仅在前向/后向中包含的文本实际匹配时继续匹配。负向先行和后向执行相反的操作 - 仅当先行/后行中包含的文本不匹配时,它们才允许正则表达式匹配。例如,我们只想匹配方法序列链中的方法名称,而不是它们操作的对象。在这种情况下,每个方法名称前面必须带有.. 使用简单回顾的正则表达式可以在这里提供帮助:
模式:(?<=\.)(\w+)
字符串:myArray. flatMap.aggregate.summarise.print!
匹配:        ^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ ^^^^^
群组:            1111111 111111111 111111111 11111    
示例)在上面的文本中,我们匹配任何单词字符序列\w+,但前提是它们前面有 字符.。我们可以使用非捕获组实现类似的效果,但结果有点混乱:
模式:(?:\.)(\w+)
字符串: myArray .flatMap.aggregate.summarise.print!
匹配:       ^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^^^
组:            1111111 111111111 111111111 11111    
示例)尽管它更短,但它匹配我们不需要的字符。虽然这个例子看起来微不足道,但前瞻和后瞻确实可以帮助我们清理正则表达式。<h3>距离终点所剩无几!以下 2 个任务将使我们离它更近一步:</h3> 负向后查找 (?<!...) 允许正则表达式引擎继续尝试查找匹配项(仅当负向后查找中包含的文本不存在时)显示直到文本的其余部分,您需要从中找到匹配项。例如,我们可以使用正则表达式仅匹配参加会议的女性的姓氏。为此,我们要确保此人的姓氏前面没有Mr.. 你能为此写一个正则表达式吗?(可以假定姓氏至少有四个字符长。)
图案:
串:先生。布朗女士,史密斯,夫人 琼斯黛西小姐、先生。绿色的
匹配:               ^^^^^ ^^^^^ ^^^^^
群组:                   11111 11111 11111    
解决方案) 假设我们正在清理数据库,并且有一列代表百分比的信息。不幸的是,有些人将数字写为 [0.0, 1.0] 范围内的十进制值,而另一些人则将百分比写为 [0.0%, 100.0%] 范围内的百分比,还有一些人写了百分比值,但忘记了字面上的百分号%。使用负前瞻(?!...),您可以仅标记那些应该是百分比但缺少数字的值吗%?这些值必须严格大于 1.00,但末尾不带%. (任何数字在小数点前后都不能包含超过两位数字。)<mark>注意</mark>这个解决方案非常困难。如果你不看我的答案就能解决这个问题,那么你已经拥有了正则表达式的高超技巧!
图案:
字符串: 0.32 100.00 5.6 0.27 98% 12.2% 1.01 0.99% 0.99 13.13 1.10
匹配:      ^^^^^^ ^^^ ^^^^ ^^^^^ ^^^^
组:         111111 111 1111 11111 1111    
解决方案) <h2>第 19 步:正则表达式中的条件</h2> RegEx:掌握正则表达式的 20 个简短步骤。 第 4 - 5 部分我们现在已经到了大多数人将不再使用正则表达式的地步。我们已经涵盖了大约 95% 的简单正则表达式用例,并且步骤 19 和 20 中完成的所有操作通常是由功能更齐全的文本操作语言(如 awk 或 sed)(或通用编程语言)完成的。也就是说,让我们继续前进,以便您了解正则表达式的真正作用。尽管正则表达式不是图灵完备的,但一些正则表达式引擎提供了与完整编程语言非常相似的功能。这样的特征之一就是“条件”。正则表达式条件允许 if-then-else 语句,其中所选分支由我们在上一步中了解的“向前看”或“向后看”确定。例如,您可能只想匹配日期列表中的有效条目:
模式:(?<=Feb )([1-2][0-9])|(?<=Mar )([1-2][0-9]|3[0-1])
字符串:工作日期: 2月28日, 2月29日, 2月30日, 3月30日, 3月31日 
比赛:                   ^^ ^^ ^^ ^^
小组:                      11 11 22 22    
示例)<mark>请注意</mark>上述组也按月索引。我们可以为所有 12 个月编写一个正则表达式,并仅捕获有效日期,然后将其组合成按一年中的月份索引的组。上面使用了一种类似 if 的结构,如果“Feb”在数字前面(第二组也类似),则只会在第一组中查找匹配项。但是,如果我们只想对二月使用特殊处理怎么办?类似于“如果数字前面有“二月”,则执行此操作,否则执行其他操作。” 条件语句的执行方式如下:
模式:(?(?<=Feb )([1-2][0-9])|([1-2][0-9]|3[0-1]))
字符串:工作日期:2 月28 日, 2月29日, 2月30日 , 3月30日, 3月31日 
比赛:                   ^^ ^^ ^^ ^^
组:                      11 11 22 22    
示例)if-then-else 结构类似于 (?(If)then|else),其中 (if) 被“向前看”或“向后看”替换。在上面的例子中,(if) 写为(?<=Feb)。您可以看到我们匹配了大于 29 的日期,但前提是它们不遵循“Feb”。如果您想确保匹配项前面有一些文本,则在条件表达式中使用lookbehinds 非常有用。正向先行条件可能会令人困惑,因为条件本身不匹配任何文本。因此,如果您希望 if 条件有一个值,它必须与前瞻相当,如下所示:
模式:(?(?=exact)exact|else)wo
字符串:exact else strictwo elsewo 
匹配:           ^^^^^^^^ ^^^^^^
示例)这意味着肯定的先行条件是没有用的。您检查该文本是否在前面,然后提供匹配的模式以供遵循。条件表达式在这里根本没有帮助我们。您也可以将上面的内容替换为更简单的正则表达式:
模式:( ?:exact|else)wo
字符串:exact else strictwo elsewo 
匹配:           ^^^^^^^^ ^^^^^^
示例)因此,条件表达式的经验法则是:测试、测试、再测试。否则,您认为显而易见的解决方案将以最令人兴奋和意想不到的方式失败:) <h3>这里我们来到了将我们与最后的第 20 步分开的最后一个任务块:</h3>编写一个正则表达式使用负先行条件表达式来测试下一个单词是否以大写字母开头。如果是这样,请仅抓取一个大写字母,然后抓取小写字母。如果没有,请抓取任何单词字符。
图案:
字符串:   Jones Smith 9sfjn Hobbes 23r4tgr9h CSV Csv vVv
匹配:^^^^^ ^^^^^ ^^^^^ ^^^^^^ ^^^^^^^^^ ^^^ ^^^
组:    22222 22222 11111 222222 111111111 222 111    
解决方案owns) 编写一个否定的lookbehind 条件表达式,仅当文本前面没有文本时才捕获文本cl,并且ouds仅当文本前面有文本时才捕获文本cl。(有点人为的例子,但你能做什么......)
图案:
字符串:那些小丑拥有一些。乌德琴。
匹配:             ^^^^ ^^^^   
解决方案) <h2>第 20 步:递归和进一步研究</h2> RegEx:掌握正则表达式的 20 个简短步骤。 第 4 - 6 部分事实上,任何主题的 20 步介绍都可以压缩很多内容,正则表达式也不例外。可以在 Internet 上找到许多不同的正则表达式实现和标准。如果您想了解更多信息,我建议您查看精彩的网站regularexpressions.info,这是一个很棒的参考资料,我当然从那里学到了很多关于正则表达式的知识。我强烈推荐它,以及用于测试和发布您的创作的regex101.com 。在最后一步中,我将为您提供更多有关正则表达式的知识,即如何编写递归表达式。简单的递归非常简单,但让我们考虑一下这在正则表达式的上下文中意味着什么。正则表达式中简单递归的语法如下所示:(?R)?。但是,当然,此语法必须出现在表达式本身中。我们要做的是将表达式嵌套在其自身中,任意次数。例如:
模式:(hey(?R)?oh)
字符串:   heyoh heyyoh heyheyohoh hey oh heyhey hey heyheyohoh 
匹配:^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^
组:    11111 1111111111 1111111111    
示例)由于嵌套表达式是可选的((?R)后跟?),最简单的匹配就是完全忽略递归。因此,hey,然后oh匹配 ( heyoh)。要匹配任何比这更复杂的表达式,我们必须在表达式中插入序列的位置找到嵌套在其自身内部的匹配子字符串(?R)。换句话说,我们可以找到 heyheyohoh 或 heyheyheyohohoh,等等。这些嵌套表达式的优点之一是,与反向引用和命名捕获组不同,它们不会将您限制为之前逐个字符匹配的确切文本。例如:
模式:([Hh][Ee][Yy](?R)?oh)
字符串:   heyoh heyyoh hEyHeYohoh hey oh heyhey hEyHeYHEyohohoh 
匹配:^^^^^ ^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^
群:    11111 1111111111 111111111111111    
示例)您可以想象正则表达式引擎实际上将正则表达式复制并粘贴到自身中任意次。当然,这意味着有时它可能不会达到您所希望的效果:
模式:((?:\(\*)[^*)]*(?R)?(?:\*\)))
字符串:(* 注释(* 嵌套 *)不是 *)
匹配:           ^^^^^^^^^^^^^
群组:               111111111111    
示例)你能说出为什么这个正则表达式只捕获嵌套注释而不捕获外部注释吗?有一点是肯定的:在编写复杂的正则表达式时,请务必对其进行测试,以确保它们按照您认为的方式工作。这场正则表达式之路上的高速拉力赛已经结束了。我希望你喜欢这次旅程。好吧,最后,正如我在一开始所承诺的那样,我将在这里留下几个有用的链接,以便更深入地研究该材料:
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION