正则表达式(Regular Expression,简称regex或regexp)是一种强大的文本处理工具,它使用一种特殊的语法来描述、匹配和处理字符串。无论是验证输入、搜索文本、替换内容,还是提取数据,正则表达式都能帮我们高效地完成。掌握正则表达式,是程序员的必备技能之一。

但是,正则表达式的语法看起来很复杂,很多人觉得难学。其实,只要掌握了基本规则和常用模式,正则表达式并不难。今天就来分享正则表达式的完全指南,从入门到精通,帮助大家掌握这个强大的工具。

正则表达式简介

1. 什么是正则表达式: 正则表达式是一种用于描述字符串模式的语法。它由普通字符(如字母、数字)和特殊字符(称为元字符,如.*+?等)组成。正则表达式可以用来:

  • 验证字符串是否符合某种格式(如邮箱、手机号、URL)
  • 在文本中搜索匹配某种模式的字符串
  • 替换文本中匹配某种模式的字符串
  • 从文本中提取匹配某种模式的部分

2. 正则表达式的历史: 正则表达式的概念最早由美国数学家Stephen Kleene在1956年提出,他在研究神经细胞模型时发明了正则表达式。后来,正则表达式被引入到计算机科学中,成为Unix工具(如grep、sed、awk)的核心功能。现在,几乎所有的编程语言(JavaScript、Python、Java、PHP、C#等)都支持正则表达式,正则表达式已经成为程序员的必备技能。

3. 正则表达式的特点

  • 强大:正则表达式可以描述非常复杂的字符串模式,几乎可以处理任何文本处理需求
  • 简洁:用很短的表达式就能描述复杂的模式,比写代码逐字符判断高效得多
  • 通用:几乎所有编程语言和工具都支持正则表达式,语法基本一致
  • 难读:正则表达式的语法比较晦涩,写出来的表达式往往很难读懂,需要注释说明

基本语法

1. 普通字符: 普通字符就是字母、数字、汉字、下划线等没有特殊含义的字符,它们匹配自身。

正则:abc
匹配:abc

2. 元字符: 元字符是有特殊含义的字符,它们不匹配自身,而是表示某种特殊的模式。

元字符含义
.匹配除换行符以外的任意单个字符
*匹配前面的字符零次或多次
+匹配前面的字符一次或多次
?匹配前面的字符零次或一次(可选)
^匹配字符串的开头
$匹配字符串的结尾
\转义字符,让元字符匹配自身
``或,匹配左边或右边的表达式
()分组,将括号内的表达式作为一个整体
[]字符类,匹配括号内的任意一个字符
{}量词,指定前面字符出现的次数

3. 转义字符: 如果要匹配元字符本身(如.*+),需要用反斜杠\转义。

正则:a\.b
匹配:a.b(匹配点号本身)

正则:a\*b
匹配:a*b(匹配星号本身)

4. 字符类: 字符类用[]表示,匹配括号内的任意一个字符。

正则:[abc]
匹配:a、b、c中的任意一个

正则:[a-z]
匹配:任意小写字母

正则:[A-Z]
匹配:任意大写字母

正则:[0-9]
匹配:任意数字

正则:[a-zA-Z0-9]
匹配:任意字母或数字

正则:[^abc]
匹配:除了a、b、c以外的任意字符(^在[]内表示取反)

5. 预定义字符类: 正则表达式提供了一些预定义的字符类,用\加字母表示,方便使用。

预定义字符类含义等价于
\d任意数字[0-9]
\D任意非数字[^0-9]
\w任意单词字符(字母、数字、下划线)[a-zA-Z0-9_]
\W任意非单词字符[^a-zA-Z0-9_]
\s任意空白字符(空格、制表符、换行符等)[ \t\n\r\f\v]
\S任意非空白字符[^ \t\n\r\f\v]
\b单词边界-
\B非单词边界-

6. 量词: 量词用来指定前面的字符或分组出现的次数。

量词含义
*零次或多次(等价于{0,}
+一次或多次(等价于{1,}
?零次或一次(等价于{0,1}
{n}恰好n次
{n,}至少n次
{n,m}n到m次
正则:a*
匹配:零个或多个a(空字符串、a、aa、aaa...)

正则:a+
匹配:一个或多个a(a、aa、aaa...)

正则:a?
匹配:零个或一个a(空字符串、a)

正则:a{3}
匹配:恰好3个a(aaa)

正则:a{3,}
匹配:至少3个a(aaa、aaaa、aaaaa...)

正则:a{3,5}
匹配:3到5个a(aaa、aaaa、aaaaa)

7. 贪婪与非贪婪: 量词默认是贪婪的,会尽可能多地匹配。如果在量词后面加?,就变成非贪婪(懒惰)的,会尽可能少地匹配。

文本:<div>hello</div>
正则(贪婪):<.*>
匹配:<div>hello</div>(尽可能多地匹配)

正则(非贪婪):<.*?>
匹配:<div>(尽可能少地匹配)

8. 分组与捕获: 用()可以将表达式分组,分组内的表达式作为一个整体。分组还可以捕获匹配的内容,供后续使用。

正则:(ab)+
匹配:一个或多个ab(ab、abab、ababab...)

正则:(\d{4})-(\d{2})-(\d{2})
匹配:2016-04-17
捕获组1:2016,捕获组2:04,捕获组3:17

9. 非捕获分组: 如果只需要分组,不需要捕获内容,可以用(?:)表示非捕获分组。

正则:(?:ab)+
匹配:一个或多个ab,但是不捕获内容

10. 反向引用: 可以用\1\2等引用前面捕获组的内容。

正则:(\w+)\s+\1
匹配:hello hello(两个相同的单词)

11. 零宽断言: 零宽断言用来匹配位置,不匹配字符,包括正向先行断言、负向先行断言、正向后行断言、负向后行断言。

断言语法含义
正向先行断言(?=pattern)后面匹配pattern
负向先行断言(?!pattern)后面不匹配pattern
正向后行断言(?<=pattern)前面匹配pattern
负向后行断言(?<!pattern)前面不匹配pattern
正则:\d+(?=元)
匹配:100元中的100(后面是"元")

正则:\d+(?!元)
匹配:100个中的100(后面不是"元")

正则:(?<=¥)\d+
匹配:¥100中的100(前面是"¥")

正则:(?<!¥)\d+
匹配:100元中的100(前面不是"¥")

12. 修饰符: 修饰符用来改变正则表达式的匹配行为,通常写在正则表达式的后面。

修饰符含义
i忽略大小写
g全局匹配(找到所有匹配,而不是只找第一个)
m多行模式(^和$匹配每行的开头和结尾)
s单行模式(.匹配包括换行符在内的任意字符)
x忽略空白字符和注释
正则(忽略大小写):/abc/i
匹配:abc、ABC、Abc、aBc...

正则(全局匹配):/abc/g
匹配:文本中所有的abc

正则(多行模式):/^abc/m
匹配:每行开头的abc

常用正则表达式

1. 邮箱验证

正则:^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
匹配:user@example.com、user.name+tag@example.co.uk

2. 手机号验证(中国大陆)

正则:^1[3-9]\d{9}$
匹配:13812345678、15912345678、18612345678

3. URL验证

正则:^https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/[^\s]*)?$
匹配:http://example.com、https://example.com/path?query=1

4. IP地址验证(IPv4)

正则:^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$
匹配:192.168.1.1、10.0.0.1、255.255.255.0

5. 身份证号验证(中国大陆18位)

正则:^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$
匹配:110101199001011234

6. 日期验证(YYYY-MM-DD)

正则:^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
匹配:2016-04-17、2016-12-31

7. 时间验证(HH:MM:SS)

正则:^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$
匹配:09:30:00、23:59:59

8. 十六进制颜色验证

正则:^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
匹配:#fff、#ffffff、#F00、#FF0000

9. 中文字符匹配

正则:[\u4e00-\u9fa5]
匹配:任意中文字符

正则:^[\u4e00-\u9fa5]+$
匹配:纯中文字符串

10. 用户名验证(字母开头,5-16位字母数字下划线)

正则:^[a-zA-Z][a-zA-Z0-9_]{4,15}$
匹配:user123、hello_world、User1

11. 密码验证(至少8位,包含大小写字母和数字)

正则:^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$
匹配:Abc12345、Password1

12. HTML标签匹配

正则:<[^>]+>
匹配:<div>、</div>、<img src="a.jpg" />

正则:<(\w+)[^>]*>.*?<\/\1>
匹配:<div>hello</div>、<p>world</p>

各语言中的正则表达式使用

1. JavaScript

// 创建正则表达式
const regex = /abc/gi;
const regex2 = new RegExp('abc', 'gi');

// 测试是否匹配
regex.test('abcdef'); // true

// 查找匹配
'abcdefabc'.match(regex); // ['abc', 'abc']

// 替换
'abcdef'.replace(regex, 'xyz'); // 'xyzdef'

// 分割
'a,b,c'.split(/,/); // ['a', 'b', 'c']

// 捕获组
const match = '2016-04-17'.match(/(\d{4})-(\d{2})-(\d{2})/);
// match[1] = '2016', match[2] = '04', match[3] = '17'

2. Python

import re

# 编译正则表达式
regex = re.compile(r'abc', re.IGNORECASE)

# 测试是否匹配
regex.search('abcdef')  # <Match object>

# 查找所有匹配
regex.findall('abcdefabc')  # ['abc', 'abc']

# 替换
regex.sub('xyz', 'abcdef')  # 'xyzdef'

# 分割
re.split(r',', 'a,b,c')  # ['a', 'b', 'c']

# 捕获组
match = re.search(r'(\d{4})-(\d{2})-(\d{2})', '2016-04-17')
# match.group(1) = '2016', match.group(2) = '04', match.group(3) = '17'

3. PHP

// 测试是否匹配
preg_match('/abc/', 'abcdef'); // 1

// 查找所有匹配
preg_match_all('/abc/', 'abcdefabc', $matches); // $matches = [['abc', 'abc']]

// 替换
preg_replace('/abc/', 'xyz', 'abcdef'); // 'xyzdef'

// 分割
preg_split('/,/', 'a,b,c'); // ['a', 'b', 'c']

// 捕获组
preg_match('/(\d{4})-(\d{2})-(\d{2})/', '2016-04-17', $matches);
// $matches[1] = '2016', $matches[2] = '04', $matches[3] = '17'

4. Java

import java.util.regex.*;

// 编译正则表达式
Pattern pattern = Pattern.compile("abc", Pattern.CASE_INSENSITIVE);

// 测试是否匹配
Matcher matcher = pattern.matcher("abcdef");
matcher.find(); // true

// 替换
"abcdef".replaceAll("abc", "xyz"); // "xyzdef"

// 分割
"a,b,c".split(","); // ["a", "b", "c"]

// 捕获组
Pattern p = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher m = p.matcher("2016-04-17");
if (m.find()) {
    // m.group(1) = "2016", m.group(2) = "04", m.group(3) = "17"
}

正则表达式实战技巧

1. 从简单开始,逐步构建: 不要试图一次写出复杂的正则表达式,应该从简单的部分开始,逐步添加,每一步都测试,确保正确。

2. 使用在线工具测试: 有很多在线正则表达式测试工具,如regex101.com、regexr.com、debuggex.com等,可以实时测试正则表达式,查看匹配结果,非常方便。

3. 注释复杂的正则表达式: 复杂的正则表达式很难读懂,应该加上注释说明。可以使用x修饰符忽略空白字符和注释,或者在代码中用注释说明。

4. 避免过度使用正则表达式: 正则表达式虽然强大,但是不是所有文本处理都需要用正则表达式。简单的字符串操作(如查找、替换、分割)用普通的字符串函数更简单、更高效。只有在需要复杂模式匹配时才用正则表达式。

5. 注意性能问题: 复杂的正则表达式可能会有性能问题,尤其是在处理大量文本时。要注意避免灾难性回溯(Catastrophic Backtracking),尽量简化正则表达式,使用非贪婪匹配,避免嵌套的量词。

6. 考虑边界情况: 写正则表达式时要考虑边界情况,如空字符串、特殊字符、超长字符串等。要确保正则表达式在各种情况下都能正确工作,不会出错或崩溃。

总结

正则表达式是一种强大的文本处理工具,掌握它能大大提高我们的文本处理效率。本文介绍了正则表达式的基本语法(普通字符、元字符、转义字符、字符类、预定义字符类、量词、贪婪与非贪婪、分组与捕获、反向引用、零宽断言、修饰符)、常用正则表达式(邮箱、手机号、URL、IP地址、身份证号、日期、时间等)、各语言中的使用方法(JavaScript、Python、PHP、Java)以及实战技巧。

正则表达式虽然看起来复杂,但是只要掌握了基本规则,多练习、多使用,就能逐渐熟练。希望这篇指南能帮助大家从入门到精通,掌握正则表达式这个强大的工具,让文本处理变得更加高效和轻松。

最后,记住一句话:"如果你有一个问题,想用正则表达式来解决,那么你现在有两个问题了。"正则表达式虽然强大,但是也要谨慎使用,不要过度使用,简单的问题用简单的方法解决。