正则表达式(Regex)完全指南

2025-05-24

正则表达式(Regex)完全指南

正则表达式(简称 regex)是一种允许你匹配具有特定模式的字符串的语法。你可以将其视为一种高级的文本搜索快捷方式,但正则表达式还增加了使用量词、模式集合、特殊字符和捕获组来创建极其高级的搜索模式的功能。
你可以在任何需要查询基于字符串的数据时使用正则表达式,例如:

  • 分析命令行输出
  • 解析用户输入
  • 检查服务器或程序日志
  • 处理具有一致语法的文本文件,例如 CSV
  • 读取配置文件
  • 搜索和重构代码

虽然理论上无需正则表达式即可完成所有这些任务,但当正则表达式出现时,它们就成为完成所有这些任务的超能力。

在本指南中我们将介绍:

下载我们的正则表达式速查表

正则表达式是什么样的?

在其最简单的形式中,正则表达式的使用可能看起来像这样:

我们使用正则表达式 /Test/ 来查找单词

此截图来自regex101 网站。以后所有截图都将以此网站为参考。

在“Test”示例中,字母 test 构成了搜索模式,与简单搜索相同。
然而,这些正则表达式并不总是如此简单。以下正则表达式匹配 3 个数字,后跟一个“-”,再后跟 3 个数字,再后跟一个“-”,最后以 4 个数字结尾。

你知道,就像电话号码一样:

^(?:\d{3}-){2}\d{4}$
Enter fullscreen mode Exit fullscreen mode

电话号码

这个正则表达式可能看起来很复杂,但要记住两点:

  1. 我们将在本文中教你如何阅读和编写这些内容
  2. 这是编写该正则表达式的相当复杂的方法。

事实上,大多数正则表达式都可以用多种方式编写,就像其他形式的编程一样。例如,上面的代码可以改写成一个更长但更易读的版本:

^[0-9]{3}-[0-9]{3}-[0-9]{4}$
Enter fullscreen mode Exit fullscreen mode

大多数语言都提供了使用正则表达式搜索和替换字符串的内置方法。然而,每种语言可能根据其自身需求,拥有不同的语法。

在本文中,我们将重点介绍 Regex 的 ECMAScript 变体,它在 JavaScript 中使用,并且与其他语言的正则表达式实现有很多共同之处。

如何读取(和编写)正则表达式

量词

正则表达式量词检查应该搜索某个字符的次数。

以下是所有量词的列表:

  • a|b- 匹配“a”或“b”
  • ?- 零个或一个
  • +- 一个或多个
  • *- 零个或多个
  • {N}- 恰好 N 次(其中 N 是一个数字)
  • {N,}- N 次或更多次(其中 N 是一个数字)
  • {N,M}- N 到 M 次之间(其中 N 和 M 为数字,且 N < M)
  • *?- 零个或多个,但第一个匹配后停止

例如,以下正则表达式:

Hello|Goodbye
Enter fullscreen mode Exit fullscreen mode

匹配字符串“Hello”和“Goodbye”。

同时:

Hey?
Enter fullscreen mode Exit fullscreen mode

将跟踪“y”从零到一次,因此将与“He”和“Hey”匹配。

或者:

Hello{1,3}
Enter fullscreen mode Exit fullscreen mode

将匹配“Hello”、“Helloo”、“Hellooo”,但不会匹配“Helloooo”,因为它正在寻找 1 到 3 次之间的字母“o”。

这些甚至可以相互结合:

He?llo{2}
Enter fullscreen mode Exit fullscreen mode

这里我们寻找的是“e”和字母“o”乘以 2 的零到一个实例的字符串,因此这将匹配“Helloo”和“Hlloo”。

贪婪匹配

我们在上一个列表中提到的正则表达式量词之一是+符号。该符号匹配一个或多个字符。这意味着:

Hi+
Enter fullscreen mode Exit fullscreen mode

将匹配从“Hi”到“Hiiiiiiiiiiiiiiii”的所有内容。这是因为所有量词默认都被视为“贪婪”。

但是,如果使用问号符号 ( ?) 将其更改为“懒惰”,则行为会发生变化。

Hi+?
Enter fullscreen mode Exit fullscreen mode

现在,i匹配器会尝试尽可能少地匹配。由于+图标表示“一个或多个”,它只会匹配一个“i”。这意味着,如果我们输入字符串“Hiiiiiiiiiiii”,则只会匹配“Hi”。

