正则表达式(Regular Expression,简称regex或regexp),是一种用来匹配字符串的强大工具。它通过一套特殊的语法规则,描述字符串的模式,可以用来查找、替换、验证、提取字符串。

在PHP开发中,正则表达式是一个非常有用的工具。无论是表单验证(邮箱、手机号、URL)、字符串处理(查找替换、提取信息)、文本解析(解析日志、解析HTML),还是数据清洗,正则表达式都能大大提高效率。

但正则表达式的语法比较特殊,初学者往往觉得很难、很晦涩。其实,只要掌握了基本的语法规则,正则表达式并没有那么难。今天分享PHP正则表达式的详解,从基础到进阶,帮你掌握这个强大的工具。

PHP中的正则表达式函数

PHP提供了两套正则表达式函数:PCRE(Perl兼容正则表达式)和POSIX扩展正则表达式。POSIX已经在PHP 5.3中废弃,PHP 7中移除,所以现在都用PCRE函数。

常用的PCRE函数:

  • preg_match():执行一个正则匹配,返回匹配次数(0或1)
  • pregmatchall():执行全局正则匹配,返回所有匹配结果
  • preg_replace():执行正则替换
  • preg_split():用正则表达式分割字符串
  • preg_grep():返回匹配模式的数组条目
  • preg_quote():转义正则表达式中的特殊字符

注意:PCRE正则表达式需要用分隔符包裹,常用/作为分隔符,也可以用#~等。

正则表达式基础语法

1. 普通字符

普通字符(字母、数字、汉字等)匹配它们本身。

preg_match('/hello/', 'hello world'); // 匹配成功,返回1

2. 元字符

元字符是正则表达式中有特殊含义的字符,需要转义才能匹配本身。

常用元字符:

  • .:匹配除换行符外的任意单个字符
  • ^:匹配字符串的开头
  • $:匹配字符串的结尾
  • *:匹配前面的字符0次或多次
  • +:匹配前面的字符1次或多次
  • ?:匹配前面的字符0次或1次
  • {n}:匹配前面的字符恰好n次
  • {n,}:匹配前面的字符至少n次
  • {n,m}:匹配前面的字符n到m次
  • []:字符类,匹配方括号中的任意一个字符
  • |:或,匹配左边或右边的表达式
  • ():分组,将括号内的表达式作为一个整体
  • \:转义字符,转义元字符

3. 字符类

[]用来定义字符类,匹配方括号中的任意一个字符。

preg_match('/[abc]/', 'a'); // 匹配a、b、c中的任意一个
preg_match('/[a-z]/', 'm'); // 匹配a到z中的任意一个小写字母
preg_match('/[A-Z]/', 'M'); // 匹配A到Z中的任意一个大写字母
preg_match('/[0-9]/', '5'); // 匹配0到9中的任意一个数字
preg_match('/[a-zA-Z0-9]/', 'x'); // 匹配字母和数字
preg_match('/[^abc]/', 'd'); // ^在[]中表示取反,匹配不是a、b、c的字符

4. 预定义字符类

正则表达式提供了一些预定义的字符类,方便使用:

  • \d:匹配任意数字,等价于[0-9]
  • \D:匹配任意非数字,等价于[^0-9]
  • \w:匹配任意字母、数字、下划线,等价于[a-zA-Z0-9_]
  • \W:匹配任意非字母、数字、下划线
  • \s:匹配任意空白字符(空格、制表符、换行符等)
  • \S:匹配任意非空白字符

5. 量词

量词用来指定前面的字符匹配多少次:

  • *:0次或多次,等价于{0,}
  • +:1次或多次,等价于{1,}
  • ?:0次或1次,等价于{0,1}
  • {n}:恰好n次
  • {n,}:至少n次
  • {n,m}:n到m次
preg_match('/a*/', 'aaa'); // 匹配0个或多个a
preg_match('/a+/', 'aaa'); // 匹配1个或多个a
preg_match('/a?/', 'a');   // 匹配0个或1个a
preg_match('/a{3}/', 'aaa'); // 匹配恰好3个a
preg_match('/a{2,}/', 'aaa'); // 匹配至少2个a
preg_match('/a{2,4}/', 'aaa'); // 匹配2到4个a

6. 贪婪与非贪婪

量词默认是贪婪的,会尽可能多地匹配。在量词后面加?,变成非贪婪,会尽可能少地匹配。

$str = '<div>hello</div><div>world</div>';
preg_match('/<div>.*<\/div>/', $str, $matches); // 贪婪,匹配整个字符串
echo $matches[0]; // <div>hello</div><div>world</div>

preg_match('/<div>.*?<\/div>/', $str, $matches); // 非贪婪,只匹配第一个
echo $matches[0]; // <div>hello</div>

7. 分组和捕获

()用来分组,将括号内的表达式作为一个整体,同时捕获匹配的内容。

preg_match('/(\d{4})-(\d{2})-(\d{2})/', '2015-07-05', $matches);
echo $matches[0]; // 2015-07-05(整个匹配)
echo $matches[1]; // 2015(第一个分组)
echo $matches[2]; // 07(第二个分组)
echo $matches[3]; // 05(第三个分组)

非捕获分组(?:)只分组,不捕获。

preg_match('/(?:\d{4})-(\d{2})/', '2015-07', $matches);
echo $matches[1]; // 07(只有一个捕获分组)

8. 或运算

|表示或,匹配左边或右边的表达式。

preg_match('/cat|dog/', 'I have a dog'); // 匹配cat或dog
preg_match('/(red|green|blue)/', 'The sky is blue'); // 匹配red、green或blue

9. 锚点

锚点用来匹配位置,而不是字符:

  • ^:匹配字符串开头
  • $:匹配字符串结尾
  • \b:匹配单词边界
  • \B:匹配非单词边界
preg_match('/^hello/', 'hello world'); // 匹配开头的hello
preg_match('/world$/', 'hello world'); // 匹配结尾的world
preg_match('/\bcat\b/', 'the cat is black'); // 匹配单词cat,不匹配category

10. 修饰符

正则表达式的分隔符后面可以加修饰符,改变匹配行为:

  • i:不区分大小写
  • m:多行模式,^$匹配每行的开头和结尾
  • s:点号.匹配包括换行符在内的所有字符
  • x:忽略正则表达式中的空白和注释
  • u:UTF-8模式,正确处理多字节字符(中文)
preg_match('/hello/i', 'HELLO WORLD'); // 不区分大小写,匹配成功
preg_match('/^hello/m', "hello\nworld"); // 多行模式
preg_match('/hello.*world/s', "hello\nworld"); // 点号匹配换行符

常用正则表达式示例

1. 邮箱验证

$email = 'test@example.com';
if (preg_match('/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', $email)) {
    echo "邮箱格式正确";
} else {
    echo "邮箱格式错误";
}

2. 手机号验证

$phone = '13812345678';
if (preg_match('/^1[3-9]\d{9}$/', $phone)) {
    echo "手机号格式正确";
} else {
    echo "手机号格式错误";
}

3. URL验证

$url = 'http://www.example.com/path?query=1';
if (preg_match('/^https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/[^\s]*)?$/', $url)) {
    echo "URL格式正确";
} else {
    echo "URL格式错误";
}

4. IP地址验证

$ip = '192.168.1.1';
if (preg_match('/^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/', $ip)) {
    echo "IP地址格式正确";
} else {
    echo "IP地址格式错误";
}

5. 日期验证

$date = '2015-07-05';
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $date, $matches)) {
    echo "年:" . $matches[1] . ",月:" . $matches[2] . ",日:" . $matches[3];
}

6. 提取HTML标签内容

$html = '<div class="title">Hello World</div>';
preg_match('/<div class="title">(.*?)<\/div>/', $html, $matches);
echo $matches[1]; // Hello World

7. 提取所有链接

$html = '<a href="http://example.com">链接1</a><a href="http://test.com">链接2</a>';
preg_match_all('/<a href="(.*?)">(.*?)<\/a>/', $html, $matches);
foreach ($matches[1] as $i => $url) {
    echo "链接" . ($i+1) . ":" . $url . ",文字:" . $matches[2][$i] . "\n";
}

8. 替换敏感词

$text = '这是一个敏感词测试,敏感词会被替换';
$badWords = ['敏感词', '测试'];
foreach ($badWords as $word) {
    $text = preg_replace('/' . $word . '/', '***', $text);
}
echo $text; // 这是一个*** ***,***会被替换

9. 分割字符串

$text = 'a,b,c,d,e';
$parts = preg_split('/,/', $text);
print_r($parts); // Array([0]=>a [1]=>b [2]=>c [3]=>d [4]=>e)

10. 去除HTML标签

$html = '<p>Hello <b>World</b></p>';
$text = preg_replace('/<[^>]+>/', '', $html);
echo $text; // Hello World
// 也可以用strip_tags()函数

正则表达式的最佳实践

  1. 不要过度使用正则:能用字符串函数(strpos、str_replace、substr等)解决的,就不要用正则,字符串函数更快。
  2. 测试正则表达式:写好正则后,一定要测试,确保匹配正确。可以用在线正则测试工具(如regex101.com)测试。
  3. 注意性能:复杂的正则表达式可能很慢,尤其是在处理大量数据时。尽量简化正则,避免灾难性回溯。
  4. 使用pregquote转义:如果正则表达式中包含用户输入,一定要用pregquote()转义特殊字符。
  5. 添加注释:复杂的正则表达式,可以用x修饰符添加注释,提高可读性。
  6. 使用UTF-8模式:处理中文时,一定要加u修饰符,否则可能匹配不正确。
  7. 避免贪婪匹配:默认的贪婪匹配可能导致匹配过多,用?改成非贪婪。

常见错误

  1. 忘记分隔符:PCRE正则必须用分隔符包裹,如/pattern/
  2. 没有转义特殊字符.*+等元字符需要转义才能匹配本身。
  3. 贪婪匹配导致匹配过多:用?改成非贪婪。
  4. 没有加锚点:验证字符串格式时,一定要加^$,否则可能部分匹配。
  5. 中文匹配问题:处理中文时加u修饰符。
  6. pregmatch只匹配一次:需要匹配所有结果时,用pregmatch_all。

总结

正则表达式是PHP开发中非常有用的工具,掌握它能大大提高字符串处理的效率。

核心要点:

  1. 基础语法:普通字符、元字符、字符类、预定义字符类、量词、分组、或运算、锚点、修饰符
  2. 常用函数:pregmatch、pregmatchall、pregreplace、pregsplit、preggrep、preg_quote
  3. 常用正则:邮箱、手机号、URL、IP、日期、HTML标签提取、敏感词替换
  4. 最佳实践:不要过度使用、测试、注意性能、转义用户输入、添加注释、UTF-8模式
  5. 常见错误:忘记分隔符、没有转义、贪婪匹配、没有锚点、中文匹配问题

正则表达式虽然看起来复杂,但只要掌握了基本语法,多练习,多使用,就能熟练掌握。希望这篇文章能帮你掌握PHP正则表达式。