PHP中的正则表达式语法和相关函数

正则表达式由两种基本字符类型组成:原义(正常)文本字符和元字符。
元字符使正则表达式具有处理能力。元字符既可以是放在[]中的单个字符([a]表示匹配单个小写字符a),也可以是字符序列([a-d]表示匹配a、b、c、d之中的任意一个字符,而\w表示热议英文字母和数字以及下下划线)

字符组

字符组就是一组字符,在正则表达式中,它表示在同一个位置可能出现的各种字符
写法:[ab][314][#.?]

基本用法

[...]

1
2
preg_match('/[123]/', '2')    ==> 1
preg_match('/[123]/', '5') ==> 0

范围表示法(range)

[x-y]表示xy整个范围内的字符。

[0123456789]表示为[0-9][abcdefghijk]表示为[a-k]

1
2
3
preg_match('/[0-9]/', '6')        ==> 1
preg_match('/[0-9]/', '6') ==> 0
preg_match('/[0-9a-fA-F]/', '0') ==> 1 //匹配16进制数

字符组简记法(shorthands)

提供比范围表示法更简洁的表示方法,如\d表示[0-9]\w表示[0-9a-zA-z_]

php中支持的字符组简记:

  • \d 所有的数字,即[0-9]
  • \D 所有的非数字,与\d互斥
  • \w 所有的单词字符(字符、数字、下划线),即[0-9a-zA-Z_]
  • \W 所有的非单词字符,与\W互斥
  • \s 所有的空白字符,包括空格、制表符、回车符、换行符等空白字符
  • \S 所有的非空白字符,与\s互斥
1
2
3
4
5
6
7
8
9
10
11
preg_match('/\d/', '8')       ==> 1
preg_match('/\d/', 'a') ==> 0
preg_match('/\d[a-z]/', '0a') ==> 1

preg_match('/\w/', 'a'); ==> 1
preg_match('/\w/', '6'); ==> 1
preg_match('/\w/', '_'); ==> 1

preg_match('/\s/', ' '); ==> 1
preg_match('/\s/', "\t"); ==> 1
preg_match('/\s/', "\r"); ==> 1

元字符与转义

在范围表示法中,字符组中的横线-不能匹配横线字符,而是用来表示范围,这类字符叫做元字符
元字符除了-还有[、]、^、$…,他们都有特殊的意义。

当元字符想要表示普通字符的含义时(如-就只想表示横线字符),就需要转义处理(在元字符前加反斜线\)。
对于-,有一个例外情况,就是当它紧跟着字符组的左方括号[时,它就表示普通横线字符,此时不用转义。

1
2
3
4
5
6
7
8
preg_match('/[0\-9]/', "-")    ==> 1
preg_match('/[0\-9]/', "8") ==> 0
preg_match('/[0\-9]/', "0") ==> 1

preg_match('/[-09]/', "-") ==> 1

preg_match("/[0\\-9]/", "-") ==> 1
preg_match("/[0\-9]/", "-") ==> 1

仔细看上面第一个表达式和最后两个表示式。这里要注意:

  • 在php中,字符串既可以用单引号标注,也可以用双引号标注。
    两者的主要区别在于双引号字符串可以解析变量,而但引号字符串不能。
    另外,双引号字符串会处理字符转义,而单引号字符串不会(唯独会转义\)
  • 正则表达式是以字符串的方式提供的。在php中,双引号字符串本身也有关于转义的规定(如"\\"'\r'"\t"等),因此"0\\-9"'0\-9'是等价的。
  • 那么最后一个表达式为什么也可以匹配呢?
    这是因为,尽管php的正则表达式用字符串文字给出,但它与常见的字符串不完全一样 —— 如果某个转义序列可以有字符串识别,则对其进行转义处理;否则,将整个转义序列“原封不动”地保存下来
    最后一个表达式的\-并没有什么特殊意义,即将其原样保存

排除型字符组(Negated Character Class)

在方括号[…]中列出希望匹配的所有字符叫做“普通字符组”。在开方括号[之后紧跟一个脱字符^,写作[^…],表示“在当前位置,匹配一个没有列出的字符”。例如,[^0-9]匹配非数字字符。

1
preg_match('/[^0-9][0-9]/', "A1")   ==> 1

排除型字符组中的紧跟着开方括号[的脱字符^也是元字符,如果要匹配尖括号字符,需要进行转义处理。但是,不紧跟着开方括号[^就是普通字符,不需要转义。

1
2
3
4
preg_match('/[^0-9]/', '0')     ==> 0
preg_match('/[\^0-9]/', '0') ==> 1
preg_match('/[\^0-9]/', '^') ==> 1
preg_match('/[0-9^]/', '^') ==> 1

量词

首先看看^$两个特殊字符

  • ^放在正则表达式的开头,表示“定位到字符串的起始位置”;
  • $用在正则表达式的末尾,表示“定位到字符串的结束位置”。
1
2
3
4
5
6
7
preg_match('/\w\d/', '1a2b')    ==> 1 匹配到的是 a2
preg_match('/^\w\d/', '1a2b') ==> 0 第二个字符必须是数字
preg_match('/\w\d$/', '1a2b') ==> 0 必须以数字结尾
preg_match('/^\w\d/', 'a2b') ==> 1 匹配到的是a2
preg_match('/\w\d$/', '1a2') ==> 1 匹配到的是a2
preg_match('/^\w\d$/', '1a2') ==> 0 只能是两个字符,最后一个是数字
preg_match('/^\w\d$/', 'a2') ==> 1

量词的一般形式

如果要匹配一个邮政编码(6位数字),目前能写出来的正则表达式是^\d\d\d\d\d\d$
\d重复6次的写法很不科学,正则表达式肯定会有更方便的写法,也就是量词。量词的通用形式是{m,n}(注意,,后面不能有空格),它限定之前的元素能够出现的次数,m是下限,n是上限。其他常见的量词形式有:

量词 说明
{n} 之前的元素必须出现n次
{m,n} 之前的元素最少出现m次,最多出现n次
{m,} 之前的元素最少出现m次,出现次数无上限
{0,n} 之前的元素可以不出现,也可以出现,最多出现n次
1
2
3
4
5
6
preg_match('/^\d{6}$/', '100010')      ==> 1

preg_match('/^\d{4,6}$/', '123') ==> 0
preg_match('/^\d{4,6}$/', '1234') ==> 1
preg_match('/^\d{4,6}$/', '123456') ==> 1
preg_match('/^\d{4,6}$/', '1234567') ==> 0

常用量词

正则表达式还有三个常用的量词,分别是+?*

常用量词 {m,n}等价形式 说明
* {0,} 可能出现,也可能不出现,出现次数没有上限
+ {1,} 至少出现1次,出现次数没有上限
? {0,1} 出现0次或1次

点号

点号.是与量词搭配比较多得一个字符。一般情况下,点号.可以匹配除了换行符\n以外的任意字符。

1
2
3
preg_match('/^.$/', 'z')    ==> 1
preg_match('/^.$/', '8') ==> 1
preg_match('/^.$/', "\n") ==> 0

如果要使达到.能匹配的字符包含换行符\n的效果,可以使用自制通配符\s\S\w\W\d\D

1
preg_match('/^[\s\S]$/', "\n")   ==> 1

使.能匹配的字符包含换行符\n的另外一种方法是指定正则匹配时使用单行模式
在php中,可以使用模式修饰符和预定义常量两种方法来指定单行模式。关于正则匹配的模式,后面会详细介绍。

1
2
preg_match('/(?s)^.$/', "\n")  ==> 1  模式修饰符
preg_match('/^.$/s', "\n") ==> 1 预定义常量

匹配优先量词、忽略优先量词

先看一个例子:

很多语言中,都可以使用/*...*/来注释代码,如果是一个支持语法高亮的文本编辑器就要能够提取/*...*/注释块。很easy的,我们可以写出如下正则表达式:

1
2
3
$str = '/*this is a comment*/ /*this is another comment*/';
preg_match('/\/\*.*\*\//', $str, $arr);
echo $arr[0]; ==> /*this is a comment*/ /*this is another comment*/

可以看到,这个正则表达式出了点小问题,它把两个注释块匹配出来了,如果两个注释块中间有代码,那么代码也会匹配出来。

这是因为,我们介绍的*、+、?都是匹配优先量词(greedy quantifier,也称贪婪量词)。
匹配优先量词是指在拿不准是否要匹配的时候,优先尝试匹配。因此,$str中间的*/ /*都被.*匹配了。

正则表达式中利用忽略优先量词来解决上述问题。与*、+、?对应的忽略优先量词的形式是*?、+?、??
忽略优先量词在不确定是否要匹配时选择“不匹配”的状态

还是以提取注释块的代码为例:

1
2
preg_match('/\/\*.*?\*\//', $str, $arr);
echo $arr[0]; ==> /*this is a comment*/
匹配优先量词 忽略优先量词 限定次数
* *? 可能出现,可能不出现,出现次数没有上限
+ +? 至少出现1次,没有上限
? ?? 出现0次或1次
{m,n} {m,n}? 出现次数大于等于m,小于等于n
{m,} {m,}? 至少出现m次,没有上限
{0,n} {0,n}? 出现0次-n次

php中有指定非贪婪匹配模式的模式修饰符预定义常量,与忽略优先量词是一样的效果:

1
2
3
4
5
6
7
8
9
10
// 默认贪婪匹配
preg_match('/\/\*.*\*\//', $str, $arr); ==> /*this is a comment*/ /*this is another comment*/
// 预定义常量 指定非贪婪匹配
preg_match('/\/\*.*\*\//U', $str, $arr); ==> /*this is a comment*/
// 模式修饰符 指定非贪婪匹配
preg_match('/(?U)\/\*.*\*\//', $str, $arr); ==> /*this is a comment*/
// 同时使用 忽略优先量词 和 预定义常量
preg_match('/\/\*.*?\*\//U', $str, $arr); ==> /*this is a comment*/ /*this is another comment*/
// 同时使用 忽略优先量词 和 模式修饰符
preg_match('/(?U)\/\*.*?\*\//', $str, $arr); ==> /*this is a comment*/ /*this is another comment*/

括号

分组

上面介绍了量词,例子中量词都只能控制它前面的字符或字符组。那么量词能否控制连续的字符或字符组呢,如控制一个单词hello出现或者不出现。这就要用到正则表达式的分组功能(子表达式),使用圆括号(...)实现分组(子表达式)。

1
2
3
4
5
6
7
8
9
// 量词限定前面一个字符
preg_match('/^hello?, world$/', 'hello, world'); ==> 1
preg_match('/^hello?, world$/', 'hell, world'); ==> 1
preg_match('/^hello?, world$/', ', world'); ==> 0

// 量词限定一个单词
preg_match('/^(hello)?, world$/', 'hello, world'); ==> 1
preg_match('/^(hello)?, world$/', 'hell, world'); ==> 0
preg_match('/^(hello)?, world$/', ', world'); ==> 1

多选结构

多选结构(alternative)的形式是(...|...),在括号内以竖线|分开多个子表达式,这些子表达式也叫多选分支(option)。

例如,匹配常见的11位手机号

1
preg_match('/(13[0-9]|15[0-356]|18[025-9])\d{8}/', '18521510001');    ==> 1

引用分组

正则表达式会保存每个(...)分组匹配的文本,这种功能叫捕获分组(capturing group)。在需要直接使用子表达式的时候非常有用

例如,提取<a>标签中的地址和描述文本

1
2
3
4
5
6
7
8
9
10
preg_match('/<a\s+href="([^\'"\s]*)">(.*?)<\/a>/', '<a href="github.com">visit github</a>', $arr);
print_r($arr);

结果:
Array
(
[0] => <a href="github.com">github</a>
[1] => github.com
[2] => visit github
)

正则表达式替换时也支持捕获分组。php中支持\num$num的形式替换,但是num不能大于10;另一种形式${num}可以大于10。

例如,日期的替换

1
2
preg_replace('/(\d{4})-(\d{2})-(\d{2})/', '$1年$2月$3日', '2015-08-25');    ==> 20150825
preg_replace('/(\d{4})-(\d{2})-(\d{2})/', '\1年\2月\3日', '2015-08-25'); ==> 20150825

非捕获分组

正则表达式默认会保存每个(...)匹配的文本,前面利用这个特性可以实现一些有用的功能。但是,有时候正则表达式比较复杂,(...)会出现的比较多,而此时仅仅是想实现分组或者多选的功能,而不需要捕获分组;同时,大量不需要的捕获分组可能会影响性能。
为了解决这种问题,正则表达式提供了非捕获分组(non-capturing group),它的形式是(?:...)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 捕获分组
preg_match('/(13[0-9]|15[0-356]|18[025-9])\d{8}/', '18521510001', $arr); ==> 1
print_r($arr);

结果:
Array
(
[0] => 18521510001
[1] => 185
)

// 非捕获分组
echo preg_match('/(?:13[0-9]|15[0-356]|18[025-9])\d{8}/', '18521510001', $arr); ==> 1
print_r($arr);

结果:
Array
(
[0] => 18521510001
)

断言

正则表达式中的有些结构不匹配真正的文本,只负责判断在某个位置左/右侧的文本是否符合要求,这种结构称为断言(assertion)。
常见的断言有三类:单词边界、行起始/结束位置、环视。

单词边界

单词边界(word boundary),记为\b
它匹配的是单词边界(一边是单词字符,另一边不是单词字符)的位置,而不是字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 单词边界
preg_match('/\b\w+\b/', 'word', $arr); // => 1
print_r($arr);

结果:
Array
(
[0] => word
)


// 匹配所有单词边界
preg_match_all('/\b\w+\b/', 'hello, world', $arr); // => 2
print_r($arr);

结果:
Array
(
[0] => Array
(
[0] => hello
[1] => world
)

)

这类匹配位置而不匹配字符的元素叫做锚点(anchor),下一节要介绍的^$等也是锚点。

行起始/结束位置

^:字符串的开始位置
$:字符串的结束位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$str = 'first line
second line
last line';

// 字符串起始位置
preg_match_all('/^\w+\b/', $str, $arr); // => 1
print_r($arr);
/*
Array
(
[0] => Array
(
[0] => first
)

)
*/

// 字符串结束位置
preg_match_all('/\b\w+$/', $str, $arr); // => 1
print_r($arr);
/*
Array
(
[0] => Array
(
[0] => line
)

)
*/

如果指定了多行模式(Multiline Mode),^$分别可以匹配行起始位置、行结束位置。
关于模式,将在后面详细介绍。指定多行模式的方式是使用模式修饰符(?m):在正则表达式之前加上(?m);或者是预定义常量的方式:/.../m

环视

环视(look-around)用来“停在原地,四处张望”,它本身也不匹配任何字符,用来限定它旁边的文本满足某种条件。

名字 记法 含义
肯定顺序环视 (?=...) 向右看看,右边出现了环视中的内容才匹配
否定顺序环视 (?!...) 向右看看,右边不出现环视中的内容才匹配
肯定逆序环视 (?<=...) 向左看看,左边出现了环视中的内容才匹配
否定逆序环视 (?<!...) 向左看看,左边不出现环视中的内容才匹配

比如在字符串格式化时匹配%s,要求%s的前面一个字符不能是%

1
2
preg_match('/(?<!%)%s/', "%s");   ==> 1
preg_match('/(?<!%)%s/', "%%s"); ==> 0

匹配模式

前面的内容中已经出现过了单行模式多行模式非贪婪模式
匹配模式是指匹配时使用的规则。常用的匹配模式还有不区分大小写模式注释模式

在开始介绍具体的模式之前,先介绍php中模式的两种具体实现/.../{modifier}...(?{modifier})...

模式修饰符 /.../{modifier} ...(?{modifier})...
示例 /<tr>.*<\/tr>/s <tr>(?s).*<\/tr>
名称(php手册) 模式修饰符 模式内修饰符
名称(正则指引) 预定义变量 模式修饰符
作用范围 整个表达式 不在分组(子表达式)中时,对它后面的全部正则表达式起作用;如果在分组(子表达式)中,则对它分组中的剩余部分起作用。在没有分组,且放在整个正则表达式最前面的时候相当于/.../{modifier}
支持程度 支持所有模式修饰符 支持部分模式修饰符
其他编程语言 可能不支持 一般都支持

不区分大小写模式

在html中是不区分大小写的,例如<td><tD><Td><TD>的作用是一样的。如果要从网页中提取<td>,不使用匹配模式的表达式应该是这样:
<[tT][dD]>

由于<td>标签只有两个字符,所以上面的写法还可以接受。但是如果标签是<script>呢?这里,就需要使用不区分大小写模式:/.../i...(?i)...。上面的正则表达式可以进一步写为:
/<td>/i或(?i)<td>

1
2
3
4
5
6
7
8
9
preg_match('/(?i)<td>/', '<tD>', $arr);
//preg_match('/<td>/i', '<tD>', $arr);
print_r($arr);
/*
Array
(
[0] => <tD>
)
*/

单行模式

形式:/.../s...(?s)...

作用:点号可以匹配换行符

多行模式

与单行模式没有关系。影响^$的匹配

形式:/.../x...(?m)...

注释模式

(?#...)的方法可以在正则表达式中添加注释

1
2
3
4
5
6
7
8
9
// (?#...)注释
preg_match('/(?#this is comment)(?i)<td>/', '<tD>', $arr);
print_r($arr);
/*
Array
(
[0] => <tD>
)
*/

非贪婪

形式:/.../U...(?U)...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 默认贪婪匹配
$str = '<tr><td>hello</td></tr><tr><td>world</td></tr>';
preg_match('/<tr>.*<\/tr>/', $str, $arr);
print_r($arr);
/*
Array
(
[0] => <tr><td>hello</td></tr><tr><td>world</td></tr>
)
*/

// 非贪婪匹配模式
$str = '<tr><td>hello</td></tr><tr><td>world</td></tr>';
preg_match('/<tr>.*<\/tr>/U', $str, $arr);
print_r($arr);
/*
Array
(
[0] => <tr><td>hello</td></tr>
)
*/

Unicode

要匹配中文等Unicode字符,最好是指定Unicode模式修饰符/.../u。如果不指定会有两个问题

  • GBK编码环境下,中文不能匹配
  • 无法利用[\x{4e00}-\x{9fff}]匹配中文
1
2
3
4
5
6
7
8
9
// 指定unicode模式
preg_match('/<td>[\x{4e00}-\x{9fff}]*<\/td>/u', '<td>姚明</td>', $arr);
print_r($arr);
/*
Array
(
[0] => <td>姚明</td>
)
*/

相关函数

在PHP中,有两套正则表达式函数库。
1.POSIX 扩展的正则表达式函数(ereg_)。
2.Perl 兼容的正则表达式函数(preg_)。

Perl兼容的函数库是后加的函数库,功能更加强大,另外JavaScriptPerl语言里面也会兼容这种模式,所以这里学习一下 Perl语言兼容的函数库。

php中 PCRE 正则共有如下函数:
preg_filter — 执行一个正则表达式搜索和替换
preg_grep — 返回匹配模式的数组条目
preg_last_error — 返回最后一个PCRE正则执行产生的错误代码
preg_match_all — 执行一个全局正则表达式匹配
preg_match — 执行匹配正则表达式
preg_quote — 转义正则表达式字符
preg_replace_callback_array — Perform a regular expression search and replace using callbacks
preg_replace_callback — 执行一个正则表达式搜索并且使用一个回调进行替换
preg_replace — 执行一个正则表达式的搜索和替换
preg_split — 通过一个正则表达式分隔字符串

常用的是preg_matchpreg_match_allpreg_replace这三个

preg_match

语法
preg_match($pattern, $subject [, &$matches [, $flags = 0 [, $offset = 0]]]) => int

功能:执行正则匹配,搜索subjectpattern给定的正则表达式的一个匹配.

参数

  • pattern string
    要搜索的模式(正则表达式)
  • subject string
    被正则匹配的字符串
  • matches string
    如果传入了这个参数,它将被用来存放搜索结果
    $match[0]包含完整模式匹配到的文本,$match[1]包含第一个捕获分组匹配到的文本,以此类推
  • flags int
    flags可以设置为PREG_OFFSET_CAPTURE(1)
    如果设置为1,对于每一个出现的匹配返回时会附加字符串偏移量(相对于目标字符串的)。
    注意:这会改变填充到matches参数的数组,使其每个元素成为一个由 第0个元素是匹配到的字符串,第1个元素是该匹配字符串 在目标字符串subject中的偏移量。
  • offset int
    通常,搜索从目标字符串的开始位置开始。可选参数offset用于指定从目标字符串的某个位置开始搜索(单位是字节)。
    如果offsetsubject的长度还要大则返回FALSE

返回值
返回pattern的匹配次数。 它的值将是0次(不匹配)或1次,因为preg_match()在第一次匹配后将会停止搜索。
如果发生错误将返回 FALSE

preg_match_all

语法
preg_match_all($pattern, $subject[, &$matches[, $flags = PREG_PATTERN_ORDER[, $offset = 0 ]]]) => int

功能
搜索subject中所有匹配pattern给定正则表达式的匹配结果并且将它们以flag指定顺序输出到matches中.
在第一个匹配找到后, 子序列继续从最后一次匹配位置搜索.

preg_match 用法完全一致,不过它匹配的是所有的信息。

返回值
返回完整匹配次数(可能是0),或者如果发生错误返回FALSE

preg_replace

语法
preg_replace ($pattern, $replacement, $subject[, $limit = -1[, &$count]]) => mixed

功能
搜索subject中匹配pattern的部分,以replacement进行替换。

参数

  • pattern string array
    要搜索的模式。可以使一个字符串或字符串数组。
    可以使用一些PCRE修饰符。
  • replacement string array
    用于替换的字符串或字符串数组。
    如果这个参数是一个字符串,并且pattern 是一个数组,那么所有的模式都使用这个字符串进行替换。
    如果patternreplacement 都是数组,每个pattern使用replacement中对应的 元素进行替换。如果replacement中的元素比pattern中的少, 多出来的pattern使用空字符串进行替换。
  • subject string array
    要进行搜索和替换的字符串或字符串数组。
    如果subject是一个数组,搜索和替换回在subject 的每一个元素上进行, 并且返回值也会是一个数组。
  • limit int
    每个模式在每个subject上进行替换的最大次数。默认是 -1(无限)。
  • count int
    如果指定,将会被填充为完成的替换次数。

返回值
如果subject是一个数组,preg_replace()返回一个数组,其他情况下返回一个字符串。

如果匹配被查找到,替换后的subject被返回,其他情况下 返回没有改变的subject。如果发生错误,返回NULL

模式修饰符(PRCE)

PHP提供了一些模式修饰符,模式修饰符中的空格,换行符会被忽略,其他字符会导致错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
i 忽略大小写,匹配不考虑大小写

m 多行独立匹配,如果字符串不包含[\n]等换行符就和普通正则一样。

s 设置正则符号 . 可以匹配换行符[\n],如果没有设置,正则符号.不能匹配换行符\n。

x 忽略没有转义的空格

e eval() 对匹配后的元素执行函数。(php7移除)

A 前置锚定,约束匹配仅从目标字符串开始搜索

D 锁定$作为结尾,如果没有D,如果字符串包含[\n]等换行符,$依旧依旧匹配换行符。如果设置了修饰符m,修饰符D 就会被忽略。

S 对非锚定的匹配进行分析

U 非贪婪,如果在正则字符量词后加“?”,就可以恢复贪婪

X 打开与perl 不兼容附件

u 强制字符串为UTF-8编码,一般在非UTF-8编码的文档中才需要这个。建议UTF-8环境中不要使用这个。
文章作者: Dar1in9
文章链接: http://dar1in9s.github.io/2020/04/27/php/正则表达式语法和PHP中的相关函数/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Dar1in9's Blog