虽然学的是 PHP 中的正则表达式,
但是正则是一种通用的规则和语法,所以学好正则可以在更广的方向上应用正则的语法和规则,
而且正则是每个程序员必须具备的,可以帮我们减轻很多工作量
什么是正则?
在编写程序中,经常会遇到某些“复杂规则的字符串”,
比如邮件地址格式、手机号码等,
正则表达式就是用于描述这些的规则的语法。
正则主要有四个应用
1. 分割
2. 匹配(最重要)
3. 查找
4. 替换
PHP 中两个常用的正则函数
以 prel 语言为基础的正则函数,该函数返回布尔值
1 | preg_match(mode, string subject, array matches); |
1. mode 正则的语法,需要一个起始和结束符号,使用 / 反斜起始和结束,也可以使用 # 井号
2. string subject 需要正则的内容
3. array matches 正则的结果,不一定是一个结果,所以结果是一个数组的形式
以 POSIX 为基础的正则函数(Unix、Script),正则规则不需要起始和结束符号,该函数已经废弃了
1 | ereg (mode, string subject, array regs); |
正则表达式中的元素
1. 原子:普通字符 a-z A-Z _ 0-9、原子表、转义字符
2. 元字符:有特殊功能的字符,比如,#井号、*星号、^等特殊符号...
3. 模式修正符:增加提高正则的功能,让正则应用的更加广泛,系统内置的字符 i、m、S、U...
一、正则表达式中的“原子”
正则语法规则里面必须要包含一个原子
正则表达式中的原子:
1. 最常见的字符 a-z A-Z _ 0-9
2. 圆括号包起来的叫做单元符号 (abc)
3. 方括号包起来叫原子表,原子表中的 ^ 代表排除或相反内容 [abc] [^abc]
4. 转义字符串 \d、\D、\w、\W ...
1、常见的字符
最常见原子字符的匹配
a-z A-Z _ 0-9 是我们最常见的通用字符,直接写这些字符就可以匹配了,
比如匹配 a,写一个正则 /a/,就可以找一下整个字符串中有多少个 a
示例,
preg_match 函数
第一个参数,正则规则,需要一个起始和结束符号 #abc#
第二个参数,正则的内容是一个字符串,字符串里面包含了 abc,匹配成功
第三个参数,匹配结果是一个数组(如果匹配不成功,返回一个空数组 Array ( ) )
1 | preg_match( '#abc#' , "sdfafabcdsdc" , $arr ); |
preg_match 函数返回一个布尔值,
写一个 if 条件语句,匹配成功输出数组的第1位 arr[0]
5 | if (preg_match( $mode , $str , $arr )){ |
/sbss/ 该规则匹配不成功,因为字符串中没有 sbss
5 | if (preg_match( $mode , $str , $arr )){ |
这就是最简单的原子匹配,匹配数字也是一样的
比如匹配数字 99
7 | $str = "sdfafabc99dsdc" ; |
9 | $str = "sdfafabc9ds9dc" ; |
2. 单元符号
(abc) 圆括号的作用是
1. 作为一个整体模块,会把 abc 看做一个完整的内容。目前看不出太大的区别,后面到原子符的时候就明白了
2. 它放会在内存当中,下一次可以调用括号里面的正则内容
(abc) 作为一个整体,必须要有一个完整的 abc 才能匹配,
不会拆分着看,只出现 a、b、bc、ab,这些都是不能匹配的
3 | $str = "sdfafabc99dsdc" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
3、原子表
[abc] 方括号括起来叫原子表,
与圆括号的完整体匹配相反,正则的字符串中出现了 a 或 b 或 bc,任何符合方括号中的有字母都可以匹配
[^abc] 原子表中的 ^ 代表排除或相反,
意思原子表中任何一个字符都不能匹配,只匹配除 abc 之外的内容,就是排除原子表中的内容
示例
可以匹配到一个 8,因为字符串中的数字 8,符合 [98] 原子表中的规则
3 | $str = "hhhhhhh8jjjj9lllll" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
也就说是在整个字符串中,只要有一个原子表中的数字就可以匹配成功
如果原子表前加一个 ^ 符号,除了 9 和 8 以外的所有内容。因为没有贪婪匹配,只匹配出字符串的第一个字母 h
3 | $str = "hhhhhhh8jjjj9lllll" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
4、转义字符
转义字符其实就是对一些常用的规范做一个优化
转义字符 |
|
\d | 包含所有数字 [0-9] |
\D | 除了所有数字以外 [^0-9] |
\w | 英文字符、下划线、数字 [z-aA-Z_0-9],包含了所有的常见字符 |
\W | 排除所有英文字符、下划线、数字 [^a-zA-Z_0-9] |
\s | 包含空白区域,如回车、换行、分页 [\f\n\r] |
... |
|
[0-9] 意思是只匹配字符串里面的数字
3 | $str = "hhhhhhh8jjjj9lllll" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
[0-9] 是常用的内容,可以写转义字符 \d
3 | $str = "hhhhhhh8jjjj9lllll" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
[^0-9] 排查所有的数字,可以写大写的 \D
3 | $str = "hhhhhhh8jjjj9lllll" ; |
\w 小写w,
匹配普通字符 [z-aA-Z_0-9],包括英文字母、数字、下划线
\W 大写W,
匹配排除了普通字符以外的特殊字符 @
3 | $str = "hhhhhhh8jj@#jj9lllll" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
\s 小写 s 匹配到了字符串里面的回车换行,虽然我们看不到回车换行
07 | if (preg_match( $mode , $str , $arr )){ |
实际上,在字符串中加一个 \n 也可以匹配成功,它代表回车换行
3 | $str = "hhhhhhh8jj@#\njj9lllll" ; |
Ps:
RegexTester(正则测试者) 微软的正则表达式测试工具
1. 下载地址 https://apps.microsoft.com/detail/9n0pbq11r3df?hl=zh-cn&gl=US
2. 需要安装微软的 NET Framework 2.0
2. 使用方法
Regex 写正则表达式,比如 [a-z]
Source 写正则的字符串内容,比如 sdfs2d3f4sd54fs6fSDFF
F5 键
Matches 正则出来的内容,所有的小写字母
二、正则表达式中的“元字符”
元字符可以理解成为特殊符号,也可以理解称为运算符号
元字符 | 作用 |
|
* | 匹配前一个内容的0次1次或多次 |
|
+ | 匹配前一个内容的1次或多次 | 但不能匹配0次,就像平时运算的 + 号,加 0 是没有意义的 |
? | 匹配前一个内容的0次或1次 | 跟上面的 * 、? 最大的区别是有次数限制,最多匹配1次 |
. | 匹配内容的0次1次或多次 | 但不包含回车换行 |
| | 选择匹配,类似 || 或运算符 | 因为这个运算符合是弱类型,导致 | 前面或后面不管是否加括号,都会做为整体匹配 |
^ | 匹配字符串首部内容 |
|
$ | 匹配字符串尾部内容 |
|
\b | 匹配单词边界,边界可以是空格或者特殊符号 | 主要用来匹配单词,国外认为匹配单词是一个重要的运算,所以放到了元字符这里 |
\B | 匹配除带单词边界以外内容 |
|
{m} | 匹配前一个内容的重复次数为M次 |
|
{m,} | 匹配前一个内容的重复次数大于等于M次 |
|
{m,n} | 匹配前一个内容的重复次数M次到N次 |
|
( ) | 合并整体匹配,并放入内存 | 在同一个正则表达式,可使用 \1 \2… 调出来依次使用 |
* 星号
规则中的 o* 是一体的,
1. 字母 o 只是做为和 * 星号匹配, /go*gle/ o 不算在正则表达式中的内容,星号 * 表示字母 o 可以出现0次1次或多次
2. 字符串中的 google 符合匹配规则
3 | $str = "abcdefgooglehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
字母 o 出现0次1次或多次都可以匹配成功
3 | $str = "abcdefgglehijk" ; |
5 | $str = "abcdefgoglehijk" ; |
7 | $str = "abcdefgooooooglehijk" ; |
go*g*le 同样的字母 g 归属了 * 号,g 也可以出现0次1次或多次,不出现也可以匹配成功
3 | $str = "abcdefgooooolehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
+ 加号
加号跟星号基本类似,但是不能0次,必须要有1次或多次
3 | $str = "abcdefgooglehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
o+ o归属于加号了,正则规则中不存在o,字符串中 o 必须要出现一次,不出现匹配不成功
3 | $str = "abcdefgglehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
换成星号可以0次,这是星号和加号的区别
字母 o 出现一次或多次都可以匹配成功
3 | $str = "abcdefgoglehijk" ; |
5 | $str = "abcdefgoooooglehijk" ; |
? 问号
只能出现1次或0次,两个 o 匹配不成功
3 | $str = "abcdefgooglehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
o? 匹配前一个字母 o 的0次或1次,也就是说最多只能出现1次
3 | $str = "abcdefgoglehijk" ; |
5 | $str = "abcdefgglehijk" ; |
? 只有两种选择,
1次或0次,不能超过1次
. 点
*号、+号、?号前面要有一个字母,不然不知道匹配什么内容,
. 点不是匹配前一个内容,点本身作为内容,代表任意的字符,除了回车以外的任何字符
比如 .? 可以是任意字符的0次或1次
3 | $str = "abcdefgjglehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
把 .? 问号换成 .* 星号,
.* 星号前面这点可以是任何内容,所有的内容,多少次都是可以匹配
3 | $str = "abcdefg0000000000glehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
只要符合这个 g.*gle 格式,中间任意内容都可以
3 | $str = "abcdefg_____glehijk" ; |
5 | $str = "abcdefgdgdsdfglehijk" ; |
.* 这个配合是一个经典的应用,任何字符串都可以通过
| 选择匹配
字符串中有 google 可以匹配
1 | $mode = "/google|baidu/" ; |
3 | $str = "abcdgoogleefghijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
和原子表有点类似
原子表 [baidu] 是单个字符来匹配
google | baidu 前后作为整体单词(模块)匹配
把字符中的 google 换成 baidu 也可以匹配成功
1 | $mode = "/google|baidu/" ; |
3 | $str = "abcdbaiduefghijk" ; |
防止 sql 注入功能
1 | $check = preg_match( '/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/' , $sql ); |
^ 首部匹配
^ 要求 google 在字符串开头出现,不是在开头出现所以匹配不成功
3 | $str = "abcdefgooglehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
google 必须在字符串开头才能匹配成功
3 | $str = "googleabcdefhijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
$ 匹配字符串尾部内容
字符串必须以 google 结束
3 | $str = "abcdefhijkgoogle" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
^ 和 $ 一起使用,
以 abc 开始,以 google 结束,这两个不能连着,中间要有内容分开
1 | $mode = "/^abc.*google$/" ; |
3 | $str = "abcdefhijkgoogle" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
匹配成功,整个字符串符合正则的规则
中间内容只能出现0次或1次,匹配就不成功了
1 | $mode = "/^abc.?google$/" ; |
3 | $str = "abcdefhijkgoogle" ; |
\b 匹配单词边界
is 前后必须是分界符的,前后有空格可以匹配
5 | if (preg_match( $mode , $str , $arr )){ |
is 后面没有分割匹配不成功
横杠可以作为分界符
\B 不允许有分界符
is 前后没有分割符匹配成功
5 | if (preg_match( $mode , $str , $arr )){ |
{m}
限制前面的字母 o 必须出现 1 次
3 | $str = "abcdefgoglehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
出现 2 次匹配不成功
3 | $str = "abcdefgooglehijk" ; |
限制出现 5 个字母
3 | $str = "abcdefgoooooglehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
{m,}
至少要有 5 个字母 o,不能少于 5 个,多着不限 6 个字母匹配成功
3 | $str = "abcdefgooooooglehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
{m,n}
2 到 5 之间的都可以匹配成功,中间有 4 个字母 o 可以匹配成功
3 | $str = "abcdefgooooglehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
超过 5 个匹配不成功
3 | $str = "abcdefgoooooglehijk" ; |
5 | $str = "abcdefgooooooglehijk" ; |
( ) 括号
abc 作为整体匹配,中间必须出现的 abc。这个上节课就学过了,下面看如何调用
3 | $str = "abcdefgoabcglehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
同一个正则表达式中,调用括号里面的内容,
第一个括号作为 \\1 在内存中保存,
\\1 相当于又把 abc 作为整体调用了,也就是 \\1 对应的就是 abc
1 | $mode = "/go(abc)g\\1le/" ; |
3 | $str = "abcdefgoabcgabclehijk" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
括号可以在年月日格式时候限制一下
前面用 - 分割后面也用 - 分割
前后用 / 分割后也用 / 分割
01 | $mode = "/2009(.*)02\\1(15)/" ; |
06 | if (preg_match( $mode , $str , $arr )){ |
\\1(15) 这里用括号,这样 \\115 都是数字会认为是内存中的第 115 个括号
日期的数字格式用正则限制
1 | $mode = "/[0-9]{2,4}(.*)[0-9]{2,4}\\1[0-9]{2,4}/" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
0-9 数字可以用 \d
1 | $mode = "/\d{2,4}(.*)\d{2,4}\\1\d{2,4}/" ; |
三、正则中的运算顺序
跟普通的数学的逻辑运算顺序是一样的,
在没有特殊的符号情况下,依然遵循从左到 → 右的运算规则,
除了运算顺序的规则,还有符号的优先级
符号的优先级
符号 | 优先级 |
( ) | 圆括号因为是内存处理所以最高 |
* ? + {} | 重复匹配内容其次 |
^ $ \b | 边界处理第三 |
| | 条件处理第四 |
| 最后按照运算顺序计算匹配 |
( ) 圆括号括起来的内容,是正则表达式中优先处理的
因为首先要读入内存中,才能在正则中第一次、第二次...调用括号里面的规则
* ? + {} 重复性的内容,
如果先用下面边界符,把边界的分开后,在去重复可能会出现错误,所以重复的排第二位
也就是首先确定有哪些重复的内容,然后在限定的边界符
\b 整个单词边界
^ $ 整个正则表达式的开始匹配,或是结束匹配
第二课学过,条件处理 | 会把左边和右边的内容,作为一个完整内容判断,
也就是说上面的功能都处理完后,然后将结果进行判断,
意思是前面一段正则,后面一段正则,两段正则都处理完了,在通过条件判断,这两个结果是不是得到内容的匹配
最后按照顺序依次继续计算匹配
四、模式修正符
模式修正符是对“正则表达式功能”的增强和补充
常用的修正符
修正符 | 作用 |
下面的字母是小写 |
i | 正则内容在匹配时候不区分大小写(默认是区分的) |
m | 在匹配首内容或者尾内容时候采用多行识别匹配 |
s | 将转义回车取消是为单行匹配如. 匹配的时候 |
x | 忽略正则中的空白 |
e | 该修正符已经废弃了 |
下面三个字母是大写 |
A | 强制从头开始匹配 |
D | 强制$匹配尾部无任何内容 \n |
U | 禁止贪婪匹配 只跟踪到最近的一个匹配符并结束,常用在采集程序上的正则表达式 |
i
默认是区分的,模式修正符 i 不区分大小写
5 | if (preg_match($mode, $str, $arr)){ |
m
主要是增强 ^符号 和 $符号 的功能,
这两个 ^ 和 $ 符号是匹配整个段落内容的开始和结尾,不管段落分了多少行或是回车,只匹配整个段落的开始和结束,
如果使用修正符 m,符号 ^ 不仅仅是匹配整个段落的开始,而是匹配每一行的开始
比如,^ 符号要求段落以 aaa 开头,字符串不是以 aaa 开头匹配不成功
3 | $str = "12345\naaa6789" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
字符串中 \n 的功能是回车换行,所以源码的格式中的 aaa 在第二行的首位置
12345
aaa6789
因为 aaa 出现在第二行的开头,加上修正符 m 可以匹配成功
3 | $str = "12345\naaa6789" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
结尾也一样,
修正符 m 不管有几行回车,只要有一行的结尾出现了 aaa 就可以匹配成功
3 | $str = "12345aaa\n6789" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
整个段落的结尾没有 aaa,在源码格式中 aaa 出现在第一行尾部,所以匹配成功
12345aaa
6789
s
小写 s 可以将段落中的回车换行取消,
比如 .* 可以匹配所有的任何内容,但是不包括回车的,
但是有时候段落里面有回车,修正符 s 可以把段落中的回车转义取消
下面的字符串的源码格式有换行,而 (.*) 是不包括回车换行的
aaabbb
cccddd
eeefffggg
修正符 s 可以把回车取消,实际是转换成空格
02 | $mode = "/aaa(.*)ggg/s" ; |
04 | $str = "aaabbb\ncccddd\neeefffggg" ; |
06 | if (preg_match( $mode , $str , $arr )){ |
s 增强了 .* 的功能,匹配大量的 html 代码做采集程序的时候可以上
x
忽略正则表达式当中的空白,
注意不是正则字符串的空白,是正则表达式中的空白
正则中 cc c 有空格匹配不到,x 可以忽略掉正则表达式中的空格
3 | $str = "aaabbbcccdddeee" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
上节课日期正则,可以使用 x 忽略正则表达式中的空格,又不影响正则
1 | $mode = "/2009(.*)02\\1 15/x" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
A
这里改用 preg_match_all() 全局匹配函数,
以 333 开头,并且修正符 m 多行匹配,可以匹配出两个 333
3 | $str = "333aaa\n333bbbcccdddeee" ; |
5 | if (preg_match_all( $mode , $str , $arr )){ |
修正符 A 强制必须从整个段落开头进行匹配,只能匹配出开头的一个 333,实际上 m 的功能就消失了,下一行不起作用了
3 | $str = "333aaa\n333bbbcccdddeee" ; |
5 | if (preg_match_all( $mode , $str , $arr )){ |
字符串开头没有 333 匹配不成功
3 | $str = "aaa\n333bbbcccdddeee" ; |
D
段落尾部是 eee 可以匹配成功,根据的是段落的内容不包括 \n 回车
3 | $str = "aaa\n333bbbccc\ndddeee\n" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
加上修正符 D 严格要求段落后面必须是干净的,有回车匹配失败。包括 A 也是前面的内容必须是干净
3 | $str = "aaa\n333bbbccc\ndddeee\n" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
U
不加修正符 U,默认是贪婪匹配
3 | $str = "大吊车<b>真厉害</b>,轻轻的<b>一抓</b>就起来" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
U 的作用只跟踪到近匹配的一个字符
3 | $str = "大吊车<b>真厉害</b>,轻轻的<b>一抓</b>就起来" ; |
5 | if (preg_match( $mode , $str , $arr )){ |
五、PHP中的正则函数
全局匹配
preg_match_all( string pattern, string subject, array matches [, int flags )
前两个参数和之前一样“正则模块”、“正则的字符串”,第三个参数正则的结果,该结果是二维数组
1 | $str = "网站名称{title}作者{author},内容是{content}以及其他" ; |
5 | preg_match_all( $mode , $str , $arr ); |
Array(
[0] => Array(
[0] => {title}
[1] => {author}
[2] => {content}
),
[1] => Array(
[0] => title
[1] => author
[2] => content
)
)
替换功能,类似 str_replace 字符串替换
preg_replace( mixed pattern, mixed replacement, mixed subject [, int limit] )
正则替换有四种用法
1. 正则表达式替换
2. 还可以替换“正则表达式数组”
3. 通过修正符 e 将替换的内容先运算在替换
4. 第四参数是可选的,功能是替换的次数
正则表达式替换
参数一:正则模块
参数二:所要替换的内容
参数三:所要替换的字符串
1 | $str = "青青的{title}上生活着{author},直到{content}搬到对岸的森林..." ; |
5 | echo preg_replace( $mode , '中文' , $str ); |
如果用字符串 str_replace 方法要替换三次
1 | $str = "青青的{title}上生活着{author},直到{content}搬到对岸的森林..." ; |
3 | $str = str_replace ( "{title}" , "草原" , $str ); |
4 | $str = str_replace ( "{author}" , "喜洋洋" , $str ); |
5 | $str = str_replace ( "{content}" , "灰太狼" , $str ); |
提示:
不仅仅可以替换正则表达式,也可以替换正则表达式数组
1. 替换内容可以是一个正则也可以是数组正则
2. 替换内容可以通过修正符 e 来解决替换执行内容
替换“正则表达式数组”的意思是,将相应的正则对应替换成不同的内容
1 | $str = "青青的{title}上生活着{author},直到{content}搬到对岸的森林..." ; |
3 | $mode = array ( "/{title}/" , "/{author}/" , "/{content}/" ); |
5 | $replace = [ "草原" , "喜洋洋" , "灰太狼" ]; |
7 | echo preg_replace( $mode , $replace , $str ); |
匹配出来的英文字符,先不进行替换,先对字符进行某种计算,比如 md5 加密,然后在进行替换
1 | $str = "青青的a上生活着b,直到c搬到对岸的森林..." ; |
5 | echo preg_replace( $mode , "md5(\\1)" , $str ); |
$mode = "/([a-z])/ie"
加上修正符 e 可以按照某种计算方式运行
但是 php5.5 以上版本 e 已废弃了
https://www.cnblogs.com/yangykaifa/p/6839586.html
可以使用正则替换过滤敏感词
1 | $str = "青青的CNM上生活着该死,直到孙子搬到对岸的森林" ; |
5 | echo preg_replace( $mode , "***" , $str ); |
还有第四个可选参数,功能是替换的次数
1 | echo preg_replace( $mode , "***" , $str , 1); |
3 | echo preg_replace( $mode , "***" , $str , 2); |
5 | echo preg_replace( $mode , "***" , $str , 3); |
正则分割,功能类似 explode 切割函数
preg_split ( string pattern, string subject [, int limit [, int flags]] )
通过多个符号分割
01 | $str = "async,异步,axios,ajax-fecth.promise" ; |
05 | echo $str = str_replace ( ',' , ',' , $str ); |
09 | $arr = preg_split( $mode , $str ); |
(
[0] => async
[1] => axios
[2] => ajax
[3] => fecth
[4] => promise
)