PHP100 正则表达式
虽然学的是 PHP 中的正则表达式,
但是正则是一种通用的规则和语法,所以学好正则可以在更广的方向上应用正则的语法和规则
正则主要作用
1. 分割
2. 匹配(重要)
3. 查找
4. 替换
什么是正则?
在编写程序中经常会遇到某些“复杂规则的字符串”,正则表达式就是用于描述这些的规则的语法,比如邮件地址格式、手机号码等...
以 prel 语言为基础的正则函数
1. mode 正则的语法,使用 / 反斜作为起始和结束,也可以使用 # 井号
string subject 所要正则的内容
array matches 正则的结果,因为不一定是一个结果,所以结果是一个数组的形式
2. 函数返回布尔值
preg_match(mode, string subject, array matches);
正则表达中包含的元素
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、\B、\W、\w、\s...
1、最常见字符(普通字符)
最常见字符的匹配,直接写这些字符就可以匹配了
比如匹配一个 'a'
需要一个起始和结束符号 '/a/',就可以找到整个字符串中有多少个 a,如果匹配不成功返回一个空数组 Array ( )
preg_match('/a/', "sdfafabcdsdc", $arr);
print_r($arr); // Array ([0] => a)匹配字符串中的 'abc'
1. 正则的语法使用反斜作为起始和结束 '/abc/' 也可以使用 # 井号
2. 函数返回一个布尔值,可以写一个 if 条件语句
3. "sdfafabcdsdc" 字符串中有 abc 匹配成功,输出数组的第 1 位 arr[0]
$mode = "/abc/";
$str = "sdfafabcdsdc";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 abc
}else{
echo "匹配不成功";
}/sbss/ 该规则匹配不成功,因为字符串中没有 sbss
$mode = "/sbss/";
$str = "sdfafabcdsdc";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0];
}else{
echo "匹配不成功"; // 匹配不成功
}匹配数字 /99/
$mode = "/99/"; $str = "sdfafabcdsdc"; // 没有数字99匹配不成功 $str = "sdfafabc9dsdc"; // 字符串中一个数字9匹配不成功 $str = "sdfafabc99dsdc"; // 字符串中包含两个数字99可以匹配成功 $str = "sdfafabc9ds9dc"; // 两个不挨着的数字9匹配不成功
2、单元符合(也叫子表达式)
(abc) 圆括号包起来的作用是
1. 作为一个整体模块,会把 abc 看做一个完整的内容
2. 它放会在内存当中,下一次可以调用括号里面的正则内容
(abc) 作为一个整体
必须要有一个完整的 abc 才能匹配
不会拆分,只出现 a、b、bc、ab 这些都是不能匹配的
$mode = "/(abc)/";
$str = "sdfafabc99dsdc";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 abc
}else{
echo "匹配不成功";
}3、原子表
[abc]
方括号括起来叫原子表,与圆括号的完整体匹配相反,
正则的字符串中出现了 a 或 b 或 bc,任何符合方括号中有的字母都可以匹配
[^abc]
原子表中的 ^ 符号代表排除或相反,
意思在原子表中任何一个字符都不能匹配,只匹配除 abc 以外的内容,就是排除原子表中的内容
[98]
可以匹配到一个 8,因为原子表中的数字 8,
也就说是在整个字符串中,只要有一个原子表中的数字就可以匹配成功
$mode = "/[98]/";
$str = "hhhhhhh8jjjj9lllll";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 9
}else{
echo "匹配不成功";
}[^98]
如果原子表前加一个 ^ 符号
除了 9 和 8 以外匹配字符串中所有内容,因为没有贪婪匹配,所以只匹配出字符串的第一个字母 h
$mode = "/[^98]/";
$str = "hhhhhhh8jjjj9lllll";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 h
}else{
echo "匹配不成功";
}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] 是 [0123456789] 的简写
$mode = "/[0-9]/";
$str = "hhhhhhh8jjjj9lllll";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 8
}else{
echo "匹配不成功";
}\d
[0-9] 是常用的内容,可以写元字符 \d
$mode = "/\d/";
$str = "hhhhhhh8jjjj9lllll";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 8
}else{
echo "匹配不成功";
}\D
[^0-9] 排查所有的数字,不匹配数字可以写大写的 \D
$mode = "/\D/";
$str = "hhhhhhh8jjjj9lllll";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 h
}else{
echo "匹配不成功";
}\w
小写 w 匹配所有的普通常见字符,包括英文字母、数字、下划线 [z-aA-Z_0-9]
\W
大写 W 排除了普通字符,只匹配特殊字符
$mode = "/\W/";
$str = "hhhhhhh8jj@#jj9lllll";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 @
}else{
echo "匹配不成功";
}\s
小写 s 匹配到了字符串里面的回车、换行、分页
字符串有回车换行,匹配成功
$mode = "/\s/";
$str = "hhhhhhh8jj@#
jj9lllll";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功
}else{
echo "匹配不成功";
}虽然我们看不到回车换行,
实际上在字符串中加一个 \n 也可以匹配成功,因为它代表回车换行
$str = "hhhhhhh8jj@#\njj9lllll";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功
}else{
echo "匹配不成功";
}5、微软的正则表达式测试工具
RegexTester(正则测试者)
1. 下载地址 https://apps.microsoft.com/detail/9n0pbq11r3df?hl=zh-cn&gl=US
2. 需要安装微软的 NET Framework 2.0
使用方法
Regex 写正则表达式 [a-z]
Source 写所要正则的字符串 sdfs2d3f4sd54fs6fSDFF
F5 键
Matches 正则出来的内容,所有的小写字母
二、特殊功能的字符
可以理解成为特殊符号或运算符,它完成的工作比较复杂不是简单的匹配,可以帮我们做一些简单的运算和筛选
| 字符 | 作用 | |
| 量词 | ||
| * | 匹配前一个内容的 0 次 1 次或多次 | |
| + | 匹配前一个内容的 1 次或多次,但不能匹配 0 次 | 就像平时运算的 + 号,加 0 是没有意义的 |
| ? | 匹配前一个内容的 0 次或 1 次 | 跟上面的 * 号 、? 号最大的区别是有次数限制,它最多匹配 1 次 |
| . | 匹配内容的 0 次 1 次或多次,但不包含回车换行 | 不是前一个内容,点它本身就是内容 |
| 运算符 | ||
| | | 选择匹配,类似 || 或运算符 | 因为这个运算符是弱类型,导致 | 前后不管是否加括号,都会做为整体匹配 |
| ^ | 匹配字符串首部内容 | /^abc/ 开头必须出现的是 abc |
| $ | 匹配字符串尾部内容 | /php100$/ 尾部必须出现 php100 |
| 单词边界 | ||
| \b | 匹配单词边界,边界可以是空格或者特殊符号 | 主要用来匹配单词,国外认为匹配单词是一个重要的运算,所以放到了元字符这里 |
| \B | 匹配非单词边界 | |
| 限制 | ||
| {m} | 匹配前一个内容的重复次数为 M 次 | |
| {m,} | 匹配前一个内容的重复次数大于等于 M 次 | |
| {m,n} | 匹配前一个内容的重复次数 M 次到 N 次 | |
| 子表达式 | ||
| () | 合并整体匹配,并放入内存,可使用 \1 \2... 调出来依次使用 | |
1、量词
* 星号
匹配前一个内容的 0 次 1 次或多次
/go*gle/
规则中的 o* 是一体的,也就是说字母 o 归属了 * 星号
星号 * 表示字母 o 可以出现 0 次 1 次或多次,出现两次 o 符合匹配规则
$mode = "/go*gle/";
$str = "abcdefgooglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 google
}else{
echo "匹配不成功";
}字母 o 出现 0 次 1 次或多次都可以匹配成功
出现的 0 次匹配成功 $str = "abcdefgglehijk"
出现的 1 次匹配成功 $str = "abcdefgoglehijk"
出现多次匹配成功 $str = "abcdefgooooooglehijk"
/go*g*le/
同样的字母 g 归属了 * 号
g 也可以出现 0 次 1 次或多次,g 不出现也是可以匹配成功的
$mode = "/go*g*le/";
$str = "abcdefgooooolehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 gooooole
}else{
echo "匹配不成功";
}
+ 加号
加号可以出现 1 次或多次(跟 * 星号基本类似,但是不能 0 次,至少要有一次)
o+
字母 o 已经归属于加号了,o 可以出现 1 次或多次
$mode = "/go+gle/";
$str = "abcdefgooglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 google
}else{
echo "匹配不成功";
}字母 o 必须要出现一次,如果不出现匹配不成功(如果换成 * 星号可以 0 次,这是星号和加号的区别)
$mode = "/go+gle/";
$str = "abcdefgglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0];
}else{
echo "匹配不成功"; // 匹配不成功
}字母 o 出现一次或多次都可以匹配成功
$str = "abcdefgoglehijk"; // 出现一次匹配成功 gogle $str = "abcdefgoooooglehijk"; // 多个也可以匹配成功 gooooogle
? 问号
? 号只有两种选择,1 次或 0 次
最多只能出现 1 次,两个字母 o 匹配不成功
$mode = "/go?gle/";
$str = "abcdefgooglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0];
}else{
echo "匹配不成功"; // 匹配不成功
}匹配前一个内容的 0 次或 1 次
$mode = "/go?gle/"; $str = "abcdefgoglehijk"; // 出现一次匹配成功 gogle $str = "abcdefgglehijk"; // 没有就是0次也可以匹配成功 ggle
. 点
代表任意的字符,除了回车以外的任何字符(点不是匹配前一个内容,点自己作为作为内容)
.?
可以是任意字符的 0 次或 1 次,abcdefgjglehijk 字母 j 出现一次匹配成功
$mode = "/g.?gle/";
$str = "abcdefgjglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 gjgle
}else{
echo "匹配不成功";
}.*
星号前面这点可以是任何内容
$mode = "/g.*gle/";
$str = "abcdefg0000000000glehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 g0000000000gle
}else{
echo "匹配不成功";
}也就说只要符合这个 g.*gle 格式,中间以任何内容多少次都可以匹配
$mode = "/g.*gle/"; $str = "abcdefg_____glehijk"; // 下划线可以匹配 g_____gle $str = "abcdefgdgdsdfglehijk"; // 任何内容都可以 gdgdsdfgle
.*
点和星配合是一个经典的应用,任何字符串都可以通过
2、运算符
| 选择匹配
把前面内容 google 和后面内容 baidu 作为一个整体,在字符串中去匹配,字符串中有完整 google 可以成功
$mode = "/google|baidu/";
$str = "abcdgoogleefghijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 google
}else{
echo "匹配不成功";
}有点类似于原子表
[abc] 原子表是里面的单个字母去匹配
而 | 是前后整个的单词的内容去匹配
^ 首部匹配
要求 google 必须字符串开头出现,字符串中间的 google 是不管用的,所以匹配不成功
$mode = "/^google/";
$str = "abcdefgooglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0];
}else{
echo "匹配不成功"; // 匹配不成功
}google 必须在字符串开头才能匹配成功
$mode = "/^google/";
$str = "googleabcdefhijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 google
}else{
echo "匹配不成功";
}$ 匹配字符串尾部内容
字符串结尾出现 google 匹配成功
$mode = "/google$/";
$str = "abcdefhijkgoogle";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 google
}else{
echo "匹配不成功";
}如果 ^ 和 $ 两个一起使用,
以 abc 开头,中间可以是任意字符,但必须以 google 结束
$mode = "/^abc.*google$/";
$str = "abcdefhijkgoogle";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 abcdefhijkgoogle
}else{
echo "匹配不成功";
}3、单词边界
\b 匹配单词边界
这个 is 前后有空格是一个单词,可以匹配成功
$mode = "/\bis\b/";
$str = "what is this";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 is
}else{
echo "匹配不成功";
}is 前面没有空格,匹配不成功
$mode = "/\bis\b/";
$str = "whatis this";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0];
}else{
echo "匹配不成功"; // 匹配不成功
}is 前面是下划线,匹配不成功
$mode = "/\bis\b/";
$str = "what_is this";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0];
}else{
echo "匹配不成功"; // 匹配不成功
}is 前面是横杠可以匹配成功
$mode = "/\bis\b/";
$str = "what is-this";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 is
}else{
echo "匹配不成功";
}分割符可以是空格、横杠,
必须有分割符才能知道这是一个单词,否则连起来是不成功的
$str = "whatisthis";
\B 不允许有分界符
字符串里面有两个 is,中间的 is 前后没有分割符匹配成功
$mode = "/\Bis\B/";
$str = "whatisthis";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 is
}else{
echo "匹配不成功";
}4、限制
{m}
限制重复的次数
{1} 前面的字母 o 必须出现 1 次 "abcdefgoglehijk"
$mode = "/go{1}gle/";
$str = "abcdefgoglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 gogle
}else{
echo "匹配不成功";
}{5} 限制字母 o 出现 5 次 "abcdefgoooooglehijk"
$mode = "/go{5}gle/";
$str = "abcdefgoooooglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 gooooogle
}else{
echo "匹配不成功";
}"abcdefgooooooglehijk" 中间有6 个 o 匹配失败
$mode = "/go{5}gle/";
$str = "abcdefgooooooglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0];
}else{
echo "匹配不成功"; // 匹配不成功
}{m,}
最少重复次数不小于 M 次,重复多过 M 次不限制
{5,} 意思是至少要有 5 个多的不限,如果在 5 后面加一个逗号,6 个字母 o 可以匹配成功
$mode = "/go{5,}gle/";
$str = "abcdefgooooooglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 goooooogle
}else{
echo "匹配不成功";
}{m,n}
指定一个范围
{2,5}
2 到 5 之间的都可以匹配成功,中间有 4 个字母 o 可以匹配成功
$mode = "/go{2,5}gle/";
$str = "abcdefgooooglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 goooogle
}else{
echo "匹配不成功";
}6 个字母 o 就匹配不成功了
$str = "abcdefgooooooglehijk";
5、子表达式
( ) 小括号
abc 作为整体匹配,字符串中必须连着出现的 abc
$mode = "/go(abc)gle/";
$str = "abcdefgoabcglehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 goabcgle
}else{
echo "匹配不成功";
}如何调用括号里面的内容?
\\1 意思是第一个括号匹配的内容 abc,作为 \\1 保存在内存中,
相当于把 abc 作为整体调用了,也就是 \\1 对应的就是 abc
$mode = "/go(abc)g\\1le/";
$str = "abcdefgoabcgabclehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 goabcgabcle
}else{
echo "匹配不成功";
}如果 \\1 调用的位置不是 abc,少了一个 c 匹配不成功
$str = "abcdefgoabcgablehijk"
$mode = "/go(abc)g\\1le/";
$str = "abcdefgoabcgablehijk";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0];
}else{
echo "匹配不成功"; // 匹配不成功
}写年月日格式时候,限制一下间隔符号
前面用 - 间隔后面也必须用 - 间隔
前后用 / 间隔后面也必须用 / 间隔
$mode = "/2009(.*)02\\1(15)/";
// $str = "2009-02-15";
$str = "2009/02/15";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 2009-02-15
}else{
echo "匹配不成功";
}这样 \\115 都是数字,会认为是内存中的第 115 个括号
所以 \\1(15) 这里要用括号
日期的位数范围可以用 {2,4} 限制
$mode = "/[0-9]{2,4}(.*)[0-9]{1,2}\\1[0-9]{1,2}/";
$str = "2010/02/15";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 2010/02/15
}else{
echo "匹配不成功";
}0-9 数字可以用 \d 代表,正则规则越来越短了
$mode = "/\d{2,4}(.*)\d{1,2}\\1\d{1,2}/";三、运算顺序
在没有特殊的符号情况下,依然遵循从左到 → 右的运算顺序,
除了运算顺序的规则,还有符号的优先级
符号的优先级
| 符号 | 优先级 |
| ( ) | 最高,圆括号因为是内存处理所以最高 |
| * ? + {} | 其次,量词匹配内容其次 |
| ^ $ \b | 第三,边界处理 |
| | | 第四,条件处理 |
| 最后,按照运算顺序计算匹配 |
( ) 圆括号括起来的内容,是正则表达式中优先处理的
因为首先要读入内存中,才能在正则中第一次、第二次...调用括号里面的规则
* ? + {} 重复性的内容,
如果先用下面边界符,把边界的分开后,再去重复可能会出现错误,所以重复的排第二位
也就是首先确定有哪些重复的内容,然后在限定的边界符
\b 整个单词边界
^ $ 整个正则表达式的开始匹配,或是结束匹配
条件处理 | 会把左边和右边的内容,作为一个完整内容判断,
也就是上面的功能都处理完后,然后将结果进行判断,
意思是前面一段正则,后面一段正则,两段正则都处理完了,在通过条件判断,这两个结果是不是得到内容的匹配
最后按照顺序依次继续计算匹配
四、模式修正符
模式修正符是对“正则表的达式功能”增强和补充
| 修正符 | 作用 | |
| 下面的字母是小写 | ||
| i | 不区分大小写 | 正则内容在匹配时候不区分大小写(默认是区分的) |
| m | 在匹配首内容或者尾内容时候采用多行识别匹配 | 主要增强 ^ 和 $ 符号的功能 |
| s | 将回车转义,就是取消回车换行 | 将转义回车取消是为单行匹配如. 匹配的时候 |
| x | 忽略正则中的空白 | |
| e | 该修正符已经废弃了 | |
| 下面三个字母是大写 | ||
| A | 强制从头开始匹配 | |
| D | 强制$匹配尾部无任何内容 \n | |
| U | 禁止贪婪匹配 | 只跟踪到最近的一个匹配符并结束,常用在采集程序上的正则表达式 |
i
默认区分大小写,修正符 i 不区分大小写
$mode = "/[a-z]/i";
$str = "B";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 B
}else{
echo "匹配不成功";
}m
这两个符号 ^ 和 $ 是匹配整个段落内容的开始和结尾,不管段落分了多少行或是回车,只匹配整个段落的开始和结束,
如果使用修正符 m,符号 ^ 就不仅仅是匹配整个段落的开始,而是匹配每一行的开始
比如 ^ 符号要求段落以 aaa 开头,字符串不是以 aaa 开头匹配不成功
$mode = "/^aaa/";
$str = "12345\naaa6789";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0];
}else{
echo "匹配不成功"; // 匹配不成功
}字符串中 \n 是换行是回车换行,aaa 在字符串中第二行的首位置,加上修正符 m 可以匹配成功
$mode = "/^aaa/m";
$str = "12345\naaa6789";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 aaa
}else{
echo "匹配不成功";
}$ 结尾也一样
修正符 m 不管有几行回车,只要有一行的结尾出现了 aaa 就可以匹配成功
$mode = "/aaa$/m";
$str = "12345aaa\n6789";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 aaa
}else{
echo "匹配不成功";
}s
小写 s 可以将字符串中的回车换行取消,
比如正则中的 (.*) 可以匹配所有的任何内容,但是不包括回车,
可是段落里面有回车,修正符 s 可以把段落中的回车转义取消
下面的字符串中有换行,而正则的 (.*) 不包括回车换行的,修正符 s 可以把回车取消,实际是把回车转换成空格
$mode = "/aaa(.*)ggg/s";
$str = "aaabbb\ncccddd\neeefffggg";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 aaabbb cccddd eeefffggg
}else{
echo "匹配不成功";
}s 增强了 .* 的功能,匹配大量的 html 代码做采集程序的时候可以上
x
忽略正则表达式当中的空白,
注意不是字符串的空白,是正则表达式中的空白
正则中 /cc c/ 有空格匹配不到,x 可以忽略掉正则表达式中的空格,相对于 /ccc/ 连起来了
$mode = "/cc c/x";
$str = "aaabbbcccdddeee";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 ccc
}else{
echo "匹配不成功";
}上节课日期正则,可以使用 x 忽略正则表达式中不需要的空格
$mode = "/2009(.*)02\\1 15/x";
$str = "2009-02-15";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 2009-02-15
}else{
echo "匹配不成功";
}A
这里改用 preg_match_all() 函数全局匹配函数,
以 333 开头并且修正符 m 多行匹配,可以匹配出两个 333
$mode = "/^333/m";
$str = "333aaa\n333bbbcccdddeee";
if(preg_match_all($mode, $str, $arr)){
print_r($arr[0]); // Array ( [0] => 333 [1] => 333 )
}else{
echo "匹配不成功";
}修正符 A 强制必须从整个段落开头进行匹配,
只能匹配出开头的一个 333,实际上 m 的多行功能就消失了,下一行不起作用了
$mode = "/^333/mA";
$str = "333aaa\n333bbbcccdddeee";
if(preg_match_all($mode, $str, $arr)){
print_r($arr[0]); // Array ( [0] => 333 )
}else{
echo "匹配不成功";
}字符串开头没有 333 匹配不成功
$mode = "/^333/mA"; $str = "aaa\n333bbbcccdddeee";
D
段落尾部是 eee 可以匹配成功,正则根据的是段落的内容结束,不包括 \n 回车,
$mode = "/eee$/";
$str = "aaa\n333bbbccc\ndddeee\n";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0]; // 匹配成功 eee
}else{
echo "匹配不成功";
}加上修正符 D 严格要求段落后面必须是干净的,有回车匹配失败(修正符 A 是前面的内容必须是干净)
$mode = "/eee$/D";
$str = "aaa\n333bbbccc\ndddeee\n";
if(preg_match($mode, $str, $arr)){
echo "匹配成功 ".$arr[0];
}else{
echo "匹配不成功"; // 匹配不成功
}U
默认是贪婪匹配,匹配最近的内容
$mode = "/<b(.*)b>/";
$str = "大吊车<b>真厉害</b>,轻轻的<b>一抓</b>就起来";
if(preg_match($mode, $str, $arr)){
echo $arr[0]; // <b>真厉害</b>,轻轻的<b>一抓</b>
}else{
echo "匹配不成功";
}加修正符 U 防止贪婪匹配,只跟踪到近匹配的一个字符
$mode = "/<b(.*)b>/U";
$str = "大吊车<b>真厉害</b>,轻轻的<b>一抓</b>就起来";
if(preg_match($mode, $str, $arr)){
echo $arr[0]; // <b>真厉害</b>
}else{
echo "匹配不成功";
}五、正则的应用
正则函数
| 函数 | 说明 | |
| 匹配 | preg_match(mode, string subject, array matches) | 以 prel 语言为基础的正则函数,函数返回布尔值 |
| ereg(mode, string subject, array regs) | 以 POSIX 为基础的正则函数(Unix、Script),该函数已经废弃了 正则规则不需要起始和结束符号,使用方法与上面基本一样 | |
| 全局匹配 | preg_match_all( string pattern, string subject, array matches [, int flags ) | |
| 替换功能 | preg_replace( mixed pattern, mixed replacement, mixed subject [, int limit] ) | 类似 str_replace 字符串替换 |
| 正则分割 | preg_split ( string pattern, string subject [, int limit [, int flags]] ) | 功能类似 explode 切割函数 |
1、全局匹配
preg_match_all() 函数用于比较详细的内容、采集网页、分析文本...
符合条件的全部匹配出来(之前的 preg_match() 只匹配出第一个),同样有三个参数
1. 正则规则
2. 字符串
3. 正则的结果数组
$mode = "/{(.*)}/U";
$str = "网站名称{title}作者{author},内容是{content}以及其他";
preg_match_all($mode, $str, $arr);
print_r($arr);结果数组是二维的
第一维,包含花括号的匹配内容 {title}
第二维,没有花括号,只有匹配的内容 title
Array(
[0] => Array(
[0] => {title}
[1] => {author}
[2] => {content}
),
[1] => Array(
[0] => title
[1] => author
[2] => content
)
)
2、替换功能
preg_replace() 正则替换函数
通过正则表达式替换相关内容,类似于 str_replace() 字符串替换函数,但功能要强大于它,
正则替换用于替换一些比较复杂的内容,也可以用于内容转换(比如 ubb 代码)
正则替换函数的用法
1. 正则表达式替换(基本使用)
2. 替换的正则规则不仅仅可以是正则表达式,也可以是“正则表达式数组”
3. 通过修正符 e 将替换的内容,先运算然后在替换
4. 第四个参数是可选的,功能是替换的次数,比如只替换第一个或替换前三个(默认是替换所有的内容)
1. 正则表达式替换(基本使用)
替换函数有三个参数
1. 正则规则
2. 替换的内容
3. 被替换的字符串 $str
$mode = "/{(.*)}/U";
$str = "青青的{title}上生活着{author},直到{content}搬到对岸的森林...";
echo preg_replace($mode, '中文', $str); // 青青的中文上生活着中文,直到中文搬到对岸的森林...如果用传统 str_replace() 字符串方法要替换三次
$str = "青青的{title}上生活着{author},直到{content}搬到对岸的森林...";
$str = str_replace("{title}", "草原", $str);
$str = str_replace("{author}", "喜洋洋", $str);
$str = str_replace("{content}", "灰太狼", $str);
echo $str; // 青青的草原生活着喜洋洋,直到灰太狼搬到对岸的森林...正则替换函数 preg_replace() 返回替换后的字符串
青青的中文上生活着中文,直到中文搬到对岸的森林...
但是所有 { } 里面的统一替换成“中文”了,
我们需要 title、author、content 替换成不同的内容,这就是下面正则替换函数的第二种用法
2. 替换的正则规则不仅仅可以正则表达式,也可以是“正则表达式数组”
“正则表达式数组”的意思是将正则写成数组,
“替换的内容”也写成数组,然后依次进行替换,类似于 smart 模板的形式
$mode = array("/{title}/", "/{author}/", "/{content}/"); // 将正则写成数组
$str = "青青的{title}上生活着{author},直到{content}搬到对岸的森林...";
$replace = ["草原", "喜洋洋", "灰太狼"];
echo preg_replace($mode, $replace, $str); // 青青的草原生活着喜洋洋,直到灰太狼搬到对岸的森林...3. 通过修正符 e 将替换的内容先运算在替换
匹配出来的字符,先不进行替换,先对字符进行某种计算,比如 md5 加密,然后在进行替换
$str = "青青的a上生活着b,直到c搬到对岸的森林..."; $mode = "/([a-z])/i"; // 括起来可以在内存中取到正则到的值 echo preg_replace($mode, "md5(\\1)", $str); // 青青的md5(a)上生活着md5(b),直到md5(c)搬到对岸的森林...
没有按照 md5 加密运算
加上修正符 e 可以按照某种计算方式运行 $mode = "/([a-z])/ie"
但是 php5.5 版本以上 e 已废弃了 https://www.cnblogs.com/yangykaifa/p/6839586.html
4. 第四个参数是可选的,功能是替换的次数(默认是替换所有的内容)
默认是替换所有的内容,比如用正则替换过滤敏感词
$str = "青青的CNM上生活着该死,直到孙子搬到对岸的森林"; $mode = "/CNM|该死|孙子/"; echo preg_replace($mode, "***", $str); // 青青的***上生活着***,直到***搬到对岸的森林
第四个可选参数,功能是替换的次数
echo preg_replace($mode, "***", $str, 1); // 青青的***上生活着该死,直到孙子搬到对岸的森林 echo preg_replace($mode, "***", $str, 2); // 青青的***上生活着***,直到孙子搬到对岸的森林 echo preg_replace($mode, "***", $str, 3); // 青青的***上生活着***,直到***搬到对岸的森林
3、分割功能
preg_split() 函数通过正则表达式分割相关内容,
功能类似 explode() 切割函数(它只能按照一种形式分割),通过 preg_split() 的正则表达式把分割形式写成原子表
正则分割函数有两个必填参数
1. 正则规则,写一个原子表可以通过多个符号分割
2. 被分割的字符串
$str = "async,异步,axios,ajax-fecth.promise"; $mode = "/[,.-]/"; $arr = preg_split($mode, $str); print_r($arr);
函数返回一个数组
(
[0] => async
[1] => 异步,axios,ajax 中文逗号的没有被分割开
[2] => fecth
[3] => promise
)
因为中文逗号,导致没有分割开,所以先将中文全角逗号转英文逗号
$str = "async,异步,axios,ajax-fecth.promise";
echo $str = str_replace(',', ',', $str); // 先将中文全角逗号转英文逗号
$mode = "/[,.-]/";
$arr = preg_split($mode, $str);
print_r($arr);(
[0] => async
[1] => axios
[2] => ajax
[3] => fecth
[4] => promise
)
第三个参数是可选的,同样可以设置切割的次数
$arr = preg_split($mode, $str, 1); $arr = preg_split($mode, $str, 2); $arr = preg_split($mode, $str, 3);
六、UBB代码
UBB 代码是 HTML 的一个变种,
是 Ultimate Bulletin Board (国外一个 BBS程序,国内也有不少地方使用这个程序)采用的一种特殊的TAG。
UBB 代码很简单,虽然功能很少,但基本实现了我们常用的一些功能。
看几种简单的 UBB 代码
| ubb | 正则 | html |
| [url] [/url] | /(\[url\])(.*)(\[\/url\])/i | <a href= |
| [b] [/b] | /(\[b\])(.*)(\[\/b\])/i | <b> |
| [img] [/img] | /(\[img\])(.*)(\[\/img\])/i | <img src= |
替换语句中的三个括号
\\1 第一个括号是 [url]
\\2 第二个括号是我们需要的内容,既在第二个参数这里引用 "<a href=\"\\2\">\\2</a>"
\\3 第三个括号是 [/url]
$str = preg_replace("/(\[url\])(.*)(\[\/url\])/i", "<a href=\"\\2\">\\2</a>", $str);写一个自己的 ubb 代码
function get_ubb($ubb) {
$ubb = preg_replace("/\[url\](.*)\[\/url\]/i", "<a href=http://\\1>\\1</a>", $ubb);
$ubb = preg_replace("/\[img\](.*)\[\/img\]/i", "<img src=http://img.baidu.com/hi/face/i_\\1>", $ubb);
return $ubb;
}
echo $str = "随便写[url]baidu.com[/url]一点[img]f01.gif[/img]内容";
echo '<hr>';
echo get_ubb($str);完整的小例子
<?php
function get_ubb($str) {
// $str = preg_replace("/(\[)em(.*?)(\])/iU", "<img src=\"emot/em\\2.gif\" />", $str);
$str = preg_replace("/(\[i_)(.*?)(\])/i", "<img src=\"http://img.baidu.com/hi/face/i_\\2.gif\" />", $str);
// 加粗
$str = preg_replace("/(\[b\])(.*)(\[\/b\])/i", "<b>\\2</b>", $str);
//链接UBB
$str = preg_replace("/(\[url\])(.*)(\[\/url\])/i", "<a href=\\2 target=\"new\">\\2</a>", $str);
//QQ号码UBB
$str = preg_replace("/\[qq\]([0-9]*)\[\/qq\]/i", "<a target=\"_blank\" href=\"tencent://message/?uin=\${1}&site=www.php100.com&menu=yes\"><img src=\"http://wpa.qq.com/pa?p=1:\${1}:8\" alt=\"QQ\${1}\" height=\"16\" border=\"0\" align=\"top\" /></a>", $str);
return $str;
}
if(isset($_POST['sub'])){
echo get_ubb($_POST['message']);
}
?>
<script>
function inserttag(topen,tclose){
var themess = document.getElementById('con');//编辑对象
// themess.focus();
var scrollPos = themess.scrollTop;
var selLength = themess.textLength; //
var selStart = themess.selectionStart;//选区起始点索引,未选择为0
var selEnd = themess.selectionEnd;//选区终点点索引
// console.log(selStart);
// if(selEnd <= 2){
// selEnd = selLength;
// }
var s1 = (themess.value).substring(0,selStart);//截取起始点前部分字符
var s2 = (themess.value).substring(selStart, selEnd)//截取选择部分字符
var s3 = (themess.value).substring(selEnd, selLength);//截取终点后部分字符
themess.value = s1 + topen + s2 + tclose + s3;//替换
themess.focus();
return;
}
</script>
<hr/>
<font size=2>
<!--
<img src="emot/em_01.gif" onclick='inserttag("[em_01", "]");' />
-->
<img src="http://img.baidu.com/hi/face/i_f01.gif" onclick='inserttag("[i_f01", "]");'/>
<img src="http://img.baidu.com/hi/face/i_f03.gif" onclick='inserttag("[i_f03", "]");'/>
<img src="http://img.baidu.com/hi/face/i_f02.gif" onclick='inserttag("[i_f02", "]");'/>
<a href="javascript:void(0);" onclick='inserttag("[b]", "[/b]");'>加粗</a>
<a href="javascript:void(0);" onclick='inserttag("[qq]", "[/qq]");'>QQ号</a>
<a href="javascript:void(0);" onclick='inserttag("[url]", "[/url]");'>超链接</a>
<br>
<form action="" method="post">
<textarea name="message" id="con" cols="70%" rows="10"></textarea>
<input type="submit" name="sub" value="提交"/>
</form>注意一个点
"/(\[i_)(.*?)(\])/i" 这个正则跟其它的不同,这个有一个 ? 号,如果去掉 ? 号,点击多个表情时会出现问题
( .* ) 这样会匹配多个表情符号,比如两个表情符号 [i_f01.gif] [i_f01.gif] 因为贪婪匹配,结果是一个表情符号 [i_f01.gif i_f01.gif]
(.*?) 问号至少匹配,两个表情符号是分开的
七、关键词高亮
1、sql 语句的模糊查找
LIKE 条件一般用在搜索某个关键字的时候,通过通配符 % 或 _ 实现模糊查询
% 表示 0 个或多个字符
_ 表示单个字符
搜索含 "PHP100" 的关键词
SELECT * FROM table WHERE title LIKE '%PHP100%';
两个关键字搜索的 sql 语句
or 或者,两个关键词有一个符合就可以
and 两个关键字都要符合
SELECT * FROM table WHERE title LIKE '%$k[0]%' or title LIKE '%$k[1]%';
2、正则替换关键字加亮
正则替换方法实现关键字 javascript 加亮
// 模拟数据库查询出来的数据
$arr = [
'学会通过网php络走群众路线',
'初中生淘javascript到3本毒气战资料初鉴为真热',
'博主mysql打假合成羊肉卷php 官方检测为真肉热',
'下一步经济工作javascript怎么干',
'女子莫mysql名当上5家公司javascript老板 官方核查热',
'央视曝光医保卡php变“购物卡”热',
'微信朋友圈javascript悄悄更新了热',
'白冰从mysql发廊小哥javascript到偷税网红热',
'日本广mysql岛爆炸事php故现场画面新',
'岗位被AI替代javascript怎么办?杭州发mysql布指引热',
'广西自治mysql区政府javascript原主席蓝天立被公诉新',
'国资委46号令重php新核算养老金不实',
'遭田永mysql明强奸追杀女子发声热',
'你给商家javascript的差评 正被mysql明码标价删除',
'多个电商php平台已下javascript架毒品前体',
'网红白冰偷逃php近千万为何没坐牢'
];
// 循环数组用正则替换关键字javascript加亮
foreach ($arr as $value) {
// echo $value . "\n";
$str = preg_replace("/(javascript)/", '<span style="color:red"> \\1 </span>', $value);
echo "<p>" .$str. "</p>";
}正则替换两个关键词加亮
1. 表单提交的关键词用空格分开
2. explode() 方法分割多个关键词
3. 一个 preg_replace() 正则替换方法实现一个关键词的高亮,写两个正则替换方法实现两个关键词的高亮
<?php
$arr = [
'学会通过网php络走群众路线',
'初中生淘javascript到3本毒气战资料初鉴为真热',
'博主mysql打假合成羊肉卷php 官方检测为真肉热',
'下一步经济工作javascript怎么干',
'女子莫mysql名当上5家公司javascript老板 官方核查热',
'央视曝光医保卡php变“购物卡”热',
'微信朋友圈javascript悄悄更新了热',
'白冰从mysql发廊小哥javascript到偷税网红热',
'日本广mysql岛爆炸事php故现场画面新',
'岗位被AI替代javascript怎么办?杭州发mysql布指引热',
'广西自治mysql区政府javascript原主席蓝天立被公诉新',
'国资委46号令重php新核算养老金不实',
'遭田永mysql明强奸追杀女子发声热',
'你给商家javascript的差评 正被mysql明码标价删除',
'多个电商php平台已下javascript架毒品前体',
'网红白冰偷逃php近千万为何没坐牢'
];
if(isset($_GET['sub'])){
$k = explode(" ", $_GET['key']); // 分割两个关键字
foreach ($arr as $value) {
$value = preg_replace("/($k[0])/", '<span style="color:red"> \\1 </span>', $value);
$value = preg_replace("/($k[1])/", '<span style="color:red"> \\1 </span>', $value);
echo "<p>" .$value. "</p>";
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>关键字高亮</title>
</head>
<body>
<hr/>
<form action="" method="get">
关键字
<input type="text" name="key" value="javascript php"/>
<input type="submit" name="sub" value="检索"/>
</form>
</body>
</html>替换函数的第一个参数,
正则规则写成“正则表达式数组”也可以实现多个关键词加亮
foreach ($arr as $value) {
$mode = array("/(javascript)/", "/(mysql)/", "/(php)/");
$str = preg_replace($mode, '<span style="color:red"> \\1 </span>', $value);
echo "<p>" .$str. "</p>";
}八、防止sql注入
SQL Injection 既 SQL 注入
1、注入的基本原理
正常情况下
通过地址接收的参数 index.php?id=2
然后参数值 2 写到 sql 语句中
select * from where id = 2;
因为 sql 语句可以写的比较复杂,我们将参数 2 替换成一段 sql 语句
替换前 index.php?id=2
替换后 index.php?id=and exists (select id from admin)
替换后成为我们需要的 sql 语句
1. and exists 意思是将前面的执行退出
2. select id from admin 然后在执行一段 sql 语句,查询 admin 表里面的内容
select * from where id = and exists (select id from admin);
本来是查询 table 表,现在查询的是 admin 表
因为我们没有对 id 后面的值进行判断就有隐患了
2、防止注入的几种方法
防止注入的原理很简单,
我们对这些关键字进行过滤 Select,insert,update,delete,and,*...
示例
如果正则函数 preg_match() 返回值为真,就是出现危险的字符了
$check = preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/', $id);
if ($check) {
echo '有危险字符';
exit();
}或者
通过 php 内置函数 addslashes() 过滤特殊字符,比如过滤参数的单引号 ?id=2' and date='2008
$_GET['id'] = addslashes($_GET['id']); echo $sql = "SELECT `id`, `name` FROM `table` WHERE `id` = '".$_GET['id']."'";
SELECT `id`, `name` FROM `table` WHERE `id` = '2\' and date=\'2008'
单引号 `id` = '2\' and date=\'2008' 被斜杠转义了,执行 sql 语句会出现错误
3、总结 PHP 的一些安全设置
1. register_globals = Off 设置为关闭状态
2. sql 语句的表名、字段名不要省略 `小引号` 和 '单引号'
sql 语句中不要直接 id = 2
虽然数字 2 可以不用加单引号,但是加了单引号注入的语句时候就很难执行了,所以规范的语句也能防止注入的可能性
Select * From Table Where id=2; --不规范 Select * From `Table` Where `id` = '2'; --规范
3. 正确的使用 $_POST $_GET $_SESSION 等接收参数,并加以过滤
4. 提高数据库命名技巧,对于一些重要的字段可根据程序特点命名
注入时候会猜测数据库的表名,字段名
这样的名称很容易被猜到 username、password、admin...
命名时候加几个单词就很难被猜到,比如 abc_username、abc_password...
5. 对于常用方法加以封装,避免直接暴露SQL语句
4、用自己写的代码过滤
地址栏输入,没有任何问题,可以看到非常规范的sql语句
?id=2
?id=name
地址栏输入,提示有危险字符,并停止执行程序
?id=2insert
// 自己写的代码过滤$_GET['id']
$_GET['id'] = inject_check($_GET['id']);
// 规范的sql语句
echo $sql = "SELECT `id`, `name` FROM `table` WHERE `id` = '".$_GET['id']."'";
function inject_check($sql_str){
$check = preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/', $sql_str);
if($check){
echo '输入非法字符';
exit();
}
return $sql_str;
}