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

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

已在 Random-ZH 群组中发布
RegEx:掌握正则表达式的 20 个简短步骤。第 1 部分 :正则表达式:掌握正则表达式的 20 个简短步骤。第 2 部分: 在这一部分中,我们将继续讨论稍微复杂一点的事情。但像以前一样掌握它们并不困难。我再说一遍,正则表达式实际上比乍看起来更容易,您不需要成为火箭科学家就可以掌握它并开始在实践中使用它。本文的英文原文在这里掌握正则表达式的 20 个简短步骤。 第 3 - 1 部分

第 11 步:用括号()作为捕获组

掌握正则表达式的 20 个简短步骤。 第 3 - 2 部分在上一个问题中,我们寻找不同类型的整数值和浮点(点)数值。但是正则表达式引擎没有区分这两种类型的值,因为所有内容都被捕获在一个大的正则表达式中。如果我们将迷你模式括在括号中,我们可以告诉正则表达式引擎区分不同类型的匹配:
模式:([AZ])|([az])
字符串:  玻利维亚现任总统是 Evo Morales。
匹配:^^^ ^^^^^^^ ^^^^^^^^^ ^^ ^^^^^^^ ^^ ^^^ ^^^^^^^
组:    122 2222222 122222222 22 1222222 22 122 1222222  
示例)上面的正则表达式定义了两个从 1 开始索引的捕获组。第一个捕获组匹配任何单个大写字母,第二个捕获组匹配任何单个小写字母。通过使用“或”号|和括号()作为捕获组,我们可以定义匹配多种字符串的单个正则表达式。如果我们将其应用于本文前一部分中的长/浮点搜索正则表达式,则正则表达式引擎将捕获适当组中的相应匹配项。通过检查子字符串匹配哪一组,我们可以立即确定它是浮点值还是长整型值:
模式:(\d*\.\d+[fF]|\d+\.\d*[fF]|\d+[fF])|(\d+[lL])
字符串:   42L 12 x 3.4f 6l 3.3 0F LF .2F 0。
匹配:^^^ ^^^^ ^^ ^^ ^^^
组:    222 1111 22 11 111  
示例)这个正则表达式非常复杂,为了更好地理解它,让我们将其分解并查看每个模式:
( // 匹配任何“float”子字符串
  \d*\.\d+[fF]
  |
  \d+\.\d*[fF]
  |
  \d+[fF]
)
| // 或者
( // 匹配任何“长”子字符串
  \d+[lL]
)
括号中的符号|和捕获组()允许我们匹配不同类型的子字符串。在这种情况下,我们匹配浮点数“float”或长整数“long”。
(
  \d*\.\d+[fF] // 小数点右边 1+ 位
  |
  \d+\.\d*[fF] // 小数点左边 1+ 位
  |
  \d+[fF] // 没有点,只有 1+ 位数字
)
|
(
  \d+[lL] // 没有点,只有 1+ 位数字
)
在“float”捕获组中,我们有三个选项:小数点右侧至少有 1 位的数字、小数点左侧至少有 1 位的数字以及没有小数点的数字。只要它们末尾附加有字母“f”或“F”,它们中的任何一个都是“浮点数”。在“长”捕获组中,我们只有一个选项 - 我们必须有 1 个或多个数字,后跟字符“l”或“L”。正则表达式引擎将在给定字符串中查找这些子字符串并将它们索引到适当的捕获组中。 笔记我们不匹配任何没有添加“l”、“L”、“f”或“F”的数字。这些数字应该如何分类?好吧,如果它们有小数点,Java 语言默认为“double”。否则它们必须是“int”。

让我们通过几个谜题来巩固我们所学到的知识:

