RegexOne 中文 通过简单的交互式练习来学习正则表达式
标签搜索
侧边栏壁纸
  • 累计撰写 59 篇文章
  • 累计收到 1 条评论

RegexOne 中文 通过简单的交互式练习来学习正则表达式

Curry
2026-01-16 / 0 评论 / 0 阅读 / 正在检测是否收录...

RegexOne 中文 - 通过简单的交互式练习来学习正则表达式

RegexOne 中文 - 通过简单的交互式练习来学习正则表达式

https://imageslr.github.io/regexone-cn/

课程 1:简介和字母 ABCs

正则表达式 (regular expressions) 是一个非常有用的工具,可以从诸如代码、日志文件、电子表格或文档等文本中提取信息。虽然规范的语言背后有许多理论,但后续的课程和例子将探索正则表达式的更实际的用法,以便您能够尽快使用这个工具。

在使用正则表达式时要认识到的第一件事是:所有东西本质上都是字符,我们正在编写模式 (pattern) 来匹配特定的字符序列(也称为字符串)。大多数模式使用普通的 ASCII 字符,包括字母、数字、标点符号和键盘上的其他符号,如 %#$@!,但是也可以使用 unicode 字符来匹配任何类型的国际文本。

下面是几行文本,当您在输入框中输入模式时,请注意每行的匹配字符是如何变化的。要继续下一课,您需要使用每个课程中介绍的新的语法 (syntax) 和概念,写出能够匹配给定的每行文本的模式。

请尝试写一个能够匹配下面三行的模式,很简单,找出每行的共同字母。

练习 1:匹配字母

TaskTextResult
matchabcdefgSuccess
matchabcdeSuccess
matchabcSuccess

输入前三个字符 abc。

