计算机干货知识点,点点关注不迷路!
你是否好奇计算机如何进行数据验证、信息提取还是文本替换?
本文将带你彻底搞懂正则表达式,让你从此掌握一把利器!
目录
在数字时代,文本数据无处不在,从简单的日志文件到复杂的网页内容,文本处理成为了每个开发者必须面对的挑战。在这些挑战中,正则表达式(Regular Expression,简称 regex)有强大的文本匹配和处理能力,无论是数据验证、信息提取还是文本替换,正则表达式都能提供简洁而高效的解决方案。
基本语法
正则表达式的核心在于其独特的语法,它使用一系列特殊字符和符号来定义搜索模式。这些特殊字符被称为元字符,它们赋予了正则表达式强大的匹配能力。下面我们将介绍一些最常用的元字符及其功能:
单个字符匹配
.(点号): 匹配任意单个字符(除了换行符)。例如,正则表达式 a.c 可以匹配 "abc"、"aac"、"acc" 等字符串。
\(反斜杠): 用于转义字符,使其失去特殊含义。例如,如果你想匹配一个实际的点号,你需要使用 \. 。
字符集匹配
[](方括号): 匹配方括号内的任意一个字符。例如,[abc] 可以匹配 "a"、"b" 或 "c"。
-(连字符): 在方括号内使用,表示字符范围。例如,[a-z] 匹配所有小写字母,[0-9] 匹配所有数字。
^ (脱字符,[]中): 在方括号内使用,表示否定,即匹配不在方括号内的字符。例如,[^abc] 匹配除了 "a"、"b" 和 "c" 之外的任何字符。
\d: 匹配任何数字,等价于 [0-9]。
\D: 匹配任何非数字字符,等价于 [^0-9]。
\w: 匹配任何字母、数字或下划线,等价于 [a-zA-Z0-9]。
\W: 匹配任何非字母、数字或下划线字符,等价于 [^a-zA-Z0-9]。
\s: 匹配任何空白字符,包括空格、制表符、换行符等。
\S: 匹配任何非空白字符。
重复匹配
* (星号): 匹配前面的字符 零次或多次。例如,ab*可以匹配 "a"、"ab"、"abb"、"abbb" 等。
+(加号): 匹配前面的字符 一次或多次。例如,ab+可以匹配 "ab"、"abb"、"abbb" 等,但不能匹配 "a"。
?(问号): 匹配前面的字符 零次或一次。例如,ab?可以匹配 "a" 或 "ab"。
{n}: 匹配前面的字符 恰好 n 次。例如,a{3}匹配 "aaa"。
{n,}: 匹配前面的字符 至少 n 次。例如,a{2,}匹配 "aa"、"aaa"、"aaaa" 等。
{n,m}: 匹配前面的字符 至少 n 次,至多 m 次。例如,a{2,4}匹配 "aa"、"aaa" 或 "aaaa"。
位置匹配
^ (脱字符,不在[]中): 匹配字符串的开头。例如,^abc 匹配以 "abc" 开头的字符串。
$ (美元符号): 匹配字符串的结尾。例如,abc$ 匹配以 "abc" 结尾的字符串。
\b: 匹配单词边界。例如,\bcat\b 匹配 "cat" 这个单词,但不匹配 "category" 中的 "cat"。
选择匹配
|(竖线): 表示或的关系,匹配左边或右边的表达式。例如,cat|dog 匹配 "cat" 或 "dog"。
分组和捕获
() (圆括号): 将表达式分组,并捕获匹配的子字符串。例如,(ab)+ 匹配 "ab"、"abab"、"ababab" 等,并将每个 "ab" 捕获到一个组中。
下面是一个python程序实例:
import re
# 匹配以 "a" 开头,以 "b" 结尾的字符串
pattern = r"^a.*b$"
text = "a123b"
match = re.match(pattern, text)
if match:
print("匹配成功!")
else:
print("匹配失败!")
分组和捕获
在正则表达式中,分组和捕获是两个非常重要的概念,它们允许我们将匹配的子字符串提取出来,以便进行进一步的处理或分析。
分组
分组是指使用圆括号 ()
将正则表达式的一部分括起来,形成一个子表达式。
分组的作用有:
提高可读性: 将复杂的正则表达式分解成更小的、更易于理解的单元。
应用量词: 对分组内的子表达式应用量词,例如 (ab)+ 匹配 "ab"、"abab"、"ababab" 等。
捕获匹配内容: 将分组内匹配的子字符串捕获到捕获组中,以便后续使用。
捕获
分组后,我们可以捕获分组内容,进行下一步操作。
捕获组: 默认情况下,每个分组都是一个捕获组,它会将匹配的子字符串捕获到一个编号的组中。例如,正则表达式 (\d{3})-(\d{4}) 匹配 "123-4567" 时,会将 "123" 捕获到第 1 组,将 "4567" 捕获到第 2 组。
非捕获组: 如果你只想对子表达式进行分组,而不需要捕获匹配内容,可以使用非捕获组
(?:)
。例如,正则表达式 (?:\d{3})-(\d{4}) 匹配 "123-4567" 时,只会将 "4567" 捕获到第 1 组。
例:
import re
# 匹配电话号码,并提取区号和号码
pattern = r"(\d{3})-(\d{4})"
text = "我的电话号码是 123-4567。"
match = re.search(pattern, text)
if match:
print("区号:", match.group(1)) # 输出: 区号: 123
print("号码:", match.group(2)) # 输出: 号码: 4567
反向引用
反向引用是指在同一正则表达式中,引用前面捕获组捕获的内容。
反向引用的语法是 \n,其中 n 是捕获组的编号。
例如,正则表达式 (\d{3})-\1 匹配 "123-123",但不匹配 "123-456"。
贪婪匹配与懒惰匹配
在正则表达式中,贪婪匹配和懒惰匹配是两种不同的匹配模式,它们决定了正则表达式引擎在匹配文本时的行为方式。理解这两种模式的区别对于编写高效、准确的正则表达式至关重要。
贪婪匹配
贪婪匹配是正则表达式的默认匹配模式。在这种模式下,正则表达式引擎会尽可能多地匹配字符,直到无法匹配为止。
例如,正则表达式 a.*b 匹配以 "a" 开头,以 "b" 结尾的字符串。对于字符串 "aabab",贪婪匹配会匹配整个字符串 "aabab"。
懒惰匹配
懒惰匹配与贪婪匹配相反,它会尽可能少地匹配字符,直到满足匹配条件为止。
在正则表达式中,可以使用 ? 来将贪婪匹配转换为懒惰匹配。例如,正则表达式 a.*?b 匹配以 "a" 开头,以 "b" 结尾的字符串,但会尽可能少地匹配字符。对于字符串 "aabab",懒惰匹配会匹配 "aab" 和 "ab" 两个子字符串。
例:
import re
# 贪婪匹配
pattern_greedy = r"a.*b"
text = "aabab"
match_greedy = re.search(pattern_greedy, text)
print("贪婪匹配:", match_greedy.group()) # 输出: 贪婪匹配: aabab
# 懒惰匹配
pattern_lazy = r"a.*?b"
match_lazy = re.findall(pattern_lazy, text)
print("懒惰匹配:", match_lazy) # 输出: 懒惰匹配: ['aab', 'ab']
零宽断言
在正则表达式中,零宽断言(Zero-width Assertions)是一种特殊的语法结构,它用于指定匹配发生的位置,而不会消耗任何字符。这意味着零宽断言只用于判断某个位置是否满足条件,而不会影响匹配结果的长度。
前瞻断言(Lookahead Assertions): 判断当前位置后面是否满足/不满足某个条件。
正向前瞻断言(Positive Lookahead): (?=)
例如,\d+(?=px) 匹配后面跟着 "px" 的数字,例如 "16px" 中的 "16"。
负向前瞻断言(Negative Lookahead): (?!)
例如,\d+(?!px) 匹配后面不跟着 "px" 的数字,例如 "16pt" 中的 "16"。
后瞻断言(Lookbehind Assertions): 判断当前位置前面是否满足/不满足某个条件
正向后瞻断言(Positive Lookbehind): (?<=)
例如,(?<=\$)\d+ 匹配前面有"$"的数字,例如""的数字,例如"$100" 中的 "100"。
负向后瞻断言(Negative Lookbehind): (?<!)
例如,(?<!\$)\d+ 匹配前面没有 "$" 的数字,例如 "100" 中的 "100"。
应用场景
提取特定位置的文本: 例如,提取 HTML 标签中的内容,可以使用正向后瞻断言和正向前瞻断言。
验证字符串格式: 例如,验证密码强度,可以使用零宽断言来确保密码包含大小写字母、数字和特殊字符。
排除特定模式: 例如,排除包含特定单词的文本,可以使用负向前瞻断言。
实例:微信号匹配
微信号的规则为:6至20位,以字母开头,后面的字符可以是字母,数字,减号,下划线。
则我们使用以下正则表达式来匹配:
/^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/