在上面的正则表达式中添加两个捕获组,以便它也可以对 double 或 int 数字进行分类。(这是另一个棘手的问题,如果需要一段时间也不要气馁,作为最后的手段请参阅我的解决方案。)
图案:
字符串:   42L 12 x 3.4f 6l 3.3 0F LF .2F 0。
匹配:^^^ ^^ ^^^^ ^^ ^^^ ^^ ^^^ ^^
组:    333 44 1111 33 222 11 111 22
)下一个问题稍微简单一些。使用括号内的捕获组()、“或”号|和字符范围对以下年龄进行排序:“在美国合法饮酒”。(>= 21) 和“不允许在美国饮酒”(<21):
图案:
字符串:   7 10 17 18 19 20 21 22 23 24 30 40 100 120
匹配:^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^ ^^^
组:    2 22 22 22 22 22 11 11 11 11 11 11 111 111 
解决方案

第 12 步:首先确定更具体的匹配

掌握正则表达式的 20 个简短步骤。 第 3 - 3 部分如果您尝试将“合法饮酒者”定义为第一个捕获组而不是第二个,那么您在上一个任务中可能会遇到一些麻烦。为了理解原因,让我们看另一个例子。假设我们要分别记录包含少于4个字符的姓氏和包含4个或更多字符的姓氏。让我们为第一个捕获组指定较短的名称,看看会发生什么:
模式:([AZ][az]?[az]?)|([AZ][az][az][az]+)
字符串:   Kim Job s Xu Clo yd Moh r Ngo Roc k。
匹配:^^^ ^^^ ^^ ^^^ ^^^ ^^^ ^^^
群组:    111 111 11 111 111 111 111   
示例)默认情况下,大多数正则表达式引擎对我们迄今为止看到的基本字符使用贪婪匹配。这意味着正则表达式引擎将捕获在提供的正则表达式中尽早定义的最长组。因此,虽然上面的第二组可以捕获名称中的更多字符,例如“Jobs”和“Cloyd”,但由于这些名称的前三个字符已经被第一个捕获组捕获,因此第二个捕获组无法再次捕获它们。现在让我们做一个小修正 - 只需更改捕获组的顺序,将更具体(更长)的组放在前面:
模式:([AZ][az][az][az]+)|([AZ][az]?[az]?)
字符串:   Kim Jobs Xu Cloyd Mohr Ngo Rock。
匹配:^^^ ^^^^ ^^ ^^^^^ ^^^^ ^^^ ^^^^
组:    222 1111 22 11111 1111 222 1111    
例子

任务...这次只有一个:)

“更具体”的模式几乎总是意味着“更长”。假设我们想要找到两种“单词”:首先是那些以元音开头的单词(更具体地说),然后是那些不以元音开头的单词(任何其他单词)。尝试编写正则表达式来捕获和识别与这两组匹配的字符串。(下面的组是用字母表示的,而不是编号的。您必须确定哪个组应对应于第一个组,哪个组应对应于第二个组。)
图案:
字符串:   pds6f uub 24r2gp ewqrty l ui_op
匹配:^^^^^ ^^^ ^^^^^^ ^^^^^^ ^ ^^^^^
组:    NNNNN VVV NNNNNN VVVVVV N VVVVV
解决方案) 一般来说,正则表达式越精确,它的长度就越长。它越准确,您捕获不需要的东西的可能性就越小。因此,虽然它们可能看起来很可怕,但更长的正则表达式〜=更好的正则表达式。很遗憾

步骤 13:花括号{}表示特定的重复次数