[](https://imageslr.github.io/regexone-cn/lesson/letters_and_digits.html#课程-11⁄2-数字-123s)课程 1½:数字 123s

字符包括普通的字母,也包括数字。事实上,数字 0-9 也是字符,如果您查看 ASCII表

(opens new window),会发现它们是按顺序列出的。

在整个课程中,我们将会学习正则表达式中的一系列特殊元字符 (metacharacter),这些元字符可用于匹配特定类型的字符。比如,字符 \d 可以用来代替从 0 到 9 的任意数字。前面的斜杠与简单的 d 区分,表示它是元字符。

下面是另外几行包含数字的文本。尝试编写模式来匹配下面字符串中的所有数字,并注意您的模式是如何匹配字符串中的任意位置的,而不仅仅是从第一个字符开始。我们将在后续的课程中学习如何控制它。

练习 1½:匹配数字

TaskTextResult
matchabc123xyz
matchdefine "123"
matchvar g = 123;

输入所有行共有的数字 123。

课程 2:点 Dot

在一些纸牌游戏中,小丑是一个百搭牌 (wildcard),可以代表桥牌中的任意一张牌。在使用正则表达式的时候,我们经常需要匹配一串不知道具体内容的、但是共享相同模式或结构的文本,例如电话号码或邮政编码。

类似于纸牌游戏,正则表达式中也有通配符 (wildcard) 的概念,由元字符 . (点) 来表示,可以匹配任意单个字符 (字母、数字、空格等等)。您可能会注意到,这包含了句点字符的匹配。因此,如果想专门匹配一个句点,我们需要使用斜杠来转义,也就是 \.

下面是一些字符不同但长度相同的字符串。尝试编写一个可以匹配前三个字符串、但不能匹配最后一个 (要跳过的) 的模式。您可能会发现必须转义元字符 . 以匹配某些行中的句点。

练习 2:使用通配符

TaskTextResult
matchcat.
match896.
match?=+.
skipabc1

您可以使用 .... 来匹配前三个 (通配) 字符,同时转义最后一个通配符元字符来匹配到句点。这保证了它不会匹配到第四行的 '1'。

课程 3:匹配特定字符

上节课的点元字符非常强大,但有时强大了。比如我们匹配电话号码时,并不希望将字母"(abc) def-ghij"视作一个有效的号码。

有一种方法可以在正则表达式中匹配特定字符,方法是在方括号 (square brackets) 中定义它们。比如,模式 [abc] 将只匹配单个字母 a、b 或者 c,其他什么也不匹配。

下面几行字符串中,我们只想匹配前三个而不匹配后三个。请注意,如果使用点元字符,我们就无法避免匹配后三个字符串。因此,必须使用上面的方法来明确声明要匹配的字母。

练习 3:匹配特定字符

TaskTextResult
matchcan
matchman
matchfan
skipdan
skipran
skippan

您可以使用表达式 [cmf]an 来只匹配 'can'、'man' 和 'fan',而不匹配任何其他行。正如您将在下一课中看到的,您也可以使用逆表达式 (inverse expression) 1an 来匹配任意以 'an' 结尾、但不以 'd'、'r' 或 'p' 开头的三个字母的单词。

课程 4:排除特定字符

在某些情况下,我们可能不想匹配某些特定的字符,例如,我们可能只想匹配不是来自区号 650 的电话号码。

为了表示这一点,我们使用方括号^ (hat) 组成的表达式来排除特定字符。例如,模式 [^abc] 将匹配除了字母 a、b 或 c 以外的任意单个字符。

在下面的字符串中,尝试写一个只匹配动物 (猪 hog、狗 dog,而不是沼泽 bog) 的模式。请注意,我们也可以使用上一课的技巧来编写模式,因为它们实际上是同一问题的两个方面。在编写模式时,您可以自己决定使用哪种方式更容易编写和理解。

练习 4:排除字符

TaskTextResult
matchhog
matchdog
skipbog

匹配以 "og" 结尾但不是 "bog" 的最简单的方法是表达式 2og。或者,您也可以使用上一课学到的知识,使用 [hd]og 来只匹配 "hog" 和 "dog"。注意,后者是一个更严格的表达式,因为它限制了可以匹配的字符串。

课程 5:字符范围

我们刚刚学习了如何创建一个匹配或排除特定字符的模式——但是如果我们想匹配一个连续范围内的字符呢?我们只能把它们全部列出来吗?

幸运的是,在使用方括号表示法时,有一种速记方法,可以通过使用破折号来表示字符范围,从而匹配到连续范围内的字符。例如,模式 [0-6] 将只匹配从 0 到 6 的任意单个数字字符。同样的,[^n-p] 将只匹配字母 n 到 p 以外的任何单个字符。

同一组方括号中还可以使用多个字符范围,以及单个字符。例如字母数字 (alphanumeric) 元字符 \w,它相当于字符范围 [A-Za-z0-9_],通常用于匹配英文文本中的字符。

在下面的练习中,请注意所有匹配行和跳过行共有的模式,并使用方括号表示法来匹配或跳过每行中的每个字符。请注意,模式是区分大小写的,并且 a-zA-Z 在匹配的字符方面是不同的(小写 vs 大写)。

练习 5:匹配字符范围

TaskTextResult
matchAna
matchBob
matchCpc
skipaax
skipbby
skipccz

从上到下,每个位置的字符都是连续的,因此可以使用 A-C[a-c] 中的不同范围来只匹配前三行。

课程 6:匹配重复字符

注意:有的正则表达式实现不支持下文重复语法的某些部分。

到目前为止,我们已经学习了如何指定要匹配的字符的范围,但是如何指定要匹配的字符的重复次数呢?一种方法是明确写出我们想要匹配多少个字符,例如 \d\d\d,它正好匹配三个数字。

更方便的方法是使用花括号表示法指定每个字符的重复次数。例如,a{3} 将匹配字符 a 正好三次。某些正则表达式引擎甚至允许您指定重复次数的范围,例如,a{1,3} 将匹配字符 a 至多 3 次,至少 1 次。

这个量词 (quantifier) 可以与任意字符或特殊的元字符一起使用,例如 w{3} (三个 w)、[wxy]{5} (五个字符,每个字符可以是 w、x 或 y) 和 .{2,6} (两到六个任意字符)。

译者注

花括号中的最大重复次数也可以省略:{n,} 表示重复至少 n 次,至多无限制。

在下面的几行中,最后一个只有一个 z 的字符串并不是俚语 "wazzup" 的正确拼写。尝试使用上面的花括号符号来编写一个只匹配前两个拼写的模式。

练习 6:匹配重复字符

TaskTextResult
matchwazzzzzup
matchwazzzup
skipwazup

在我们必须匹配的前两行中有多个 'z',因此表达式 waz{3,5}up 将匹配所有包含这些个 'z' 的字符串。

进阶课程:A4:贪婪 vs 懒惰

课程 7:Kleene 操作符

正则表达式中一个强大的概念是能够匹配任意数量的字符。例如,假设您编写了一个表单,其中有一个以美元为单位的捐赠字段。一个富有的用户可能会顺道来,想捐 25000 美元,而一个普通用户可能想捐 25 美元。

表达这种模式的一种方法是使用克莱尼星号 (Kleene Star)克莱尼加号 (Kleene Plus),分别表示 0 个或更多个1 个或更多个它所跟随的字符 (或组)。例如,为了匹配上面的捐赠金额,我们可以使用模式 \d\* 来匹配任意数量的数字,但是更严格的正则表达式是 \d+,它确保输入的字符串至少有一个数字。

这些量词 (quantifiers) 可以与任意字符或特殊的元字符一起使用,例如 a+ (一个或多个 a)、[abc]+ (一个或多个字符 a、b 或 c) 和 .\* (零个或多个任意字符)。

下面是几个简单的字符串,您可以使用星号和加号元字符来匹配它们。

练习 7:匹配重复字符

TaskTextResult
matchaaaabcc
matchaabbbbc
matchaacc
skipa

要匹配的字符串中,有至少两个 'a'、0 个或多个 'b'、至少一个 'c',因此可以使用表达式 aa+b*c+ 来表示。

或者,一个限制性更强的表达式是 a{2,4}b{0,4}c{1,2},它为每个字符的数量都设置了一个上限和下限。

课程 8:可选字符

正如您在上一课中看到的,Kleene star 和 plus 允许我们匹配一行中的重复字符。

另一个在匹配和提取文本时非常常见的量词 (quantifier) 是 ? (问号) 元字符,表示可选性。这个元字符允许您匹配前面的零个或一个字符或组。例如,模式 ab?c 将匹配字符串 "abc" 或 "ac",因为 b 被认为是可选的。

与点元字符类似,问号是一个特殊字符,您必须使用斜杠 \? 匹配字符串中的普通问号字符。

在下面的字符串中,请注意找到的文件数是如何决定单词 "file" 的复数形式的。尝试编写一个模式,使用元字符 ? 来匹配找到一个或多个文件的行。

练习 8:匹配可选字符

TaskTextResult
match1 file found?
match2 files found?
match24 files found?
skipNo files found.

我们可以使用元字符 '\d' 来匹配文件的数量,并使用表达式 \d+ files? found? 来匹配找到文件的所有行。

请注意,第一个问号用于前面的 "s" 字符 (表示复数),最后的问号必须转义以匹配文本。

课程 9:空白字符

在处理真实世界的输入时,例如日志文件或者用户输入,很难不遇到空白符 (whitespace)。我们使用它来格式化信息片段,使其更直观、易于阅读。但是一个空格却破坏了最简单的正则表达式。

正则表达式中最常见的空白符的形式有空格 (␣)制表符 (\t)换行 (\n)回车 (\r) (在 Windows 环境中很有用)。这些特殊字符匹配的空白符各不相同,但特殊字符 \s 能够匹配上面的任意空白符,这在处理原始输入文本时非常有用。

在下面的字符串中,您会发现每一行的内容都会在标号后面用一些空白符缩进 (标号也是要匹配的文本的一部分)。请尝试编写一个模式,该模式可以匹配在标号和内容之间包含空白字符的每一行。注意,空白字符和其他字符一样,也可以使用特殊的元字符,如星号和加号。

练习 9:匹配空白符

TaskTextResult
match1. abc
match2. abc
match3. abc
skip4.abc

我们要匹配的是标号和 "abc" 之间有空格的行,也就是前三行。我们可以使用表达式 \d.\s+abc 来匹配数字、实际句点 (必须转义)、一个或多个空白符,然后匹配文本。

如果我们用 * 代替 +,我们也会匹配到第四行,但实际上我们想跳过它。

课程 10:开始与结束

到目前为止,我们编写的正则表达式会在所有文本中匹配部分片段。有时这是不符合预期的,例如,我们想要在日志文件中匹配单词 "success"。我们当然不希望该模式与 "Error: unsuccessful operation!" 匹配。这就是为什么最好的做法是编写尽可能特定的正则表达式,以确保在与真实文本匹配时不会出现误报。

使模式更严格的一种方法是使用特殊的元字符 ^ (hat)$ (美元符号) 来描述一行的开始和结束。在上面的例子中,我们可以使用模式 ^success匹配以单词 "success" 开头的行,而不匹配 "Error: unsuccessful operation"。如果我们把 ^$ 结合起来,就能实现一个匹配从开头到结尾一整行的模式。

请注意,^ 和方括号 [^...] 中用于排除字符的 hat 不同,这在读取正则表达式时容易混淆。

尝试使用这些新的特殊字符来匹配下面的第一个字符串。

练习 10:匹配行

TaskTextResult
matchMission: successful
skipLast Mission: unsuccessful
skipNext Mission: successful upon capture of target

表达式 "Mission:successful" 会匹配文本中的任何位置,因此我们需要在表达式 ^Mission: successful$ 中使用开始和结束锚点,以便只匹配以 "Mission" 开头并以 "successful" 结尾的完整字符串。

课程 11:捕获组

正则表达式不仅允许我们匹配文本,还允许我们提取信息以便进一步处理。这是通过定义字符组 (groups of characters) 并使用特殊的圆括号 () 元字符捕获它们来实现的。一对括号内的任何子模式 (subpattern) 都将被捕获 (capture) 为一个组 (group)。实际上,它可以用来从各种数据中提取诸如电话号码或电子邮件之类的信息。

例如,假设您有一个命令行工具来列出云中的所有图像文件,您可以使用 ^(IMG\d+\.png)$ 等模式来捕获和提取完整的文件名。但如果只想捕获不带扩展名的文件名,您可以使用模式 ^(IMG\d+)\.png$,该模式只捕获句点之前的部分。

尝试使用圆括号来编写一个正则表达式,匹配下面 PDF 文件的文件名 (不包括扩展名)。

练习 11:捕获组

TaskTextCapture GroupsResult
capturefile_record_transcript.pdffile_record_transcript
capturefile_07241999.pdffile_07241999
skiptestfile_fake.pdf.tmp

我们只想捕获以 "file" 开始并具有文件扩展名 ".pdf" 的行,因此我们可以编写一个简单的模式来捕获从开头的 "file" 到扩展名之间的所有内容,比如 ^(file.+).pdf$。

课程 12:嵌套组

在处理复杂数据时,您经常会发现自己必须提取多层次的信息,这时就需要使用嵌套组 (nested groups)。通常,结果中的捕获组是按照它们被定义的顺序 (按开括号的顺序) 排列的。

以上一课中 "捕获所有图像文件的文件名" 为例。如果每个图像文件的文件名中都有一个连续的图片编号,我们可以通过编写 ^(IMG(\d+))\.png$ 之类的表达式 (使用嵌套的括号来捕获数字),使用同一个模式同时提取文件名和图片编号。

在模式中,嵌套组按照从左到右的左括号出现的顺序定义,第一个捕获组是第一个括号里的内容,以此类推。捕获的结果也按照这个顺序排列。

对于以下字符串,编写一个表达式,该表达式匹配并捕获完整日期和日期年份。

练习 12:匹配嵌套组

TaskTextCapture GroupsResult
captureJan 1987Jan 19871987
captureMay 1969May 19691969
captureAug 2011Aug 20112011

这个表达式需要捕获两部分数据,即年份和整个日期。这需要使用嵌套的捕获组,如表达式 (\w+ (\d+)) 所示。

我们也可以使用 \s+ 来代替空格,以捕获月份和年份之间任意数量的空格。

课程 13:关于分组的更多内容

正如您在前面的课程中看到的,所有的量词 (quantifiers) ——包括星号 \*、加号 +、重复 {m,n} 和问号 ? ——都可以在捕获组模式中使用。捕获组也是将量词应用于字符序列而不是单个字符的唯一方法。

例如,如果我们知道一个电话号码可能包含区号,也可能不包含区号,那么正确的模式是测试整个数字组是否存在 (\d{3})?,而不是测试单个字符本身 (这是错误的)。

根据您使用的正则表达式引擎,您还可以使用非捕获组 (non-capturing groups),这将允许您匹配该组,但不会让它显示在结果中。

下面是几种常见的显示器分辨率,请尝试捕获每个显示器的宽度和高度。

练习 13:匹配捕获组

TaskTextCapture GroupsResult
capture1280x7201280720
capture1920x160019201600
capture1024x7681024768

这个很简单,我们只需要捕获两组数字:(\d+)x(\d+)。

课程 14:条件

正如我们前面提到的,更准确总是好的,这适用于编码、对话、以及正则表达式。例如,您不会写一份购物清单让别人买更多的 .* (Buy more .*),因为您不知道您能得到什么。相反,您可以写买更多的牛奶 (Buy more milk) 或买更多的面包 (Buy more bread)。在正则表达式中,我们可以明确地定义这些条件 (conditionals)。

我们可以使用 | (逻辑或 locigal OR,也就是管道 pipe) 来表示可能的不同的字符集,尤其是在使用组的时候。在上面的示例中,我们可以编写模式 Buy more (milk|bread|juice) 来匹配字符串 Buy more milk、Buy more bread 或 Buy more juice。

与正常的组一样,您可以在条件 (condition) 中使用任何字符或元字符序列,例如,([cb]ats*|[dh]ogs?) 将要么匹配 cats 或 bats,要么匹配 dogs 或 hogs。编写具有许多条件的模式可能很难阅读,因此如果它们太复杂,您应该考虑将它们拆分为单独的模式。

继续尝试写一个条件模式 (conditional pattern),只匹配下面包含小动物的行。

练习 14:匹配条件文本

TaskTextResult
matchI love cats
matchI love dogs
skipI love logs
skipI love cogs

通过使用逻辑或,我们可以使用表达式 I love (cats|dogs) 来匹配前两行。

课程 15:其他元字符

这一小节将介绍一些其他的元字符,以及捕获组的结果。

我们已经学习了最常见的元字符:数字 \d、空白符 \s、字母和数字 \w,但是正则表达式还提供了一种捕获每个元字符的相反集合的方法,即使用大写字母。例如,\D 表示任何非数字字符,\S 表示任何非空白字符,\W 表示任何非字母或数字字符 (如标点符号)。灵活使用这些元字符,会使编写正则表达式更容易

此外,还有一个特殊的元字符 \b,它匹配单词和非单词字符之间的边界。它在捕获整个单词时最有用,例如模式 \w+\b

在这些课程中,我们不会详细探讨的一个概念是反向引用 (back referencing) (译者注:见反向引用),这主要是因为它在不同的实现里有所区别。但是,许多系统都允许您通过使用 \0 (通常是完全匹配的文本)、\1 (组 1)、\2 (组 2) 等来引用捕获的组。这在某些情况下很有用,举个例子,当您需要使用正则表达式搜索文本中的两个数字并交换它们的位置时,可以先使用模式 (\d+)-(\d+) 来搜索,再使用 \2-\1 来替换。下面是一段 JavaScript 代码示例:

"123-456".replace(
  /(\d+)-(\d+)/, // searchValue
  "$2-$1" // replaceValue, $1、$2 等价于上文的 \1、\2
) 
// Output => "456-123"

下面是一些不同的字符串,请尝试练习我们在前面的课程中学到的各种类型的元字符,并观察它们所匹配到的内容。

练习 15:匹配其他特殊字符

TaskTextResult
matchThe quick brown fox jumps over the lazy dog.
matchThere were 614 instances of students getting 90.0% or above.
matchThe FCC had to censor the network for saying &$#*@!.

这节课的练习更像是一个沙盒,提供了一些示例文本。最简单的答案可以是 .* :)

