正则表达式(Regular Expression,简称 Regex)是一种强大的文本处理工具,用于匹配、查找、替换和提取字符串中的特定模式。它广泛应用于编程、数据清洗、日志分析以及 Markdown 解析等场景。
基础语法元素
正则表达式由普通字符和元字符组成。以下是核心语法的分类说明:
预定义字符类
| 字符 | 含义 | 等价形式 |
|---|---|---|
. |
匹配除换行符以外的任意字符 | - |
\d |
匹配数字字符 | [0-9] |
\D |
匹配非数字字符 | [^0-9] |
\w |
匹配单词字符(字母、数字、下划线) | [a-zA-Z0-9_] |
\W |
匹配非单词字符 | [^a-zA-Z0-9_] |
\s |
匹配空白字符(空格、Tab、换行等) | [\t\n\x0B\f\r] |
\S |
匹配非空白字符 | [^\t\n\x0B\f\r] |
边界匹配
| 字符 | 含义 |
|---|---|
^ |
匹配字符串的开头 |
$ |
匹配字符串的结尾 |
\b |
匹配单词边界 |
\B |
匹配非单词边界 |
量词(重复匹配)
| 字符 | 含义 |
|---|---|
* |
匹配前一个元素 0 次或多次 |
+ |
匹配前一个元素 1 次或多次 |
? |
匹配前一个元素 0 次或 1 次 |
{n} |
匹配前一个元素恰好 n 次 |
{n,} |
匹配前一个元素至少 n 次 |
{n,m} |
匹配前一个元素 n 到 m 次 |
注意:默认情况下,量词是贪婪模式(尽可能多匹配)。在量词后加
?(如*?,+?)可变为非贪婪模式(尽可能少匹配),这在解析嵌套结构(如 HTML/Markdown 标签)时非常重要。
分组与捕获
分组允许将多个字符视为一个整体进行重复匹配,并可以"捕获"匹配到的内容供后续使用。
捕获组 (exp)
使用圆括号 () 包裹的子表达式称为捕获组。正则引擎会记录匹配到的内容,并分配编号。
- 编号规则:从左到右,根据左括号出现的顺序编号,从 1 开始。整个表达式默认为第 0 组。
- 反向引用:在正则表达式内部,可以使用
\1,\2引用前面捕获组匹配到的具体文本。
示例:匹配成对的引号
regex
(['"]).*?\1
(['"]):捕获单引号或双引号,存入第 1 组。.*?:非贪婪匹配中间的内容。\1:引用第 1 组捕获的内容,确保结尾引号与开头一致。
命名分组 (?P<name>exp)
在 Python 等语言中,可以为分组命名,提高可读性。
- 定义:
(?P<name>pattern) - 引用:
(?P=name)或在替换时使用$name/${name}
示例:匹配 HTML 标签
regex
<(?P<tag>\w+)>(.*?)</(?P=tag)>
非捕获组 (?:exp)
如果只需要分组功能(如应用量词),但不需要保存匹配结果,使用非捕获组可以节省内存。
- 语法:
(?:pattern) - 特点:不占用分组编号,无法通过
\1或group()获取内容。
应用:解析 Markdown 语法
正则表达式常用于轻量级的 Markdown 到 HTML 转换。以下是常见元素的解析思路:
标题 (Headers)
Markdown 标题以 `` 开头,后跟空格和文本。
- 正则:
^{1,6}\s+(.+)$(需开启多行模式) - 替换为:
<h$1>$2</h$1>$1:捕获 `` 的数量(1-6),决定标题级别。$2:捕获标题文本。
强调 (Emphasis)
-
粗体 (
text或__text__)- 正则:
(\*\*|__)([^\*\n]+?)\1 - 替换为:
<strong>$2</strong> - 说明:
\1确保结束标记与开始标记一致(都是**或都是__);+?使用非贪婪匹配防止跨行误捕。
- 正则:
-
斜体 (
*text*或_text_)- 正则:
(\*|_)([^\*\n]+?)\1 - 替换为:
<em>$2</em>
- 正则:
行内代码 (Inline Code)
- 正则:
([^`\n]+) - 替换为:
<code>$1</code>
链接与图片 (Links & Images)
链接和图片语法相似,图片多一个前置 !。
-
图片:
!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)$1:Alt 文本$2:图片源地址 (src)$3:可选标题 (title)- 替换为:
<img src="$2" alt="$1" title="$3" />
-
链接:
\[([^\]]*)\]\(([^)\s]+)\)- 替换为:
<a href="$2">$1</a>
- 替换为:
注意事项
- 转义特殊字符:在匹配
.*+?()[]{}^$\|等元字符时,必须使用\进行转义。 - HTML 转义:在将 Markdown 转换为 HTML 时,务必先对原始文本中的
<>&等字符进行 HTML 实体转义,防止 XSS 注入或标签闭合错误。 - 非贪婪匹配:在处理包含多个相同标记的行内元素(如粗体、链接)时,务必使用非贪婪量词(
*?,+?),否则正则可能会从第一个开始标记一直匹配到最后一个结束标记,导致中间内容被错误吞并。 - 性能考量:复杂的嵌套正则可能导致回溯灾难(ReDoS)。对于复杂的 Markdown 解析(如嵌套列表、表格、块引用),建议使用专门的解析器(如 Marked, Remarkable)而非纯正则。
- 测试工具:编写正则时,推荐使用在线测试工具(如 Regex101, RegExr)实时验证匹配结果和分组捕获情况。
常用语言中的调用示例 (Python)
python
import re
text = "Hello World\nThis is bold and this is *italic*."
# 替换粗体
bold_pattern = r'(\*\*|__)([^\*\n]+?)\1'
text = re.sub(bold_pattern, r'<strong>\2</strong>', text)
# 替换斜体
italic_pattern = r'(\*|_)([^\*\n]+?)\1'
text = re.sub(italic_pattern, r'<em>\2</em>', text)
# 替换标题
header_pattern = r'^{1,6}\s+(.+)$'
def replace_header(match):
level = len(match.group(0).split()) # 计算 的数量
content = match.group(1)
return f'<h{level}>{content}</h{vel}>'
text = re.sub(header_pattern, replace_header, text, flags=re.MULTILINE)
print(text)