Go to comments

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}&amp;site=www.php100.com&amp;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;
}



Leave a comment 0 Comments.

Leave a Reply

换一张