发布于 2026-01-06 0 阅读
0

(别害怕)正则表达式:正则表达式实用入门

(别害怕)正则表达式:正则表达式实用入门

你以前用过字符串吗?没错,就是我们都熟悉且喜爱的“字符数组”?除非你只用 C 语言编程,否则我敢打赌你肯定用过——甚至可能用过很多次。

但如果使用大量字符串呢?或者使用程序未生成的字符串呢?也许你在读取电子邮件、解析命令行参数或阅读人类编写的指令,而你需要一种更结构化的方法来处理这些内容。

当然,你可以遍历字符串中的每个单词或元素。而且你可能也能理解你用来编写这段代码的方法。但对于大型应用程序来说,这会变得非常繁琐,而且维护成本也很高

输入:正则表达式

我们不深入探讨计算机科学,让我们快速定义一下正则表达式。

  • 正则表达式是描述正则语言的语法。
  • 正则语言是一种形式语言,可以用有限状态机来描述。

网上有很多关于常规语言的更好解释,所以如果你还不满意,只需花几分钟在谷歌上搜索一下即可。

输入:正则表达式

如果还没发生的话,接下来就有点奇怪了……我要区分编程语言中所谓的“正则表达式”和计算机科学中所谓的正则表达式。

  • 计算机科学正则表达式——一种描述正则语言的语法
  • 编程语言正则表达式——一种至多用于描述上下文相关语言的语法

上下文相关的语言要复杂得多,也更有用,所以我们现在将编程语言正则表达式称为“regex”,以巩固其语言并非规则这一区别。

学习编写正则表达式