译者注:试试看 \w+\b,3+

课程 X:无限与超越!

恭喜你完成了所有课程!我们希望它们能让你对正则表达式有更多的经验。

正则表达式中还有一些我们还没有研究过的主题,比如反向引用、贪婪表达式与非贪婪表达式、posix 表示法等等。我们将在以后的课程中详细描述这些内容。

这里是一些有用的正则表达式学习资源:

(opens new window)

正则表达式状态图可视化工具

(opens new window)

在线正则表达式测试工具

如果您对如何改进网站有任何疑问或建议,请随时通过 Github Issue

(opens new window) 或电子邮件 (elonzzz@163.com) 联系!

现在,请继续练习后续的问题,学习如何将正则表达式应用到实际场景中。

​ ← 课程 15:其他元字符

资源

正则表达式在线测试:

正则表达式可视化工具:

问题 1:匹配十进制数字

乍一看,写一个正则表达式来匹配一个数字应该很容易吧?

我们可以用特殊字符 \d 来匹配任何数字,唯一要做的就是匹配小数点。真的是这样吗?对于简单的数字来说,这没有问题,但是对于科学或金融数字来说,我们经常需要处理正数和负数有效数字指数,甚至不同的表示法 (比如用来分隔千和百万的逗号)。

下面是您可能遇到的几种不同格式的数字。请注意您是如何匹配小数点本身的。如果无法顺利跳过最后一个数字,请观察该数字和其他数字的末尾的区别。

