正则表达式
正则表达式是一种描述字符串结果的语法规则,是一个特定的格式化模式,可以匹配、替换、截取匹配的字符串。
- 正则表达式被用来检索或替换那些符合某个模式的文本内容。
- 简单来说正则表达式就是完成字符串的增、删、改、查。
定界符
正则表达式语句由 分隔符(定界符)闭合包裹,分隔符可以是:
- 任意非字母、数字
- 非反斜线\
- 非空白字符
经常使用的分隔符是:正斜线(/),hash符号(#)以及取反符号(~)。
建议使用 // 做为定界符,因为与js一致。
$status = preg_match('/foo/', 'www.foo.com');
var_dump($status); // int(1)边界匹配 / 行定位符(^与$)
行定位符是用来描述字符串的边界
| 符号 | 说明 |
|---|---|
| ^ | 匹配字符串的开始 |
| $ | 匹配字符串的结束,忽略换行符 |
// 表示是否匹配以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 次或多次匹配
{ 自定义量词开始标记
} 自定义量词结束标记//下面的\s匹配任意的空白符,包括空格,制表符,换行符。[^\s]代表非空白符。[^\s]+表示一次或多次匹配非空白符。
$p = '/^我[^\s]+(苹果|香蕉)$/';
$str = "我喜欢吃苹果";
if (preg_match($p, $str)) {
echo '匹配成功';
}元字符具有两种使用场景,一种是可以在任何地方都能使用,另一种是只能在方括号内使用,在方括号内使用的有:
\ 转义字符
^ 仅在作为第一个字符(方括号内)时,表明字符类取反
- 标记字符范围
其中^在反括号外面,表示断言目标的开始位置,但在方括号内部则代表字符类取反,方括号内的减号-可以标记字符范围,例如0-9表示0到9之间的所有数字。//下面的\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 | 字表符 |
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 以外的任何字符
$status = preg_match('/[^678]/', 678);
var_dump($status); // int(0)匹配大小写字母
$status = preg_match('/[a-zA-Z]/', 'a');
var_dump($status); // int(1)用 . 匹配字符
$status = preg_match('/./', 'abc');
var_dump($status); // int(1)preg_split
通过原子表拆分字符串
$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 )原子组
- 如果一次要匹配多个元子,可以通过元子组完成
- 原子组与原子表的差别在于原子组一次匹配多个元子,而原子表则是匹配成功表中的一个元字符就可以
- 元字符组用()表示
$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标签包裹
$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>) 形式可以实现
preg_match('/(?<foo>\d+)/', 'food2021', $match);
print_r($match); // Array ( [0] => 2021 [foo] => 2021 [1] => 2021 )也可以写成引号的形式,但要注意引号的嵌套问题
preg_match("/(?'foo'\d+)/", 'food2021', $match);
print_r($match); // Array ( [0] => 2021 [foo] => 2021 [1] => 2021 )不记录分组
如果希望分组匹配,但结果中不保存分组结果时使用 (?:exp),下例中的结果中将不包括分组数据
preg_match('/(?:\d+)/', 'food2021', $match);
print_r($match); // Array ( [0] => 2021 )断言匹配
断言匹配的内容不会保存到结果中,是对内容前后位置的限制操作。
正向先行断言
匹配指定内容前面的内容时使用 (?=exp)
// 匹配数字2前面的内容
preg_match("/.*(?=2)/", 'foo21', $match);
print_r($match); // Array ( [0] => foo )
// 如果有重复,会一直匹配到最后一个为止
preg_match("/.*(?=2)/", 'foo2021', $match);
print_r($match); // Array ( [0] => foo20 )正向后行断言
匹配指定内容后面的内容时使用 (?<=exp)
// 匹配数字2前面的内容
preg_match("/(?<=2).*/", 'foo21', $match);
print_r($match); // Array ( [0] => 1 )
// 如果有多个重复,匹配到第一个就返回
preg_match("/(?<=2).*/", 'foo2021', $match);
print_r($match); // Array ( [0] => 021 )负向先行断言
使用(?!exp)匹配后面不能是指定内容
// 匹配后面不是#的四位数值
preg_match_all("/\d{4}(?!#)/", '2030# 2022年', $match);
print_r($match); // Array ( [0] => Array ( [0] => 2022 ) )负向后行断言
负向先行断言 指使用(?<!exp)匹配前面不能是指定内容
// 匹配前面不是#的四位数值
preg_match_all("/(?<!#)\d{4}/", '#2030 2022年', $match);
print_r($match); // Array ( [0] => Array ( [0] => 2022 ) )选择修释
| 这个符号带表选择修释符,也就是 | 左右两侧有一个匹配到就可以。
// 把所有的 .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匹配域名后缀
$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次 |
// ^表示开始,$表示结束。
// 点.表示除换行符外的任意字符 * 零个及空字符串也是可以的
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)匹配域名
$web = 'sina.com.cn';
var_dump(preg_match('/^[a-z-0-9-]+\.(com|net|com\.cn|org|cn)$/', $web)); // int(1)把 h1 标签内容加上超链接
$str = <<<html
<h1>hello world</h1>
html;
echo preg_replace('/<h1>(.+)<\/h1>/', '<a href="http://www.foo.com">\1</a>', $str);返回结果
贪婪模式与懒惰模式
正则表达式中每个元字符匹配一个字符,当使用+之后将会变的贪婪,它将匹配尽可能多的字符,但使用问号?字符时,它将尽可能少的匹配字符,既是懒惰模式。
贪婪模式:在可匹配与可不匹配的时候,优先匹配
//下面的\d表示匹配数字
$p = '/\d+\-\d+/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0]; //结果为:010-12345678懒惰模式:在可匹配与可不匹配的时候,优先不匹配
$p = '/\d?\-\d?/';
$str = "我的电话是010-12345678";
preg_match($p, $str, $match);
echo $match[0]; //结果为:0-1当我们确切的知道所匹配的字符长度的时候,可以使用{}指定匹配字符数
$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次以上,但尽可能少重复 |
$str = '123456';
preg_match('/\d+?/', $str, $match);
print_r($match); // Array ( [0] => 1 )
// 因为增加了 `?` 所以只匹配数字1使用禁止贪婪,替换将h1标签内容倾斜处理
$str = "<h1>Hello</h1><h1>World</h1>";
$preg = '/<h1>(.*?)<\/h1>/';
$replace = '<h1><em>\1</em></h1>';
echo preg_replace($preg, $replace, $str);返回结果
Hello
World
$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
)
)$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 不匹分大小
# 默认匹配
// 区分大小写
$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); // HelloWorlds 将字符串视为单行
删除换行符,继续把所有标签删掉
$str = "<H1>
Hello
</H1>";
$preg = '#<h1>.*?</h1>#i';
echo preg_replace($preg, '', $str); // Hello
// 去掉换行,并删除匹配到的数据,清空
$preg = '#<h1>.*?</h1>#is';
echo preg_replace($preg, '', $str); // 空m ^与$符匹配按行匹配
$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 foox 忽略空白、#,只做正则注释
$str = 'abc';
$preg = '/^a\w+
#这是正则的注释
/x';
echo preg_replace($preg, '', $str);$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 fooU 与禁止贪婪?相似,意为禁止贪婪匹配
$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 与 ^ 限定符使用效果相似,必须以目标字符串开始
验证邮箱
$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 不允许以换行结束
$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进行转义。
$p = 'http://';
$p = '/'.preg_quote($p, '/').'/';
echo $p;preg_match
获取第一个匹配的内容
$str = '1@2@3';
preg_match('/\d+/', $str, $matches);
print_r($matches); // Array ( [0] => 1 )preg_match_all
获取所有匹配的内容
$str = '1@2@3';
preg_match_all('/\d+/', $str, $matches);
print_r($matches); // Array ( [0] => Array ( [0] => 1 [1] => 2 [2] => 3 ) )preg_split
通过正则表达式拆分字符串
$str = '1@2#3';
$arr = preg_split('/@|#/', $str);
print_r($arr); // Array ( [0] => 1 [1] => 2 [2] => 3 )preg_replace
通过正则表达式替换
正则表达式的搜索与替换在某些方面具有重要用途,比如调整目标字符串的格式,改变目标字符串中匹配字符串的顺序等。
例如我们可以简单的调整字符串的日期格式:
$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。
str = '主要有以下几个文件:index.php, style.css, common.js';
$p = '/\w+\.\w+/i';
$str = preg_replace($p, '<em>$0</em>', $str);
echo $str;将拼接符@#转成破折号-拼接:
$str = '1@2#3';
echo preg_replace('/@|#/', '-', $str); // 1-2-3preg_replace_callback
使用回调函数进行替换操作
$str = '1@2@3';
echo preg_replace_callback('/\d+/', function ($matches) {
return $matches[0] + 100;
}, $str); // 101@102@103