虽然它本身并不是特别有用,但当与诸如符号之类的更宽泛的匹配项结合使用时.,它就变得非常重要,我们将在下一节中介绍。.符号在正则表达式中用于查找“任意字符”。

现在如果你使用:

H.*llo
Enter fullscreen mode Exit fullscreen mode

您可以匹配从“Hillo”到“Hello”到“Hellollollo”的所有内容。

我们使用正则表达式 /H.*llo/ 来查找单词

但是,如果您只想匹配最后一个示例中的“Hello”,该怎么办?

好吧,只需使用 a 使搜索变得懒惰?,它就会按我们想要的方式工作:

H.*?llo
Enter fullscreen mode Exit fullscreen mode

我们使用正则表达式 /H.*?llo/ 来查找单词

图案集合

模式集合允许你搜索要匹配的字符集合。例如,使用以下正则表达式:

My favorite vowel is [aeiou]
Enter fullscreen mode Exit fullscreen mode

您可以匹配以下字符串:

My favorite vowel is a
My favorite vowel is e
My favorite vowel is i
My favorite vowel is o
My favorite vowel is u
Enter fullscreen mode Exit fullscreen mode

但没有别的了。

以下是最常见的图案集合列表:

  • [A-Z]- 匹配从“A”到“Z”的任意大写字符
  • [a-z]- 匹配从“a”到“z”的任意小写字符
  • [0-9]- 匹配任意数字
  • [asdf]- 匹配任意字符“a”、“s”、“d”或“f”
  • [^asdf]- 匹配不属于以下任何字符:“a”、“s”、“d”或“f”

您甚至可以将它们组合在一起:

  • [0-9A-Z]- 匹配任意数字或“A”至“Z”的大写字母
  • [^a-z]- 匹配任何非小写字母

通用代币

并非所有字符都如此容易识别。虽然像“a”到“z”这样的键可以用正则表达式匹配,但换行符呢?