练习 1:匹配数字

TaskTextResult
match3.14529
match-255.34
match128
match1.9e10
match123,340.00
skip720p

当我们考虑金融数字、指数等形式时,表达式可能相当复杂。

对于上面的例子,表达式 ^-?\d+(,\d+)*(.\d+(e\d+)?)?$ 可以匹配这样的字符串:以一个可选的负号开头、一位或几位数字、可选的逗号和更多位数字、可选的小数点与小数数字、可选的指数部分。

这不是唯一的解决方案,比如表达式 ^-?((\d+,)+)?\d.?\d(e\d+)?$ 也可以匹配这些数字。

(译者注) 让我们尝试一步步解决这个问题:

  1. 匹配一个可能是负数的整数:^-?\d+
  2. 匹配小数:^-?\d+(.\d+)?
  3. 整数部分可能使用了千位分隔符:^-?\d+(,\d+)*(.\d+)?
  4. 匹配科学计数法:^-?\d+(,\d+)*(.\d+)?(e\d+)?$

问题 2:匹配电话号码

验证电话号码是另一项棘手的任务。有的电话号码需要区号,有的国际号码还需要前缀,这都增加了正则表达式的复杂性。此外,人们输入电话号码时的偏好也不同 (例如,有些人会输入破折号空格,而有些人不输入)。