正则表达式用双引号(//)括起来,如果字符串符合双引号之间定义的“模式”,则匹配该字符串。例如,`\{ /Hi/"Hi"}` 匹配“Hi”,因此我们可以使用正则表达式检查字符串是否为“Hi”(或者是否包含“Hi”,稍后会详细介绍)。

我们通过正常输入正则表达式来匹配字符串中的字符。例如,`?`/Hello World/将匹配字符串“Hello World”。

我们可以通过添加一些正则表达式魔法来简化它,使其匹配任何\w单词:匹配任何仅由字母组成的“单词”:\w将匹配任何一个单词(如果只有字母)。

我们也可以类似地将数字与……匹配\d

例 1

太好了,现在我们可以进行字符串相等性判断或者检查字符串是否符合一些简单的模式了。那又怎样呢?它们还能有更多用途吗?

当然可以!假设我们写了一个 IRC 聊天机器人,专门监听有人说“Josh”。我们的机器人会扫描频道里每条消息,直到找到匹配项。然后,机器人会回复:“哇哦,希望你没在说我朋友 Josh 的坏话!” 因为 Josh 的朋友只有机器人。

...

我们的机器人将使用该模式/Josh/来匹配字符串。

突然,一个名叫伊莱的人跌跌撞撞地走了过来:“伊莱:乔希,你真的需要那么多咖啡因吗?”

我们的机器人会立即启动,扫描这条信息/Josh/,并找到一个匹配项!于是他回复了,伊莱被吓得不轻。任务完成!

真的是这样吗?

如果我们的机器人更智能呢?如果机器人能叫出说话者的名字呢?比如说:“哇,伊莱,我希望你没有在说我朋友乔什的坏话。”

量词(重复字符)

0 个或多个

我们可以做到……但我们需要学习一些东西才能实现。首先是量词(用于重复字符)。

我们可以使用 * 来匹配它前面的0 个或多个字符。例如,*/a*/可以匹配“aaaaa” ,但也可以匹配空字符串“”。没错,它也会匹配字符串。

* 用于匹配可选内容,因为它匹配的字符不一定存在。但它可以存在。而且它可以存在很多很多次(理论上是无限多次)。

我们可以将“Josh”与匹配/Josh/,但我们也可以将“JJJJJJJJJosh”和“osh”与匹配/J*osh/

1 或多个

+ 可以用来匹配一个或多个字符。它的工作方式与 * 基本相同,区别在于字符的存在与否不再是可选的。现在必须至少有一个字符才能匹配。

所以,我们可以将“JJJJosh”和“Josh”匹配/J+osh/,但不能将“osh”匹配。

外卡

太好了,现在我们可以匹配更多有趣的功能了。说不定有人真生我的气,还会大喊“乔什”呢……

但如果他们气得把脸砸在键盘上怎么办?如果我们不知道他们的鼻子有多尖,我们该如何匹配“afuhiudgosigs”呢?

通配符

通配符可以匹配任何内容。它们的语法是.`.`(是的,就是一个句点。句点。)。你可能会经常用到它,所以不要把它和匹配句子的结尾混淆。

我们可以利用这个正则表达式来匹配“Joooafhuaisggsh”,方法是结合我们对重复字符和通配符的了解:/Jo+.*sh/。具体来说,这个正则表达式会匹配 1 个“J”、1 个或多个“o”、0 个或多个通配符、1 个“s”和 1 个“h”。这五个部分引导我们得出我们所说的……

角色组

字符组是指字符串中按顺序出现的字符块。当你使用 `\n`*或 `\s` 时+,实际上匹配的是最后一个字符组中的多个字符,而不仅仅是最后一个字符

理解这一点本身就很有用,但如果结合重复字符,效果会更强大。为此,我们可以使用括号定义自己的字符组(就是这些字符)。

假设我们想重复匹配“Jos”,但排除“h”。那么“JosJosJosJosJosh”就能匹配。我们可以用正则表达式来实现/(Jos)+h/,很简单,对吧?

但最后……回到我们的例子,我们如何才能在 Eli 发送的 IRC 聊天消息中获取他的名字呢?

字符组也是记忆字符串片段的一种方法。这样,当我们在程序代码中遇到符合某种模式的字符串时,就可以将字符串的某些部分添加到变量中。

为此,通常你需要执行类似\1匹配第一个指定组的操作。

例如,/(.+) \1/这是一个特殊的正则表达式。它检查一组随机字符出现一次或多次,之后加一个空格,然后再重复完全相同的字符。因此,这个正则表达式会匹配字符串“abc abc”,但不会匹配“abc def”,即使(.*)单独来看,“def”是可以匹配的。

记住匹配项非常强大,它很可能成为使用正则表达式编程时最有用的功能。

例 2

呼……终于可以继续我们的IRC机器人项目了。让我们运用所学知识,看看是谁在背后说人坏话。

如果我们想在对方说“Josh”时捕获对方的名字,我们的正则表达式可以这样写:/(\w+): .*Josh.*/我们可以将匹配结果保存为编程语言中的一个变量,以便在回复中使用。

那只是 1 个或多个字母,后面跟着“:”,一个表示 0 个或多个字符的通配符,字符串“Josh”,以及一个表示 0 个或多个字符的通配符。

注意/.*word.*/这是一种简单的匹配包含“单词”的字符串的方法,该字符串可能包含也可能不包含其他内容。

在 Python 中,该正则表达式可能如下所示:

import re
pattern = re.compile(ur'(\w+): .*Josh.*')  # Our regex
string = u"Eli: Josh go move your laundry" # Our string

matches = re.match(pattern, string)        # Test the string

who = matches.group(1)                     # Get who said the message
print(who)                                 # "Eli"
Enter fullscreen mode Exit fullscreen mode

注意,我们使用的方式与在正则表达式模式中.group(1)使用的方式完全相同\1。除了在 Python 中使用正则表达式之外,这里没有什么新内容。

始末

到目前为止,我们实际上允许匹配的字符串出现在字符串的任何位置。例如,`stringg`将匹配任何/(Jos)+h/包含重复字母 `h` 的字符串

如果我们想让字符串以 Jos-repeating-h 开头呢?我们可以使用 `--repeating-h` 来指定/^(Jos)+h/,其中 ` ^--repeating-h` 匹配字符串的开头。

类似地,$也可以用来匹配字符串的结尾。

因此,如果我们希望我们的模式匹配从头到尾包含 Jos-repeating-h 的字符串,我们可以将其修改为如下所示:/^(Jos)+h$/

角色选项

但也许你正在为一份三明治订单编写正则表达式。你不知道顾客想要白面包还是全麦面包,但两者都接受。如何在正则表达式中添加选择呢?使用字符选项

字符选项允许您为一组值指定可能的值。(white|wheat)在我们的三明治示例中,语法示例为“white”(白面包)或“wheat”(小麦面包)。

您还可以使用[brackets]另一种方式来指定选项。这里每个字符都是一个选项,而不是整个字符串。例如,“b”、“r”、“s”、“t”、“e”、“k”、“c”、“r”都可以单独接受。但这对于更复杂的字符组非常有用,因为您可以在这里用字符组内的更复杂的表达式来替换其他字符。

修饰符

我们用逗号来讨论正则表达式/slash marks/,对吧?我们知道中间部分应该放什么,但是两侧应该放什么呢?

剧情反转?什么也没有。

……放在左边。不过,右边有一些非常非常有用的东西。我们竟然忽略了它这么久,真是太可惜了!

修饰符用于修改应用正则表达式的规则。

以下是常用修饰符列表(来自Regex101.com):

修饰符 昵称 描述
全球的 所有比赛(不包括第一场比赛)
多行 使 ^ 和 $ 匹配每行的开头/结尾(而不仅仅是字符串的开头/结尾)。
冷漠无情 不区分大小写匹配(忽略[a-zA-Z]的大小写)
x 扩展 模式中 # 符号后的空格和文本将被忽略。
X 额外的 反斜杠 (\) 后跟一个没有特殊含义的字母是错误的。
s 单线 点匹配换行符
Unicode 模式字符串被视为 UTF-16 编码。此外,转义序列也会匹配 Unicode 字符。
U 不贪婪 默认情况下,匹配操作会采用惰性匹配。现在,如果?后面跟着量词,则会采用贪婪匹配。
一个 锚定 模式被强制^
J 复制 允许重复的子模式名称

例如,到目前为止,我们所有的示例都是区分大小写的。这意味着,将任何一个字符大写或小写都会使该字符串不再符合模式。我们可以使用修饰符使模式不区分大小写i

也许伊莱气得在聊天室里狂发一堆大小写混杂的信息。别担心,i它来了!我们可以用它来回应他“我讨厌和乔什住在一起!!!”的怒火/i ha+te living with josh!+/i。现在它更容易阅读,也更强大、更实用。太棒了!

剩下的修饰符就留给你们自己去尝试吧,但我敢打赌,你会发现igm它是你最常用的。

接下来是什么?

希望这篇文章能向您展示另一种更智能地处理字符串的实用方法。我仅仅触及了正则表达式的皮毛,但您现在应该已经知道如何使用正则表达式完成一些简单的任务了。

正则表达式中可以使用的符号/标记数量庞大。通常情况下,你会通过 Stack Overflow 搜索找到它们,或者根据以往经验猜测(例如,\n 是换行符)。你现在基本掌握了所需的知识,但还有很多东西需要学习。

你可以在这里获取完整的标记列表并全面测试你的正则表达式。我几乎每次编写正则表达式时都会使用这个网站,因为它的测试工具非常实用且功能强大。如果你还不确定如何在你的编程语言中实现,它甚至可以为你生成代码。

如果你觉得这很简单,那就去试试正则表达式填字游戏吧。它们会让你真正开始思考正则表达式!

文章来源:https://dev.to/hawkinjs/dont-fear-the-regex-a-practical-introduction-to-regular-expressions