“换行符”是您按“Enter”键添加新行时输入的字符。

  • .- 任何角色
  • \n- 换行符
  • \t- 制表符
  • \s- 任何空白字符(包括\t\n以及其他一些字符)
  • \S- 任何非空白字符
  • \w- 任何单词字符(大写和小写拉丁字母、数字 0-9 和_
  • \W- 任何非单词字符(标记的逆\w
  • \b\w- 单词边界:和之间的边界\W,但匹配中间的字符
  • \B- 非词边界:\b
  • ^- 一行的开头
  • $- 一行的结尾
  • \\- 文字字符“\”

因此,如果您想删除每个以新单词开头的字符,您可以使用类似以下正则表达式:

\s.
Enter fullscreen mode Exit fullscreen mode

并将结果替换为空字符串。执行此操作后,结果如下:

Hello world how are you
Enter fullscreen mode Exit fullscreen mode

变为:

Helloorldowreou
Enter fullscreen mode Exit fullscreen mode

我们使用正则表达式 /\s./ 来查找字符串中与以下字符一起出现的空格

与集合合并

不过,这些 token 本身并不那么有用!假设我们要删除任何大写字母或空格。当然,我们可以这样写

[A-Z]|\s
Enter fullscreen mode Exit fullscreen mode

但实际上我们可以将它们合并在一起并将我们的\s令牌放入集合中:

[A-Z\s]
Enter fullscreen mode Exit fullscreen mode

我们使用正则表达式 /[AZ\s]/ 来查找字符串中的大写字母和空格

词边界

在我们的标记列表中,我们提到了\b匹配单词边界。我想花点时间解释一下它与其他标记的作用有何不同。

给定一个像“This is a string”这样的字符串,你可能期望空格字符被匹配——然而事实并非如此。相反,它匹配的是字母和空格之间的字符:

我们使用单词边界正则表达式 /\b/ 来查找字符之间的空格

这可能有点难以理解,但简单地匹配单词边界并不常见。相反,你可能会使用类似下面的代码来匹配完整的单词:

\b\w+\b
Enter fullscreen mode Exit fullscreen mode

我们使用正则表达式 /\b\w+\b/ 来查找完整单词。在字符串中

您可以像这样解释该正则表达式:

“一个单词边界。然后,一个或多个‘单词’字符。最后,另一个单词边界”。

开始和结束线

我们讨论的另外两个标记是^$。它们分别标记一行的开始和结束。

因此,如果您想找到第一个单词,您可以执行以下操作:

^\w+
Enter fullscreen mode Exit fullscreen mode

匹配一个或多个“单词”字符,但仅限于紧接在行首之后的字符。记住,“单词”字符是指任何大小写拉丁字母、数字 0-9 以及 的字符_

正则表达式 /^\w+/ 匹配字符串中的第一个单词。在

同样,如果你想找到最后一个单词,你的正则表达式可能看起来像这样:

\w+$
Enter fullscreen mode Exit fullscreen mode

您可以使用 /\w+$/ 来匹配字符串中的最后一个单词。在

但是,仅仅因为这些标记通常结束一行并不意味着它们后面不能有字符。

例如,如果我们想找到换行符之间的每个空格字符来充当基本的JavaScript 压缩器,该怎么办?

好吧,我们可以使用以下正则表达式说“查找一行结束后的所有空格字符”:

$\s+
Enter fullscreen mode Exit fullscreen mode

我们可以使用 /$\s+/ 来查找字符串末尾和下一个字符串开头之间的所有空格。

字符转义

虽然标记非常有用,但在尝试匹配实际包含标记的字符串时,它们可能会带来一些复杂性。例如,假设您在博客文章中有以下字符串:

"The newline character is '\n'"
Enter fullscreen mode Exit fullscreen mode

或者,你想找出这篇博文中所有使用“\n”字符串的地方。你可以使用 转义字符\。这意味着你的正则表达式可能如下所示:

\\n
Enter fullscreen mode Exit fullscreen mode

如何使用正则表达式

然而,正则表达式不仅仅用于查找字符串。你还可以通过其他方法使用它们来修改或处理字符串。

虽然许多语言都有类似的方法,但我们以 JavaScript 为例。

使用正则表达式创建和搜索

首先,让我们看看正则表达式字符串是如何构造的。

在 JavaScript(以及许多其他语言)中,我们将正则表达式放在//块内。搜索小写字母的正则表达式如下所示:

/[a-z]/
Enter fullscreen mode Exit fullscreen mode

然后,此语法会生成一个 RegExp 对象,我们可以使用该对象和内置方法(例如exec)来匹配字符串。

/[a-z]/.exec("a"); // Returns ["a"]
/[a-z]/.exec("0"); // Returns null
Enter fullscreen mode Exit fullscreen mode

然后我们可以利用这个真实性来确定正则表达式是否匹配,就像我们在这个例子的第 3 行中所做的那样:

在代码沙箱中运行相关的代码示例

我们也可以使用RegExp想要转换为正则表达式的字符串来调用构造函数:

const regex = new RegExp("[a-z]"); // Same as /[a-z]/
Enter fullscreen mode Exit fullscreen mode

用正则表达式替换字符串

您还可以使用正则表达式来搜索和替换文件内容。假设您想将任何问候语替换为“再见”。您可以这样做:

function youSayHelloISayGoodbye(str) {
  str = str.replace("Hello", "Goodbye");
  str = str.replace("Hi", "Goodbye");
  str = str.replace("Hey", "Goodbye");  str = str.replace("hello", "Goodbye");
  str = str.replace("hi", "Goodbye");
  str = str.replace("hey", "Goodbye");
  return str;
}
Enter fullscreen mode Exit fullscreen mode

还有一种更简单的替代方法,使用正则表达式:

function youSayHelloISayGoodbye(str) {
  str = str.replace(/[Hh]ello|[Hh]i|[Hh]ey/, "Goodbye");
  return str;
}
Enter fullscreen mode Exit fullscreen mode

在代码沙箱中运行相关的代码示例

但是,您可能会注意到,如果您运行youSayHelloISayGoodbye“Hello, Hi there”:它将不会匹配多个输入:

下一句是这张图片的解释。抱歉,这张图片的 Markdown 处理出了问题。

如果对字符串“Hello, Hi there”使用正则表达式 /[Hh]ello|[Hh]i|[Hh]ey/,则默认情况下它将只匹配“Hello”。

在这里,我们应该看到“Hello”和“Hi”都匹配,但是没有。

这是因为我们需要利用正则表达式“标志”进行多次匹配。

旗帜

正则表达式标志是对现有正则表达式的修饰符。这些标志始终附加在正则表达式定义中的最后一个正斜杠之后。

以下是一些可供您使用的标志的简短列表。

  • g- 全局,匹配多次
  • m- 强制 $ 和 ^ 分别匹配每个换行符
  • i- 使正则表达式不区分大小写

这意味着我们可以重写以下正则表达式:

/[Hh]ello|[Hh]i|[Hh]ey/
Enter fullscreen mode Exit fullscreen mode

要使用不区分大小写的标志:

/Hello|Hi|Hey/i
Enter fullscreen mode Exit fullscreen mode

有了这个标志,这个正则表达式现在将匹配:

Hello
HEY
Hi
HeLLo
Enter fullscreen mode Exit fullscreen mode

或任何其他经过修改的变体。

具有字符串替换功能的全局正则表达式标志

正如我们之前提到的,如果执行不带任何标志的正则表达式替换,它将仅替换第一个结果:

let str = "Hello, hi there!";
str = str.replace(/[Hh]ello|[Hh]i|[Hh]ey/, "Goodbye");
console.log(str); // Will output "Goodbye, hi there"
Enter fullscreen mode Exit fullscreen mode

但是,如果传递该global标志,则会匹配正则表达式匹配的每个问候语实例:

let str = "Hello, hi there!";
str = str.replace(/[Hh]ello|[Hh]i|[Hh]ey/g, "Goodbye");
console.log(str); // Will output "Goodbye, Goodbye there"
Enter fullscreen mode Exit fullscreen mode

关于 JavaScript 全局标志的说明

exec当使用全局 JavaScript 正则表达式时,您可能会在多次运行命令时遇到一些奇怪的行为。

特别是,如果使用全局正则表达式运行,它将每隔一次exec返回:null

如果我们将正则表达式赋给一个变量,然后在该变量上运行 raw `exec` endraw,它会在第一次和第三次找到正确的结果,但第二次返回 raw `null` endraw这是因为,正如MDN 解释的那样:

JavaScript RegExp对象在设置了全局粘性标志时是有状态的……它们存储上一个匹配项的lastIndex。在内部使用此功能,exec() 可用于迭代文本字符串中的多个匹配项……

exec命令会尝试向前查找lastIndex。由于lastIndex设置为字符串的长度,它会尝试将""空字符串与您的正则表达式进行匹配,直到它exec再次被另一个命令重置。虽然此功能在特定情况下很有用,但它常常会让新用户感到困惑。

为了解决这个问题,我们可以lastIndex在运行每个exec命令之前简单地分配为 0:

如果我们在每个原始的 `regex.exec` endraw 之间运行原始的 `regex.lastIndex = 0` endraw,那么每个原始的 `exec` endraw 都会按预期运行

团体

使用正则表达式搜索时,一次搜索多个匹配项会很有帮助。这时“组”就派上用场了。组允许您一次搜索多个项目。

在这里,我们可以看到与两者的匹配Testing 123,并且Tests 123没有在正则表达式中重复“123”匹配器。

/(Testing|tests) 123/ig
Enter fullscreen mode Exit fullscreen mode

使用正则表达式 /(Testing|tests) 123/ig 我们可以匹配组由括号定义;有两种不同类型的组——捕获组和非捕获组:

  • (...)- 任意三个字符匹配成组
  • (?:...)- 匹配任意三个字符的非捕获组

当“替换”成为等式的一部分时,这两者之间的差异通常出现在对话中。

例如,使用上面的正则表达式,我们可以使用以下 JavaScript 将文本替换为“Testing 234”和“tests 234”:

const regex = /(Testing|tests) 123/ig;

let str = `
Testing 123
Tests 123
`;

str = str.replace(regex, '$1 234');
console.log(str); // Testing 234\nTests 234"
Enter fullscreen mode Exit fullscreen mode

我们用 来$1指代第一个捕获组,(Testing|tests)。我们也可以匹配多个组,例如(Testing|tests)(123)

const regex = /(Testing|tests) (123)/ig;

let str = `
Testing 123
Tests 123
`;

str = str.replace(regex, '$1 #$2');
console.log(str); // Testing #123\nTests #123"
Enter fullscreen mode Exit fullscreen mode

但是,这仅适用于捕获组。如果我们更改:

/(Testing|tests) (123)/ig
Enter fullscreen mode Exit fullscreen mode

成为:

/(?:Testing|tests) (123)/ig;
Enter fullscreen mode Exit fullscreen mode

那么就只有一个捕获组——(123)相反,上面的相同代码将输出不同的内容:

const regex = /(?:Testing|tests) (123)/ig;

let str = `
Testing 123
Tests 123
`;

str = str.replace(regex, '$1');
console.log(str); // "123\n123"
Enter fullscreen mode Exit fullscreen mode

在代码沙箱中运行相关的代码示例

命名捕获组

虽然捕获组很棒,但当捕获组数量较多时,很容易让人感到困惑。$3和之间的区别$5并不总是一目了然。

为了解决这个问题,正则表达式有一个称为“命名捕获组”的概念

  • (?<name>...)- 名为“name”的命名捕获组,匹配任意三个字符

您可以在正则表达式中使用它们来创建一个名为“num”的组,该组匹配三个数字:

/Testing (?<num>\d{3})/
Enter fullscreen mode Exit fullscreen mode

然后,您可以像这样使用它来替换:

const regex = /Testing (?<num>\d{3})/
let str = "Testing 123";
str = str.replace(regex, "Hello $<num>")
console.log(str); // "Hello 123"
Enter fullscreen mode Exit fullscreen mode

命名反向引用

有时在查询内部引用命名的捕获组会很有用。这时“反向引用”就可以发挥作用了。

  • \k<name>在搜索查询中引用命名捕获组“名称”

假设您想要匹配:

Hello there James. James, how are you doing?
Enter fullscreen mode Exit fullscreen mode

但不是:

Hello there James. Frank, how are you doing?
Enter fullscreen mode Exit fullscreen mode

你可以编写一个正则表达式来重复单词“James”,如下所示:

/.*James. James,.*/
Enter fullscreen mode Exit fullscreen mode

更好的替代方案可能是这样的:

/.*(?<name>James). \k<name>,.*/
Enter fullscreen mode Exit fullscreen mode

现在,您不再需要硬编码两个名称,而只需要一个。

在代码沙箱中运行相关的代码示例

前瞻组和后瞻组

前瞻组和后瞻组非常强大,但经常被误解。

前瞻和后瞻有四种不同的类型:

  • (?!)- 负面前瞻
  • (?=)- 积极展望
  • (?<=)- 积极回顾
  • (?<!)- 负面回顾

前瞻的工作原理就像它听起来的那样:它要么查看某物是否在前瞻组之后,要么查看它是否不在前瞻组之后,这取决于它是正数还是负数。

因此,像这样使用负向前瞻:

/B(?!A)/
Enter fullscreen mode Exit fullscreen mode

将允许您匹配BC但不允许BA

使用正则表达式 /B(?!A)/ 我们可以匹配

你甚至可以将它们与^$标记组合起来,尝试匹配完整的字符串。例如,以下正则表达式将匹配任何以“Test”开头的字符串

/^(?!Test).*$/gm
Enter fullscreen mode Exit fullscreen mode

/^(?!Test).*$/gm 让我们匹配

同样,我们可以将其切换为正向预测,以强制我们的字符串必须以“Test”开头

/^(?=Test).*$/gm
Enter fullscreen mode Exit fullscreen mode

反转我们之前的项目 - /^(?=Test).*$/gm 让我们匹配

整合起来

正则表达式非常强大,可以用于各种字符串操作。了解它们可以帮助您重构代码库、编写快速的语言脚本等等!

让我们回到最初的电话号码正则表达式并尝试再次理解它:

^(?:\d{3}-){2}\d{4}$
Enter fullscreen mode Exit fullscreen mode

请记住,此正则表达式旨在匹配如下电话号码:

555-555-5555
Enter fullscreen mode Exit fullscreen mode

这里的正则表达式是:

  • 使用^$定义正则表达式行的开始和结束。
  • 使用非捕获组来查找三个数字,然后是破折号
    • 重复此组两次,以匹配555-555-
  • 查找电话号码的最后 4 位数字

希望本文能帮助您更好地了解正则表达式。如果您想快速了解一些常用正则表达式的定义,请查看我们的速查表。

下载我们的正则表达式速查表

文章来源:https://dev.to/coderpad/the-complete-guide-to-regular-expressions-regex-1m6
PREV
学习 HTML/CSS,然后开始自由职业——我如何通过电话推销以 19,950 美元的价格签下我的第一个客户
NEXT
提高你的算法和数据结构技能