下面是一些真实场景下可能遇到的电话号码,请编写一个与号码匹配的正则表达式,并捕获正确的区号。

练习 2:匹配电话号码

TaskTextCapture GroupsResult
capture415-555-1234415Success
capture650-555-2345650Success
capture(416)555-3456416Success
capture202 555 4567202Success
capture4035555678403Success
capture1 416 555 9292416Success

观察可以发现,电话号码由 3、3、4 位数字组成,每部分之间可以用空格或破折号连接,前三位数字是区号。因此,为了获取电话号码的区号,最简单的方法是使用表达式 (\d{3}) 捕获前三位数字。

为了同时匹配到整个电话号码,我们可以使用表达式 1?[\s-]?(?(\d{3}))?[\s-]?\d{3}[\s-]?\d{4}。解释如下:

  1. 匹配可能存在的国家号:1?[\s-]?
  2. 匹配可能被括号包住的区号:(?(\d{3}))?,注意这里外层的括号是转义后的字符,内层的括号表示捕获组
  3. 匹配后续的 3 位、4 位号码:[\s-]?\d{3}[\s-]?\d{4}
  4. [\s-]? 表示可能通过破折号或空格连接,也可能没有连接符

问题 3:匹配邮箱

正则表达式在验证 HTML 表单的输入时通常很有用。由于规范 (opens new window)的复杂性,电子邮件很难正确匹配,因此我建议使用内置语言或框架函数,而不是自己处理。但是,您依然可以使用我们迄今所学的知识,非常轻松地创建一个相当健壮的正则表达式,它能够匹配大量常见的电子邮件。

需要注意的一点是,许多人使用一次性的 加号地址 (opens new window)(plus-addressing),例如 "name+filter@gmail.com",邮件还是会发送到 "name@gmail.com",但是可以作一些过滤操作。此外,有些域名有不止一个部分,例如,您可以在 "hellokitty.hk.com" 注册域名,并使用一个形如 "ilove@hellokitty.hk.com" 的邮箱,因此在匹配电子邮件的域名部分时必须小心。

下面是一些常见的电子邮箱,请尝试捕获电子邮件的名称,不包括筛选器 (+ 字符和之后) 和域 (@ 字符和之后) 部分。

练习 3:匹配邮箱

TaskTextCapture GroupsResult
capturetom@hogwarts.comtom
capturetom.riddle@hogwarts.comtom.riddle
capturetom.riddle+regexone@hogwarts.comtom.riddle
capturetom@hogwarts.eu.comtom
capturepotter@hogwarts.compotter
captureharry@hogwarts.comharry
capturehermione+regexone@hogwarts.comhermione

要提取每封电子邮件的开头,我们可以使用一个简单的表达式 ^([\w.]*),它将匹配以字母数字字符 (包括句点) 开头的电子邮件,直到遇到 "@" 或 "+"。

再次强调,您应该使用一个框架来匹配电子邮件!

问题 4:匹配 HTML

如果您正在寻找一种健壮的方法来解析 HTML,正则表达式通常不是好的解决方案,因为现在互联网上的 HTML 页面非常脆弱 —— 常见的错误如缺少结束标签、标签不匹配、属性引号没有关闭等,都会使一个非常好的正则表达式无法使用。相反,您可以使用像 Beautiful Soup (opens new window)html5lib (opens new window) (都是 Python) 或 phpQuery (opens new window) (PHP) 这样的库,它们不仅可以解析 HTML,还可以让您快速、轻松地访问 DOM。

尽管如此,您可能经常需要在编辑器中快速匹配标签和标签的内容。如果您可以保证输入规范,正则表达式就是一个很好的工具。

继续为下面的示例编写正则表达式来捕获外层标签,请注意属性中的转义引号和嵌套的标签。

练习 4:匹配 HTML 标签

TaskTextCapture GroupsResult
captureThis is a linkaSuccess
captureLinkaSuccess
capture
Test
divSuccess
capture
Hello world
divSuccess

最佳做法是使用合适的库来解析 html,但如果只是查找简单地标签名,可以使用表达式 <(\w+)。

还可以使用表达式 >([\w\s])< 捕获标签内容,甚至可以捕获属性值 ='([\w://.])。

问题 5:匹配特定文件名

如果您经常使用 Linux 或命令行,往往会需要处理文件列表。大多数文件都由一个文件名和一个扩展名组成,但在 Linux 中,隐藏的文件没有文件名也是很常见的。

在下面这个简单的示例中,请提取出图像文件的文件名和扩展名 (不包括当前正在编辑的图像的临时文件 .tmp)。图像文件定义为 .jpg.png.gif

练习 5:捕获文件名

TaskTextCapture GroupsResult
skip.bash_profile
skipworkspace.doc
captureimg0912.jpgimg0912jpgFailed
captureupdated_img0912.pngupdated_img0912pngFailed
skipdocumentation.html
capturefavicon.giffavicongifFailed
skipimg0912.jpg.tmp Failed
skipaccess.lock

我们只查找以 "jpg"、"png" 和 "gif" 扩展名结尾的图像文件,因此可以使用表达式 (\w+).(jpg|png|gif)$ 捕获所有此类文件名。

问题 6:修剪掉行首和行尾的空白

有时,您会发现自己的日志文件的空格格式错误,导致某些行缩进太多或者不够。解决这个问题的一种方法是使用编辑器的搜索功能,通过正则表达式来提取行中不包含额外空格的内容。

我们之前已经看到了如何分别使用帽子符号 (hat) ^ 和美元符号 $ 匹配一整行文本。当这两个符号与空格 \s 一起使用时,可以轻松跳过所有前面和后面的空格。

写一个简单的正则表达式来捕获每一行的内容,不包含额外的空格。

练习 6:匹配行

TaskTextCapture GroupsResult
captureThe quick brown fox...The quick brown fox...Success
capturejumps over the lazy dog.jumps over the lazy dog.Success

可以使用表达式 ^\s(.)\s*$ 跳过所有开始和结束的空格,只捕获内容。

问题 7:从日志文件中提取信息

在本例中,我们的目标是使用目前所学的任何正则表达式技术,从 Android adb 调试会话的实际输出日志中提取文件名、方法名、堆栈跟踪行的行号。它们在日志中的格式为:

at package.class.methodname(filename:linenumber)

练习 7:从日志条目中提取数据

TaskTextCapture GroupsResult
skipW/dalvikvm( 1553): threadid=1: uncaught exception
skipE/( 1553): FATAL EXCEPTION: main
skipE/( 1553): java.lang.StringIndexOutOfBoundsException
captureE/( 1553): at widget.List.makeView(ListView.java:1727)makeViewListView.java1727Success
captureE/( 1553): at widget.List.fillDown(ListView.java:652)fillDownListView.java652Success
captureE/( 1553): at widget.List.fillFrom(ListView.java:709)fillFromListView.java709Success

我们只想捕获方法名、文件名和行号,这可以使用表达式 (\w+)(([\w.]+):(\d+)) 来实现,其中第一个捕获组是方法名,之后是转义的括号、文件名、冒号,最后是行号。

译者注:试试 at \w.\w.([\w.]+)、(([\w.]+)、:(\d+)

问题 8:从 URL 中解析和提取数据

在网络上处理文件和资源时,我们经常会遇到可以直接解析和使用的 URIs 和 URLs。大多数标准库都提供了用来解析和构造这类标识符的类,但是如果您需要在日志文件或更大的文本语料库中匹配它们,那么使用正则表达式可以很容易地从它们的结构化形式中提取信息。

URIs (统一资源标识符,Uniform Resource Identifiers),是资源的一种表示方法,通常包括 scheme (方案名 (opens new window)),host (主机名),port (端口,可选的) 和 resource path (资源路径),如下所示:

http://regexone.com:80/page

Scheme 描述了用于通信的协议 (protocol),host 和 port 描述了资源的来源,path 描述了资源所在的位置。

在下面的练习中,尝试提取出的所有资源的协议、主机和端口。

练习 8:从 URL 中提取数据

TaskTextCapture GroupsResult
captureftp://file_server.com:21/top_secret/life_changing_plans.pdfftpfile_server.com21
capturehttps://regexone.com/lesson/introduction#sectionhttpsregexone.com
capturefile://localhost:4040/zip_filefilelocalhost4040
capturehttps://s3cur3-server.com:9999/httpss3cur3-server.com9999
capturemarket://search/angry%20birdsmarketsearch

我们必须分别匹配这三个部分:

  • 列表中的协议都是字母组成的,所以可以使用 (\w+):// 匹配
  • 主机名可以包含非字母数字字符,如破折号或句点,因此可以使用 ://([\w-.]+) 匹配;也可以使用 ://(4+) 匹配
  • 端口是 URI 的可选部分,前面有冒号,可以使用 (:(\d+)) 匹配

最终的完整表达式为:(\w+)://([\w-.]+)(:(\d+))?,或者 (\w+)://(4+)(:(\d+))?。

问题 X:无限与超越!

恭喜你解决了所有的问题!要了解如何在一些常用编程语言中使用正则表达式,请继续阅读下列文章。我们希望您可以开始在日常工作中应用正则表达式!

如果您对如何改进网站有任何疑问或建议,请随时通过 Github Issue (opens new window) 或电子邮件 (elonzzz@163.com) 联系!

课程 A1:反向引用

说明

进阶课程是译者针对 regexone.com (opens new window) 课程中没有涉及的内容所作的补充。

反向引用 (back referencing),或者回溯引用,是指通过 \0\1\2 这样的变量形式来引用模式中已经匹配到的部分。\0 表示整个模式匹配到的文本,\1 表示捕获的第一个组、\2 表示捕获的第二个组,以此类推。

在模式中,嵌套组按照从左到右左括号出现的顺序定义,第一个捕获组是第一个括号里的内容,以此类推。捕获的结果也按照这个顺序排列。见课程 12:嵌套组

回溯引用在替换字符串时十分常用。比如在使用正则表达式搜索文本中的两个数字并交换它们的位置时,可以先使用模式 (\d+)-(\d+) 来搜索,再使用 \2-\1 来替换。下面是 JavaScript 中的代码示例:

"123-456".replace(
  /(\d+)-(\d+)/, // searchValue
  "$2-$1" // replaceValue, $1、$2 等价于上文的 \1、\2
) 
// Output => "456-123"

或者,当我们需要在文本中匹配两个连续的相同单词时,也可以使用反向引用。下面是一段 JS 代码示例,用于删除字符串中的连续的重复单词:

"hello hello world world".replace(
  /(\w+) \1/g, // g 表示全局匹配,即匹配整个字符串中满足该模式的所有子串,而不仅是第一个
  "$1"
)
// Output => "hello world"

请尝试完成下面的练习:

练习 A1:匹配两个连续的相同单词

TaskTextCapture GroupsResult
capturehello hello worldhello hello

使用模式 ((\w+) \2),其中 \2 表示从左到右第二个括号 (\w+) 捕获到的单词。

课程 A2:零宽断言

零宽断言用于指定要匹配的内容的前缀或后缀应该满足的约束条件,匹配到的内容并不包含这些前缀 / 后缀。“零宽”的含义是没有宽度、只匹配位置、不匹配内容;“断言”用来声明一个应该为真的事实,只有断言为真时正则表达式才会匹配。

下面是正则表达式中的四种零宽断言:

语法作用名称
(?=exp)指定后缀零宽度正预测先行断言 zero-width positive lookahead assertion
(?<=exp)指定前缀零宽度正回顾后发断言 zero-width positive lookbehind assertion
(?!exp)指定后缀不能是零宽度负预测先行断言 zero-width negative lookahead assertion
(?<!exp)指定前缀不能是零宽度负回顾后发断言 zero-width negative lookbehind assertion

例如,模式 \w+(?=ing) 会匹配所有以 ing 结尾的单词,模式 (?<=un)\w+ 会匹配所有以 un 开头的单词。注意:断言左右两侧的括号是必须的

上面这四个语法可以这样理解:

  • = 表示“是”、! 表示“不是”
  • < 是一个向左的箭头,表示表达式的左面,也就是前缀

我们不需要记住每个语法的具体名称,只要知道怎么用就可以。请尝试完成下面的练习。

练习 A2-1:匹配后缀是 "ly" 的单词

TaskTextResult
skipsad
matchslightly
matchhardly
matchperfectly
matchsuddenly

最简单的方法是使用模式 \w+ly来匹配所有以 ly 结尾的单词。我们也可以使用 \w+(?=ly),这个模式匹配的内容并不包含 ly。请注意对比这两个模式的匹配结果的区别。

​ 解决上述任务以继续下一个问题,或者查看答案。

练习 A2-2:匹配前缀是 "un" 的单词

TaskTextResult
skiphappy
matchunfinished
matchundoubted
matchunused
matchunhappy

可以使用模式 un\w+ 或 (?<=un)\w+ 来匹配所有以 un 开头的单词。请注意对比这两个模式的匹配结果的区别。

​ 解决上述任务以继续下一个问题,或者查看答案。

练习 A2-3:只匹配前两行的 "re"

TaskTextResult
matchrepresents
matchreclaim
skipregex
skipregular

可以使用模式 re(?!g),该模式限定了 re 右边不能是字符 g

​ 解决上述任务以继续下一个问题,或者查看答案。

练习 A2-4:只匹配第一行的 "happy"

TaskTextResult
matchhappy
skipunhappy

可以使用模式 (?<!un)happy,该模式限定了 happy 左边不能是前缀 un

课程 A3:非捕获组

课程 11:捕获组课程 A1:反向引用中,我们已经学习到:一对括号内的任何子模式都将被捕获为一个组,每个组按照从左到右的左括号出现的顺序,编号从 1 开始递增。

但是某些情况下,我们并不需要使用捕获组的内容,这个时候可以使用非捕获组 (non-capturing groups)。非捕获组的语法是在捕获组的基础上,在左括号的右侧加上 ?:,即 (?:exp)。使用非捕获组可以节省内存、提升效率。

在下面这段 JavaScript 代码中,我们使用了非捕获组,反向引用 $1 / $2 并没有与之对应的捕获组,在这里相当于纯字符串

"123-456".replace(
  /(?:\d+)-(?:\d+)/, // searchValue
  "$2-$1" // replaceValue, $1、$2 等价于反向引用 \1、\2
) 
// Output => "$2-$1"

请完成下面的练习,要求使用条件语法匹配前两行的动物,但不能生成捕获组。

练习 A3:非捕获组

TaskTextCapture GroupsResult
matchI love catscats
matchI love dogsdogs
skipI love logs
skipI love cogs

如果使用模式 I love (cats|dogs),会同时捕获到 "cats"、"dogs"。改为 I love (?:cats|dogs) 就可以避免生成捕获组。

课程 A4:贪婪 vs 懒惰

当正则表达式中包含 *+? 等表示重复的元字符时,默认会匹配尽可能多的字符,这被称为贪婪匹配。例如给定字符串 abcab,模式 a.*b 会匹配最长的以 a 开始、以 b 结束的字符串,也就是匹配到整个字符串而不是 ab

与贪婪匹配相反的是非贪婪匹配,或者称为懒惰匹配,也就是匹配尽可能少的字符。语法是在上述元字符后面加上一个问号 ?。在上面的例子中,模式 a.*?b 将匹配到 ab

所有表示重复的元字符都可以转化为懒惰匹配模式:

  • *?:重复任意次,但尽可能少重复
  • +?:重复 1 次或更多次,但尽可能少重复
  • ??:重复 0 次或 1 次,但尽可能少重复
  • {n,m}?:重复 n 到 m 次,但尽可能少重复
  • {n,}?:重复 n 次或更多次,但尽可能少重复

在下面的练习中,请尝试修改模式 (\d+)0*$,使其捕获不包含 0 的数字前缀。

练习 A4:懒惰匹配

TaskTextCapture GroupsResult
capture123000123
capture1010101
capture789789

默认情况下,正则表达式是贪婪匹配,所以 \d+ 总是尽可能多地向后匹配,将后面的 0 也包含进来。将模式改为非贪婪匹配:(\d+?)0*$,就可以让 \d+ 尽可能少匹配,让 0* 尽可能多匹配。

课程 AX:无限与超越!

恭喜你完成了所有进阶课程!

如果您对如何改进网站有任何疑问或建议,或者需要补充其他有关正则表达式的内容,请随时通过 Github Issue (opens new window) 或电子邮件 (elonzzz@163.com) 联系!

我的个人博客:http://imageslr.com (opens new window)
我的 Github 主页:https://github.com/imageslr (opens new window)

最后,希望这些课程对你有所帮助!


  1. drp
  2. b
  3. \d\s\w
  4. /:
0

评论 (0)

取消