Regular Expressions Tutorial: A Beginner-Friendly Guide to Regex
· 10 min read
什么是正则表达式以及为什么要学习它
正则表达式(Regular Expression,简称 regex 或 regexp)是一种强大的文本模式匹配工具。它使用特殊的字符序列来定义搜索模式,可以用于查找、替换、验证和提取文本中的特定内容。
想象一下,你需要从一个包含数千行日志的文件中找出所有的电子邮件地址,或者验证用户输入的手机号码格式是否正确。如果使用传统的字符串操作方法,代码会变得冗长且难以维护。而正则表达式可以用一行简洁的模式来完成这些任务。
为什么要学习正则表达式?
- 文本处理效率:正则表达式可以快速处理大量文本数据,执行复杂的搜索和替换操作
- 数据验证:验证用户输入(如邮箱、电话号码、密码强度)是 Web 开发中的常见需求
- 数据提取:从非结构化文本中提取结构化信息,如从网页中提取链接或从日志中提取错误信息
- 跨平台通用:几乎所有编程语言和文本编辑器都支持正则表达式
- 提高生产力:掌握正则表达式可以大幅减少编写重复代码的时间
正则表达式的应用场景非常广泛:
- Web 表单验证(邮箱、电话、邮编等)
- 日志分析和数据挖掘
- 文本编辑器中的搜索和替换
- 网络爬虫中的数据提取
- 代码重构和批量修改
- 配置文件解析
基础语法
正则表达式由普通字符和特殊字符(元字符)组成。让我们从最基础的概念开始。
字面字符(Literal Characters)
最简单的正则表达式就是普通字符本身。例如,正则表达式 cat 会匹配文本中的 "cat" 字符串。
文本: "The cat sat on the mat"
正则: cat
匹配: "The cat sat on the mat"
字面字符是区分大小写的,所以 cat 不会匹配 "Cat" 或 "CAT"。
点号(.)
点号 . 是一个特殊字符,它可以匹配除换行符外的任意单个字符。
文本: "cat", "cot", "cut", "c@t"
正则: c.t
匹配: 所有四个字符串都会被匹配
如果你想匹配字面意义上的点号,需要使用反斜杠进行转义:\.
文本: "file.txt"
正则: file\.txt
匹配: "file.txt" (而不是 "fileAtxt" 等)
锚点(Anchors)
锚点用于指定匹配的位置,而不是匹配具体的字符。
^ (脱字符) - 行首
^ 匹配字符串或行的开始位置。
文本: "cat\ndog\ncat"
正则: ^cat
匹配: 只匹配第一行的 "cat"
$ (美元符号) - 行尾
$ 匹配字符串或行的结束位置。
文本: "cat\ndog\ncat"
正则: cat$
匹配: 只匹配最后一行的 "cat"
组合使用
结合使用 ^ 和 $ 可以匹配整行内容:
正则: ^cat$
匹配: 只匹配完整的一行,且该行只包含 "cat"
转义字符
某些字符在正则表达式中有特殊含义,如果要匹配这些字符本身,需要使用反斜杠 \ 进行转义:
特殊字符: . * + ? ^ $ { } [ ] ( ) | \
转义方式: \. \* \+ \? \^ \$ \{ \} \[ \] \( \) \| \\
例如,匹配价格 "$5.99":
正则: \$5\.99
字符类
字符类允许你定义一组字符,匹配其中的任意一个。
方括号字符类 [abc]
方括号 [] 内的字符表示一个字符集,匹配其中的任意一个字符。
文本: "cat", "cot", "cut", "cit"
正则: c[aou]t
匹配: "cat", "cot", "cut" (不匹配 "cit")
范围 [a-z]
使用连字符 - 可以定义字符范围:
[a-z] 匹配任意小写字母
[A-Z] 匹配任意大写字母
[0-9] 匹配任意数字
[a-zA-Z] 匹配任意字母
[a-z0-9] 匹配任意字母或数字
示例:
文本: "a1", "b2", "c3", "d4"
正则: [a-c][1-3]
匹配: "a1", "b2", "c3" (不匹配 "d4")
否定字符类 [^abc]
在方括号内使用 ^ 表示否定,匹配不在集合中的任意字符:
正则: [^0-9]
含义: 匹配任意非数字字符
文本: "a1b2c3"
正则: [^0-9]+
匹配: "a", "b", "c" (所有非数字字符)
预定义字符类
正则表达式提供了一些常用的预定义字符类:
\d - 数字
\d 等价于 [0-9],匹配任意数字字符。
文本: "Room 123"
正则: Room \d+
匹配: "Room 123"
\D - 非数字
\D 等价于 [^0-9],匹配任意非数字字符。
\w - 单词字符
\w 等价于 [a-zA-Z0-9_],匹配字母、数字或下划线。
文本: "user_name123"
正则: \w+
匹配: "user_name123"
\W - 非单词字符
\W 等价于 [^a-zA-Z0-9_],匹配任意非单词字符。
\s - 空白字符
\s 匹配任意空白字符,包括空格、制表符、换行符等。
等价于: [ \t\n\r\f\v]
文本: "hello world"
正则: hello\sworld
匹配: "hello world"
\S - 非空白字符
\S 匹配任意非空白字符。
实际应用示例
匹配一个简单的用户名(只包含字母、数字和下划线,3-16个字符):
正则: ^[a-zA-Z0-9_]{3,16}$
或者: ^\w{3,16}$
匹配十六进制颜色代码:
正则: #[0-9A-Fa-f]{6}
示例: "#FF5733", "#00ff00"
量词
量词用于指定前面的字符或组应该出现的次数。
* (星号) - 零次或多次
* 匹配前面的元素零次或多次。
文本: "ct", "cat", "caat", "caaat"
正则: ca*t
匹配: 所有字符串都匹配
+ (加号) - 一次或多次
+ 匹配前面的元素一次或多次。
文本: "ct", "cat", "caat", "caaat"
正则: ca+t
匹配: "cat", "caat", "caaat" (不匹配 "ct")
? (问号) - 零次或一次
? 匹配前面的元素零次或一次,使其成为可选的。
文本: "color", "colour"
正则: colou?r
匹配: 两个单词都匹配
另一个常见用法是匹配可选的协议:
正则: https?://
匹配: "http://" 和 "https://"
{n} - 精确次数
{n} 匹配前面的元素恰好 n 次。
文本: "123", "1234", "12345"
正则: \d{4}
匹配: "1234" 中的 "1234", "12345" 中的前四个数字
{n,} - 至少 n 次
{n,} 匹配前面的元素至少 n 次。
正则: \d{3,}
匹配: 至少三个连续的数字
{n,m} - n 到 m 次
{n,m} 匹配前面的元素至少 n 次,最多 m 次。
正则: \d{2,4}
匹配: 2到4个连续的数字
贪婪与非贪婪匹配
默认情况下,量词是贪婪的,会尽可能多地匹配字符。在量词后面加上 ? 可以使其变为非贪婪(懒惰)模式。
文本: "<div>content</div>"
正则(贪婪): <.+>
匹配: "<div>content</div>" (整个字符串)
正则(非贪婪): <.+?>
匹配: "<div>" 和 "</div>" (分别匹配)
实际应用示例
匹配中国大陆手机号码(11位数字,以1开头):
正则: ^1\d{10}$
解释: ^ 开始, 1 字面字符, \d{10} 十个数字, $ 结束
匹配 HTML 标签:
正则: <[^>]+>
解释: < 左尖括号, [^>]+ 一个或多个非右尖括号字符, > 右尖括号
匹配重复的单词:
正则: \b(\w+)\s+\1\b
示例: "the the" 会被匹配
分组与捕获
圆括号 () 用于创建分组,可以将多个字符作为一个单元处理,并且可以捕获匹配的内容供后续使用。
基本分组
使用圆括号可以将多个字符组合成一个单元,然后对整个单元应用量词:
文本: "ababab"
正则: (ab)+
匹配: "ababab" (整个字符串)
没有分组的话:
正则: ab+
匹配: "ab", "abb", "abbb" 等 (只有 b 重复)
捕获组
圆括号不仅用于分组,还会捕获匹配的内容。每个捕获组都有一个编号,从1开始。
文本: "John Doe"
正则: (\w+)\s(\w+)
捕获组1: "John"
捕获组2: "Doe"
捕获的内容可以在替换操作中使用,或者通过编程语言的 API 访问。
反向引用
反向引用允许你在正则表达式中引用之前捕获的内容。使用 \1, \2 等表示第1、第2个捕获组。
文本: "hello hello", "world world"
正则: (\w+)\s\1
匹配: 重复的单词
匹配 HTML 标签对:
正则: <(\w+)>.*?</\1>
示例: "<div>content</div>" 匹配
"<div>content</span>" 不匹配
非捕获组
如果只想分组而不想捕获,可以使用 (?:...):
正则: (?:https?|ftp)://\w+
解释: 匹配 http, https 或 ftp 协议,但不捕获协议部分
非捕获组的优势:
- 提高性能(不需要存储捕获的内容)
- 简化捕获组的编号
- 使正则表达式更清晰
命名捕获组
许多现代正则表达式引擎支持命名捕获组,使用 (?<name>...) 语法:
正则: (?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})
文本: "2024-03-15"
捕获: year="2024", month="03", day="15"
实际应用示例
提取日期并重新格式化:
文本: "2024-03-15"
正则: (\d{4})-(\d{2})-(\d{2})
替换: $2/$3/$1
结果: "03/15/2024"
验证并提取邮箱地址的用户名和域名:
正则: ^([a-zA-Z0-9._%-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$
文本: "[email protected]"
捕获组1: "user"
捕获组2: "example.com"
选择符
管道符 | 用于表示"或"的关系,匹配多个可能的模式之一。
基本用法
正则: cat|dog
匹配: "cat" 或 "dog"
文本: "I have a cat and a dog"
正则: cat|dog
匹配: "cat" 和 "dog"
与分组结合
选择符通常与分组一起使用,以限制其作用范围:
正则: (cat|dog)s?
匹配: "cat", "cats", "dog", "dogs"
没有分组的话:
正则: cat|dogs
匹配: "cat" 或 "dogs" (注意 s 只应用于 dog)
多个选择
可以有多个选择项:
正则: red|green|blue
匹配: "red", "green" 或 "blue"
实际应用示例
匹配常见的图片文件扩展名:
正则: \.(jpg|jpeg|png|gif|bmp|svg)$
示例: "image.jpg", "photo.png"
匹配不同的日期格式:
正则: \d{4}-\d{2}-\d{2}|\d{2}/\d{2}/\d{4}
匹配: "2024-03-15" 或 "03/15/2024"
匹配常见的编程语言关键字:
正则: \b(if|else|while|for|return|function|class)\b
前瞻与后顾
前瞻(lookahead)和后顾(lookbehind)是零宽度断言,它们匹配位置而不是字符,不会消耗字符。
正向前瞻 (?=...)
正向前瞻断言某个位置后面跟着特定的模式,但不包括该模式在匹配结果中。
文本: "abc123"
正则: \w+(?=\d)
匹配: "abc" (后面跟着数字,但数字不包括在匹配中)
实际应用 - 匹配后面跟着 "px" 的数字:
文本: "width: 100px, height: 200px"
正则: \d+(?=px)
匹配: "100" 和 "200"
负向前瞻 (?!...)
负向前瞻断言某个位置后面不跟着特定的模式。
文本: "abc123 def456"
正则: \w+(?!\d)
匹配: "abc12" 和 "def45" (后面不跟数字的字母数字序列)
实际应用 - 匹配不以 ".jpg" 结尾的文件名:
正则: \w+\.(?!jpg)\w+
匹配: "file.png", "doc.pdf" (不匹配 "image.jpg")
正向后顾 (?<=...)
正向后顾断言某个位置前面是特定的模式。
文本: "$100, €200, £300"
正则: (?<=\$)\d+
匹配: "100" (前面是美元符号的数字)
实际应用 - 提取价格数字(前面有货币符号):
正则: (?<=\$|€|£)\d+
匹配: 所有货币符号后的数字
负向后顾 (?<!...)
负向后顾断言某个位置前面不是特定的模式。
文本: "abc123 xyz789"
正则: (?<![a-z])\d+
匹配: 前面不是字母的数字序列
组合使用
前瞻和后顾可以组合使用,创建复杂的匹配条件:
正则: (?<=@)\w+(?=\.)
文本: "[email protected]"
匹配: "example" (@ 和 . 之间的内容)
实际应用示例
密码强度验证(至少8个字符,包含大写字母、小写字母和数字):
正则: ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$
解释:
(?=.*[a-z]) 确保至少有一个小写字母
(?=.*[A-Z]) 确保至少有一个大写字母
(?=.*\d) 确保至少有一个数字
.{8,} 至少8个字符
提取 HTML 标签之间的内容:
正则: (?<=>)[^<]+(?=<)
文本: "<div>Hello World</div>"
匹配: "Hello World"
常用正则表达式模式表
以下是一些常用的正则表达式模式,可以直接在项目中使用或根据需要进行调整。
| 类型 | 正则表达式 | 说明 | 示例 |
|---|---|---|---|
| 电子邮件 | ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ |
基本的邮箱验证 | [email protected] |
| 电子邮件(严格) | ^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ |
符合 RFC 5322 标准 | [email protected] |
| 中国手机号 | ^1[3-9]\d{9}$ |
11位,以1开头 | 13812345678 |
| 美国电话 | ^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$ |
支持多种格式 | (123) 456-7890 |
| URL | ^https?://[^\s/$.?#].[^\s]*$ |
基本 URL 验证 | https://example.com |
| URL(完整) | ^(https?|ftp)://[^\s/$.?#].[^\s]*$ |
包括 FTP 协议 | ftp://files.example.com |
| IPv4 地址 | ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ |
验证 IPv4 地址 | 192.168.1.1 |
| IPv6 地址 | ^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::)$ |
简化的 IPv6 验证
Frequently Asked QuestionsWhat does the regex .* mean?The dot (.) matches any character except newline, and the asterisk (*) means zero or more times. So .* matches any sequence of characters, including an empty string. It is the most greedy pattern. How do I match an email address with regex?A practical pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ This matches most valid emails. Note that fully RFC-compliant email validation is extremely complex. What is the difference between * and + in regex?* matches zero or more occurrences (optional, can match nothing). + matches one or more occurrences (requires at least one match). For example, a* matches empty string, but a+ requires at least one "a". How do I make regex case-insensitive?Add the i flag: /pattern/i in JavaScript, re.IGNORECASE in Python, or -i flag in grep. This makes [a-z] also match uppercase letters. What is catastrophic backtracking in regex?When a regex engine tries exponentially many paths to match a pattern, causing extreme slowness. Common cause: nested quantifiers like (a+)+. Avoid by using atomic groups, possessive quantifiers, or restructuring the pattern. Related Tools |