掌握正则表达式的 20 个简短步骤。 第 3 - 4 部分在上一步中姓氏的示例中,我们在一个模式中有 2 个几乎重复的组:
模式:([AZ][az][az][az]+)|([AZ][az]?[az]?)
字符串:   Kim Jobs Xu Cloyd Mohr Ngo Rock。
匹配:^^^ ^^^^ ^^ ^^^^^ ^^^^ ^^^ ^^^^
组:    222 1111 22 11111 1111 222 1111    
对于第一组,我们需要包含四个或更多字母的姓氏。第二组必须捕获包含三个或更少字母的姓氏。[a-z]有没有比一遍又一遍地重复这些组更简单的写法呢?如果为此使用花括号则存在{}。大括号{}允许我们指定前一个字符或捕获组的最小和(可选)最大匹配数。有以下三个用例{}
{X} // 正好匹配 X 次
{X,} // 匹配 >= X 次
{X,Y} // 匹配 >= X 且 <= Y 次
以下是这三种不同语法的示例:
模式:[az]{11}
字符串:   humuhumunuk unukuapua'a。
匹配:^^^^^^^^^^^   
例子
模式:[az]{18,}
字符串:   humuhumunukunukuapua 'a.
匹配:^^^^^^^^^^^^^^^^^^^^^^    
例子
模式:[az]{11,18}
字符串:   humuhumunukunukuap ua'a。
匹配:^^^^^^^^^^^^^^^^^^^    
示例)上面的示例有几点需要注意。笔记:。首先,使用 {X} 表示法,前一个字符或组将精确匹配该数字 (X) 次。如果“单词”中有更多字符(多于数字 X)可以匹配模式(如第一个示例所示),那么它们将不会包含在匹配中。如果字符数小于 X,则完全匹配将失败(尝试将第一个示例中的 11 更改为 99)。其次,符号{X,}和{X,Y}是贪婪的。他们将尝试匹配尽可能多的字符,同时仍然满足给定的正则表达式。如果指定 {3,7},则可以匹配 3 到 7 个字符,如果接下来的 7 个字符有效,则将匹配所有 7 个字符。如果指定 {1,} 并且接下来的 14,000 个字符全部匹配,则所有 14,000 个字符都将包含在相应的字符串中。我们如何利用这些知识来重写上面的表达式?最简单的改进可能是将相邻组替换[a-z][a-z]{N},其中 N 是相应选择的:
模式:([AZ][az]{2}[az]+)|([AZ][az]?[az]?)  
...但这并没有让事情变得更好。查看第一个捕获组:我们有[a-z]{2}(与 2 个小写字母精确匹配),后跟[a-z]+(与 1 个或多个小写字母匹配)。我们可以通过使用大括号要求 3 个或更多小写字母来简化:
模式:([AZ][az]{3,})|([AZ][az]?[az]?) 
第二个捕获组是不同的。这些姓氏中不需要超过三个字符,这意味着我们有上限,但下限为零:
模式:([AZ][az]{3,})|([AZ][az]{0,2}) 
使用正则表达式时,特异性总是更好,因此明智的做法是就此停止,但我忍不住注意到这两个相邻的字符范围 ([AZ][az]) 看起来几乎像一个“单词字符”类,\w( [A-Za-z0-9_]) 。如果我们确信我们的数据仅包含格式良好的姓氏,那么我们可以简化正则表达式以简单地编写:
模式:(\w{4,})|(\w{1,3}) 
第一组捕获 4 个或更多“单词字符”( [A-Za-z0-9_]) 的任何序列,第二组捕获 1 到 3 个“单词字符”(含)的任何序列。这行得通吗?
模式:(\w{4,})|(\w{1,3})
字符串:   Kim Jobs Xu Cloyd Mohr Ngo Rock。
匹配:^^^ ^^^^ ^^ ^^^^^ ^^^^ ^^^ ^^^^
组:    222 1111 22 11111 1111 222 1111    
示例)成功了!这种方法怎么样?而且它比我们之前的示例干净得多。由于第一个捕获组与具有四个或更多字符的所有姓氏相匹配,我们甚至可以将第二个捕获组更改为 simple \w+,因为这将允许我们捕获所有剩余的姓氏(具有 1、2 或 3 个字符):
模式:(\w{4,})|(\w+)
字符串:   Kim Jobs Xu Cloyd Mohr Ngo Rock。
匹配:^^^ ^^^^ ^^ ^^^^^ ^^^^ ^^^ ^^^^
组:    222 1111 22 11111 1111 222 1111    
例子

让我们帮助大脑学习这一点并解决以下两个问题:

使用大括号{}重写步骤 7 中的社会安全号码查找正则表达式:
图案:
字符串:113-25=1902 182-82-0192 H23-_3-9982 1I1-O0-E38B
匹配:              ^^^^^^^^^^^
(解决方案) 假设某网站的密码强度检查器要求用户密码长度在6到12个字符之间。编写一个正则表达式来标记下面列表中的无效密码。每个密码都包含在括号中()以便于匹配,因此请确保正则表达式以文字()符号字符开头和结尾。提示:请确保密码中不允许使用带有[^()]或类似的文字括号,否则最终将匹配整个字符串!
图案:
字符串:   (12345)(我的密码)(Xanadu.2112)(su_do)(OfSalesmen!)
匹配:^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^  
解决方案

第14步:\b零宽度边框符号

掌握正则表达式的 20 个简短步骤。 第 3 - 5 部分最后的任务相当困难。""但是,如果我们通过将密码放在引号而不是括号中来使其变得更复杂一点呢()?我们可以通过简单地将所有括号字符替换为引号字符来编写类似的解决方案吗?
模式:\"[^"]{0.5}\"|\"[^"]+\s[^"]*\"
字符串:   "12345" "我的密码" "Xanadu.2112 " " su_do" " OfSalesmen! ”
匹配:^^^^^^^ ^^^^^^^^^^^^^ ^^^ ^^^  
示例)结果并不令人印象深刻。你已经猜到原因了吗?问题是我们在这里寻找不正确的密码。“Xanadu.2112”是一个很好的密码,因此当正则表达式意识到该序列不包含空格或文字字符时,它会在右侧限定密码的"字符之前产生。(因为我们指定使用 无法在密码中找到"字符。)一旦正则表达式引擎确信这些字符与特定正则表达式不匹配,它就会再次运行,恰好在它停止的位置 - 字符所在的位置。这限制了“世外桃源.2112”在右边。从那里他看到一个空格字符和另一个字符- 对他来说这是错误的密码!基本上,他找到了这个序列并继续前进。这根本不是我们想要得到的......如果我们能够指定密码的第一个字符不应该空格,那就太好了。有没有办法做到这一点?(到现在为止,您可能已经意识到我所有反问句的答案都是“是”。) 是的!有这样的办法!许多正则表达式引擎提供转义序列,例如“字边界” 。“字边界”是一个零宽度转义序列,奇怪的是,它与字边界匹配。请记住,当我们说“单词”时,我们指的是类中的任何字符序列或. 单词边界匹配意味着紧邻序列之前或之后的字符必须是单词字符。但是,在匹配时,我们不会将该字符包含在捕获的子字符串中。这是零宽度。为了了解它是如何工作的,让我们看一个小例子: "[^"]""" "\b\b\w[A-Za-z0-9_]\bне
模式:\b[^]+\b
字符串:   Ve still vant ze money , Lebowski。
匹配:^^ ^^^^^ ^^^^ ^^ ^^^^^ ^^^^^^^^  
示例)序列[^ ]必须匹配非文字空格字符的任何字符。那么为什么这不匹配,Money 后面的逗号或.Lebowski 后面的句号 " 呢?这是因为逗号,和句号.不是单词字符,所以在单词字符和非单词字符之间创建了界限。它们出现y在“money”一词,及其后的逗号。以及“ iLebowski 一词及其.后的句号(句号/句号)之间。正则表达式匹配这些单词的边界(但不匹配仅帮助定义它们的非单词字符)。\b但是,如果我们的模板中 不包含一致性,会发生什么情况?
模式:[^]+
字符串:   Ve still vant ze money,Lebowski。
匹配:^^ ^^^^^ ^^^^ ^^ ^^^^^^ ^^^^^^^^^  
示例)是的,现在我们也找到了这些标点符号。现在让我们使用单词边界来修复带引号的密码的正则表达式:
模式:\"\b[^"]{0.5}\b\"|\"\b[^"]+\s[^"]*\b\"
字符串:   "12345" "我的密码" " Xanadu。第2112
匹配:^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^  
示例)通过将单词边界放在引号内(“\b ... \b”),我们实际上是在说匹配密码的第一个和最后一个字符必须是“单词字符”。因此,这在这里工作正常,但如果用户密码的第一个或最后一个字符不是单词字符,则效果不佳:
模式:\"\b[^"]{0.5}\b\"|\"\b[^"]+\s[^"]*\b\"
字符串:“thefollowingpasswordistooshort”“C++”
火柴:   
示例)看看第二个密码如何没有被标记为“无效”,即使它显然太短。你一定是小心与序列\b,因为它们只匹配字符之间的边界\w,而不匹配\w。在上面的示例中,由于我们在密码中允许使用 not , 字符\w,因此密码的第一个/最后一个字符之间的边界\不能保证是单词边界\b

为了完成这一步骤,我们将只解决一个简单的问题:

当我们想要匹配特定的字符序列,但想要确保它们只出现在单词的开头或结尾(或单独出现)时,单词边界在语法突出显示引擎中非常有用。假设我们正在编写语法突出显示并希望突出显示单词 var,但仅当它单独出现时(不触及单词中的其他字符)。你能为此写一个正则表达式吗?当然可以,这是一个非常简单的任务;)
图案:
字符串:   var varx _var ( var j) barvarcar * var var -> { var }
匹配:^^^ ^^^ ^^^ ^^^ ^^^  
解决方案

第 15 步:“插入符号”^作为“行的开头”,美元符号$作为“行的结尾”

掌握正则表达式的 20 个简短步骤。 第 3 - 6 部分单词边界序列\b(来自本文上一部分的最后一步)并不是唯一可在正则表达式中使用的特殊零宽度序列。最流行的两个是“插入符号” ^-“行的开头”和美元符号$-“行的结尾”。在正则表达式中包含其中之一意味着匹配必须出现在源字符串的开头或结尾:
模式:^start|end$
字符串:   start end start end start end start end
匹配:^^^^^ ^^^  
示例)如果您的字符串包含换行符,它将^start匹配任何行开头的序列“start”,并将end$匹配任何行末尾的序列“end”(尽管这很难在此处显示)。这些符号在处理包含分隔符的数据时特别有用。^让我们使用“行首”回到步骤 9 中的“文件大小”问题。在此示例中,我们的文件大小由空格“”分隔 。因此,我们希望每个文件大小以数字开头,前面是空格字符或行开头:
模式:(^| )(\d+|\d+\.\d+)[KMGT]B
字符串:   6.6KB 1..3KB 12KB 5G 3.3MB KB .6.2TB 9MB。
匹配:^^^^^ ^^^^^ ^^^^^^ ^^^^
组:    222 122 1222 12    
)我们已经离目标很近了!但您可能会注意到,我们仍然有一个小问题:我们在有效文件大小之前匹配空格字符。现在,当我们的正则表达式引擎找到此捕获组 (1) 时,我们可以简单地忽略它,或者我们可以使用非捕获组,我们将在下一步中看到。

同时,让我们解决另外 2 个语气问题:

继续上一步的语法突出显示示例,某些语法突出显示将标记尾随空格,即非空白字符和行尾之间的任何空格。您可以编写一个正则表达式来仅突出显示尾随空格吗?
图案:
字符串:myvec <- c(1, 2, 3, 4, 5)  
匹配:                         ^^^^^^^  
解决方案)一个简单的逗号分隔值(CSV)解析器将查找用逗号分隔的“标记”。一般来说,除非用引号括起来,否则空格没有任何意义""。编写一个简单的 CSV 解析正则表达式,该表达式匹配逗号之间的标记,但忽略(不捕获)不在引号之间的空格。
图案:
字符串:   a, "b", "c d",e,f, "g h", dfgi,, k, "", l
匹配:^^ ^^^^ ^^^^^^^^^^ ^^^ ^^^ ^^^^^^ ^^ ^^^ ^
群组:    21 2221 2222212121 222221 222211 21 221 2    
解决方案RegEx:20个简短步骤掌握正则表达式。第 4 部分。
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION