Skip to content

正则表达式

正则表达式是一种描述字符串结果的语法规则,是一个特定的格式化模式,可以匹配、替换、截取匹配的字符串。

  • 正则表达式被用来检索或替换那些符合某个模式的文本内容。
  • 简单来说正则表达式就是完成字符串的增、删、改、查。

定界符

正则表达式语句由 分隔符(定界符)闭合包裹,分隔符可以是:

  • 任意非字母、数字
  • 非反斜线\
  • 非空白字符

经常使用的分隔符是:正斜线(/)hash符号(#)以及取反符号(~)

建议使用 // 做为定界符,因为与js一致。

php
$status = preg_match('/foo/', 'www.foo.com');
var_dump($status); // int(1)

边界匹配 / 行定位符(^与$)

行定位符是用来描述字符串的边界

符号说明
^匹配字符串的开始
$匹配字符串的结束,忽略换行符
php
// 表示是否匹配以12开头的字符串
$status = preg_match("/^12/", '1234');
var_dump($status); // int(1)
// 表示是否匹配以34结尾的字符串
$status = preg_match("/12$/", '1234');
var_dump($status); // int(1)

元字符

元字符是正则表达式中的最小元素,只代表单一(一个)字符。

正则表达式中具有特殊含义的字符称之为元字符,常用的元字符有:

\ 一般用于转义字符
^ 断言目标的开始位置(或在多行模式下是行首)
$ 断言目标的结束位置(或在多行模式下是行尾)
. 匹配除换行符外的任何字符(默认)
[ 开始字符类定义
] 结束字符类定义
| 开始一个可选分支
( 子组的开始标记
) 子组的结束标记
? 作为量词,表示 0 次或 1 次匹配。位于量词后面用于改变量词的贪婪特性。 (查阅量词)
- 量词,0 次或多次匹配

+ 量词,1 次或多次匹配
{ 自定义量词开始标记
} 自定义量词结束标记
php
//下面的\s匹配任意的空白符,包括空格,制表符,换行符。[^\s]代表非空白符。[^\s]+表示一次或多次匹配非空白符。
$p = '/^我[^\s]+(苹果|香蕉)$/';
$str = "我喜欢吃苹果";
if (preg_match($p, $str)) {
    echo '匹配成功';
}

元字符具有两种使用场景,一种是可以在任何地方都能使用,另一种是只能在方括号内使用,在方括号内使用的有:

\ 转义字符
^ 仅在作为第一个字符(方括号内)时,表明字符类取反

- 标记字符范围

其中^在反括号外面,表示断言目标的开始位置,但在方括号内部则代表字符类取反,方括号内的减号-可以标记字符范围,例如0-9表示0到9之间的所有数字。
php
//下面的\w匹配字母或数字或下划线。
$p = '/[\w\.\-]+@[a-z0-9\-]+\.(com|cn)/';
$str = "我的邮箱是Spark.eric@imooc.com";
preg_match($p, $str, $match);
echo $match[0];

元字符列表:

元字符说明范围
\d匹配任意一个数字[0-9]
\D与除了数字以外的任何一个字符匹配[^0-9]
\w与任意一个英文字母,数字或下划线匹配[a-zA-Z_0-9]
\W除了字母,数字或下划线外与任何字符匹配[^a-zA-Z_0-9]
\s与任意一个空白字符匹配[\n\f\r\t\v]
\S与除了空白符外任意一个字符匹配[^\n\f\r\t\v]
\n换行字符
\t制表符

显示不可打印的字符

字符含义
\a报警
\b退格
\f换页
\n换行
\r回车
\t字表符
php
var_dump(preg_match('/\d/', '1'));
var_dump(preg_match('/\D/', 'h'));
var_dump(preg_match('/\w/', '_'));
var_dump(preg_match('/\W/', '@'));
var_dump(preg_match('/\s/', ' '));
var_dump(preg_match('/\S/', 'h'));
var_dump(preg_match('/\n/', "\n"));
var_dump(preg_match('/\n/', '
'));
var_dump(preg_match('/\t/', "\t"));
// 以上都返回:int(1)

模式修饰符

模式修饰符的作用是设定模式,也就是正则表达式如何解释。php中主要模式如下表:

修饰符说明
i忽略大小写
m多文本模式
s单行文本模式
x忽略空白字符

原子表

在一组字符中匹配某个元字符,在正则表达式中通过元字符表来完成,就是放到 方括号[] 中。

原子表说明
[]只匹配其中的一个原子
[^]只匹配"除了"其中字符的任意一个原子
[0-9]匹配0-9任何一个数字
[a-z]匹配小写a-z任何一个字母
[A-Z]匹配大写A-Z任何一个字母
.表示除换行符外的任意字符

preg_match

匹配除了 678 以外的任何字符

php
$status = preg_match('/[^678]/', 678);
var_dump($status); // int(0)

匹配大小写字母

php
$status = preg_match('/[a-zA-Z]/', 'a');
var_dump($status); // int(1)

. 匹配字符

php
$status = preg_match('/./', 'abc');
var_dump($status); // int(1)

preg_split

通过原子表拆分字符串

php
$str = "1.jpg@2.jpg@3.jpg#4.jpg";
$arr = preg_split('/[@#]/', $str); // 按正则表达式拆分字符串
print_r($arr); // Array ( [0] => 1.jpg [1] => 2.jpg [2] => 3.jpg [3] => 4.jpg )

原子组

  • 如果一次要匹配多个元子,可以通过元子组完成
  • 原子组与原子表的差别在于原子组一次匹配多个元子,而原子表则是匹配成功表中的一个元字符就可以
  • 元字符组用()表示
php
$str = "官网www.food.com 论坛http://bbs.food.com,我的网名叫:foo";
$preg = "/(foo)d/is";
$newStr = preg_replace($preg, '<span style="color:#f00">\1</span>d', $str);
echo $newStr;
返回结果

官网www.d.com 论坛http://bbs.d.com,我的网名叫:foo

也可以使用$n的形式获取组数据,下例是将超链接替换为使用h2标签包裹

php
$content = <<<str
<a href="https://www.qq.com">腾讯网</a>
<a href="https://www.baidu.com">百度</a>
str;
echo preg_replace('/<a.*?>(.*?)<\/a>/i', '<p>$1</p>', $content);
返回结果

腾讯网

百度

分组别名

有时要为匹配到的结果使用字符串命名(别名),而不是使用默认的数字索引,使用 (?<foo>) 形式可以实现

php
preg_match('/(?<foo>\d+)/', 'food2021', $match);
print_r($match); // Array ( [0] => 2021 [foo] => 2021 [1] => 2021 )

也可以写成引号的形式,但要注意引号的嵌套问题

php
preg_match("/(?'foo'\d+)/", 'food2021', $match);
print_r($match); // Array ( [0] => 2021 [foo] => 2021 [1] => 2021 )

不记录分组

如果希望分组匹配,但结果中不保存分组结果时使用 (?:exp),下例中的结果中将不包括分组数据

php
preg_match('/(?:\d+)/', 'food2021', $match);
print_r($match); // Array ( [0] => 2021 )

断言匹配

断言匹配的内容不会保存到结果中,是对内容前后位置的限制操作。

正向先行断言

匹配指定内容前面的内容时使用 (?=exp)

php
// 匹配数字2前面的内容
preg_match("/.*(?=2)/", 'foo21', $match);
print_r($match); // Array ( [0] => foo )
// 如果有重复,会一直匹配到最后一个为止
preg_match("/.*(?=2)/", 'foo2021', $match);
print_r($match); // Array ( [0] => foo20 )

正向后行断言

匹配指定内容后面的内容时使用 (?<=exp)

php
// 匹配数字2前面的内容
preg_match("/(?<=2).*/", 'foo21', $match);
print_r($match); // Array ( [0] => 1 )
// 如果有多个重复,匹配到第一个就返回
preg_match("/(?<=2).*/", 'foo2021', $match);
print_r($match); // Array ( [0] => 021 )

负向先行断言

使用(?!exp)匹配后面不能是指定内容

php
// 匹配后面不是#的四位数值
preg_match_all("/\d{4}(?!#)/", '2030# 2022年', $match);
print_r($match); // Array ( [0] => Array ( [0] => 2022 ) )

负向后行断言

负向先行断言 指使用(?<!exp)匹配前面不能是指定内容

php
// 匹配前面不是#的四位数值
preg_match_all("/(?<!#)\d{4}/", '#2030 2022年', $match);
print_r($match); // Array ( [0] => Array ( [0] => 2022 ) )

选择修释

| 这个符号带表选择修释符,也就是 | 左右两侧有一个匹配到就可以。

php
// 把所有的 .baidu. 或者 .sina. 改为 .qq.
$str = "百度 www.baidu.com 新浪网 www.sina.com";
$preg = '/\.(baidu|sina)\./is';
$replace = '.qq.';
echo preg_replace($preg, $replace, $str); // 百度 www.qq.com 新浪网 www.qq.com

$preg = '/\.(baidu|sina)(\.com)/is';
// (baidu|sina) 代表 \1 (\.com) 代表 \2
$replace = '.\1\2';
echo preg_replace($preg, $replace, $str); // 百度 www.qq.com 新浪网 www.qq.com

匹配域名后缀

php
$str = "百度 www.baidu.com 谷歌 www.google.com";
$preg = '/www\.(baidu|google)\.com/';
$replace = '<a href="https://www.\1.com" target="_blank">\1.com</a>';
echo preg_replace($preg, $replace, $str);
返回结果

百度 baidu.com 谷歌 google.com

重复匹配 * + ? {n} {n,} {n,m}

因为正则最小单位是元字符,而我们很少只匹配一个元字符如a、b,所以基本上重复匹配在每条正则语句中都是必用到的内容。

如果要重复匹配一些内容时我们要使用重复匹配修饰符,包括以下几种:

符号说明
*重复零次或更多次
+重复一次或更多次
?重复零次或一次
{n}重复n次
{n,}重复n次或更多次
{n,m}重复n到m次
php
// ^表示开始,$表示结束。

// 点.表示除换行符外的任意字符 * 零个及空字符串也是可以的
var_dump(preg_match('/^.*$/', '')); // int(1)
var_dump(preg_match('/^.+$/', '')); // int(0)

var_dump(preg_match('/^\d?$/', '')); // int(1)
var_dump(preg_match('/^\d?$/', 1)); // int(1)
var_dump(preg_match('/^\d?$/', 12)); // int(0)

var_dump(preg_match('/^[0-9]+$/', '1976')); // int(1)
var_dump(preg_match('/^9?$/', '99')); // int(0)

var_dump(preg_match('/^9{2}$/', '99')); // int(1)
var_dump(preg_match('/^\d{2}$/', 17)); // int(1)
var_dump(preg_match('/^\d{2}/', 17)); // int(1)
var_dump(preg_match('/^\d{3}$/', 17)); // int(0)

var_dump(preg_match('/^[0-9]{2,}$/', '123')); // int(1)

// 只能2~3位
var_dump(preg_match('/^[0-9]{2,3}$/', '1234')); // int(0)

匹配域名

php
$web = 'sina.com.cn';
var_dump(preg_match('/^[a-z-0-9-]+\.(com|net|com\.cn|org|cn)$/', $web)); // int(1)

h1 标签内容加上超链接

php
$str = <<<html
<h1>hello world</h1>
html;
echo preg_replace('/<h1>(.+)<\/h1>/', '<a href="http://www.foo.com">\1</a>', $str);
返回结果

hello world

贪婪模式与懒惰模式

正则表达式中每个元字符匹配一个字符,当使用+之后将会变的贪婪,它将匹配尽可能多的字符,但使用问号?字符时,它将尽可能少的匹配字符,既是懒惰模式。

贪婪模式:在可匹配与可不匹配的时候,优先匹配

php
//下面的\d表示匹配数字
$p = '/\d+\-\d+/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0]; //结果为:010-12345678

懒惰模式:在可匹配与可不匹配的时候,优先不匹配

php
$p = '/\d?\-\d?/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0];  //结果为:0-1

当我们确切的知道所匹配的字符长度的时候,可以使用{}指定匹配字符数

php
$p = '/\d{3}\-\d{8}/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0]; //结果为:010-12345678

禁止贪婪

正则表达式在进行重复匹配时,默认是贪婪匹配模式,也就是说会尽量匹配更多内容,但是有的时候我们并不希望他匹配更多内容,这时可以通过?进行修饰来禁止重复匹配。

符号说明
*?重复任意次,但尽可能少重复
+?重复1次或更多次,但尽可能少重复
??重复0次或1次,但尽可能少重复
{n,m}?重复n到m次,但尽可能少重复
{n,}?重复n次以上,但尽可能少重复
php
$str = '123456';
preg_match('/\d+?/', $str, $match);
print_r($match); // Array ( [0] => 1 )
// 因为增加了 `?` 所以只匹配数字1

使用禁止贪婪,替换将h1标签内容倾斜处理

php
$str = "<h1>Hello</h1><h1>World</h1>";
$preg = '/<h1>(.*?)<\/h1>/';
$replace = '<h1><em>\1</em></h1>';
echo preg_replace($preg, $replace, $str);
返回结果

Hello

World

php
$str = "<h1>Hello</h1><h1>World</h1>";
$preg = '/<h1>(.+?)<\/h1>/';
preg_match_all($preg, $str, $match);
print_r($match);
返回结果
Array
(
    [0] => Array
        (
            [0] => <h1>Hello</h1>
            [1] => <h1>World</h1>
        )

    [1] => Array
        (
            [0] => Hello
            [1] => World
        )

)
php
$str = "<h1>Hello</h1><h1>World</h1>";
$preg = '/<h([1-6])>(.*)<\/h\1>/';
$replace = '<h\1><em>\2</em></h\1>';
echo preg_replace($preg, $replace, $str);
print_r($str);
返回结果

Hello

World

Hello

World

模式修正

正则表达式在执行时会按他们的默认执行方式进行,但有时候默认的处理方式总不能满足我们的需求,所以可以使用模式修正符更改默认方式。

符号说明
i不区分大小写字母的匹配
s将字符串视为单行,换行符做普通字符看待,使“.” 匹 配任何字符
x忽略空白及#符号,根据此特性可以添加正则注释
m^与$符匹配按行匹配
A强制从字符串开始匹配(多行时默认以每行开始设置)
D以$结尾时不允许后面有换行(使用\m时无效)
u匹配内容按utf-8处理,可以用来匹配utf-8的中文字符

i 不匹分大小

php
# 默认匹配
// 区分大小写
$str = "<h1>Hello</h1><H2>World</H2>";
$preg = '/<h([1-6])>.*?<\/h\1>/';
preg_match_all($preg, $str, $match);
print_r($match); // 只能匹配到h1

# 模式修正
// 不区分大小写字母的匹配
$preg = '/<h([1-6])>.*?<\/h\1>/i';
preg_match_all($preg, $str, $match);
print_r($match); // 能正常匹配到H2的字符串

// 把所有标签删掉
$preg = '/<h([1-6])>(.*?)<\/h\1>/i';
echo preg_replace($preg, '\2', $str); // HelloWorld

s 将字符串视为单行

删除换行符,继续把所有标签删掉

php
$str = "<H1>
Hello
</H1>";
$preg = '#<h1>.*?</h1>#i';
echo preg_replace($preg, '', $str); // Hello

// 去掉换行,并删除匹配到的数据,清空
$preg = '#<h1>.*?</h1>#is';
echo preg_replace($preg, '', $str); // 空

m ^与$符匹配按行匹配

php
$str = <<<eof
#1
admin
@#100
user
#2
foo
eof;
$preg = '/^#\d+/m';
preg_match_all($preg, $str, $matches);
print_r($matches); // Array ( [0] => Array ( [0] => #1 [1] => #2 ) )

echo preg_replace($preg, '', $str); // admin @#100 user foo

x 忽略空白、#,只做正则注释

php
$str = 'abc';
$preg = '/^a\w+
#这是正则的注释   
/x';
echo preg_replace($preg, '', $str);
php
$str = <<<eof
#1
admin
@#100
user
#2
foo
eof;

// 如果使用了/x,而#后面不想被忽略,正则表达式使用\#
$preg = '/^\#\d+/mx';
echo preg_replace($preg, '', $str); // admin @#100 user foo

$preg = '/^
\#\d  #匹配以数字开始
+.* $ #后跟任何字符
/mx';
echo preg_replace($preg, '', $str); // admin @#100 user foo

U 与禁止贪婪?相似,意为禁止贪婪匹配

php
$str = <<<eof
<h1>Hello</h1>
<h1>World</h1>
eof;
$preg = '#<h1>.*</h1>#sU';
preg_match_all($preg, $str, $matches);
print_r($matches);
返回结果
Array
(
    [0] => Array
        (
            [0] => <h1>Hello</h1>
            [1] => <h1>World</h1>
        )

)

\A 与 ^ 限定符使用效果相似,必须以目标字符串开始

验证邮箱

php
$str = '^&&#foo@foo.com';
// 严格限定是字符串开头,跟^相似
$preg = '/\w+@[\w\.]+/A';
preg_match_all($preg, $str, $matches);
print_r($matches); // Array ( [0] => Array ( ) )
// 匹配正则部分返回,而忽略其他字符(如开头的特殊字符)
$preg = '/\w+@[\w\.]+/';
preg_match_all($preg, $str, $matches);
print_r($matches); // Array ( [0] => Array ( [0] => foo@foo.com ) )

\D 不允许以换行结束

php
$str = <<<eof
3a\n
eof;

$preg = '/\d+a$/D';
preg_match_all($preg, $str, $matches);
print_r($matches); // Array ( [0] => Array ( ) )

$preg = '/\d+a$/';
preg_match_all($preg, $str, $matches);
print_r($matches); // Array ( [0] => Array ( [0] => 3a ) )

常用函数

preg_quote

如果模式中包含较多的分割字符,建议更换其他的字符作为分隔符,也可以采用preg_quote进行转义。

php
$p = 'http://';
$p = '/'.preg_quote($p, '/').'/';
echo $p;

preg_match

获取第一个匹配的内容

php
$str = '1@2@3';
preg_match('/\d+/', $str, $matches);
print_r($matches); // Array ( [0] => 1 )

preg_match_all

获取所有匹配的内容

php
$str = '1@2@3';
preg_match_all('/\d+/', $str, $matches);
print_r($matches); // Array ( [0] => Array ( [0] => 1 [1] => 2 [2] => 3 ) )

preg_split

通过正则表达式拆分字符串

php
$str = '1@2#3';
$arr = preg_split('/@|#/', $str);
print_r($arr); // Array ( [0] => 1 [1] => 2 [2] => 3 )

preg_replace

通过正则表达式替换

正则表达式的搜索与替换在某些方面具有重要用途,比如调整目标字符串的格式,改变目标字符串中匹配字符串的顺序等。

例如我们可以简单的调整字符串的日期格式:

php
$string = 'April 15, 2014';
$pattern = '/(\w+) (\d+), (\d+)/i';
$replacement = '$3, ${1} $2';
echo preg_replace($pattern, $replacement, $string); //结果为:2014, April 15

其中${1}与$1的写法是等效的,表示第一个匹配的字串,$2代表第二个匹配的。

将目标字符串$str中的文件名替换后增加em标签,例如index.php要替换成index.php

php
str = '主要有以下几个文件:index.php, style.css, common.js';
$p = '/\w+\.\w+/i';
$str = preg_replace($p, '<em>$0</em>', $str);
echo $str;

将拼接符@#转成破折号-拼接:

php
$str = '1@2#3';
echo preg_replace('/@|#/', '-', $str); // 1-2-3

preg_replace_callback

使用回调函数进行替换操作

php
$str = '1@2@3';
echo preg_replace_callback('/\d+/', function ($matches) {
    return $matches[0] + 100;
}, $str); // 101@102@103
最近更新