# 正则表达式
[TOC]
## 运算顺序
1. `( )` 圆括号因为是内存处理所以最高
2. `* ? + { }` 重复匹配内容其次
3. `^ $ \b` 边界处理第三
4. `|` 条件处理第四 最后按照从左到右来匹配
## 原子
原子是正则表达式中的最小的元素,包括英文、标点符号等。**代表只匹配一个**
\d 匹配任意一个数字 [0-9]
\D 与除了数字以外的任何一个字符匹配 [^0-9]
\w 与任意一个英文字母,数字或下划线匹配 [a-z0-9A-Z_]
\W 除了字母,数字或下划线外与任何一个字符匹配 [â-z0-9A-Z_]
\s 与任意一个空白字符匹配 [\n\f\r\t\v]
\f 换页字符;
\n 换行字符;
\r 回车字符;
\t 制表符;
\v 垂直制表符;
\S 与除了空白符外任意一个字符匹配 [^\n\f\r\t\v]
## 元字符
代表着特殊意义的特殊字符
. 除换行符以外的任何一个字符
| 或的意思,匹配其中一项就代表匹配成功
```javascript
/^\d{15}|\d{18}$/ //匹配身份证号,旧版是15位数字,新版是18位数字
/\S+/ //匹配不包含空白符的字符串。
/<a[^>]+>/ //匹配用尖括号括起来的以a开头的字符串。
```
## 原子表
相当于或的一个集合,只匹配到原子表中的任一个就代表成功.
[] 只匹配其中的一个原子
[0-9] 匹配0-9任何一个数字
[a-z] 匹配小写a-z任何一个字母
[A-Z] 匹配大写A-Z任何一个字母
[^] 只匹配"除了"其中字符的任意一个原子
```javascript
/[^x]/ //匹配除了x以外的任意字符
/[^aeiou]/ //匹配除了aeiou这几个字母以外的任意字符
```
> 上面都是只匹配一个字符,如果要匹配多个字符,用原子分组;
## 原子分组
分组代表一个原子集合或者说一个大原子(每个括号必须要全部按顺序),并压入堆栈(内存)用于调用,组号是从左到右计数的调用时:在js中如果是字面量形式用\1,构造函数方式用\\1 这种方式我们叫做反向引用,如果不想让其到内存中可以用(?:exp)来匹配;另外,在php中\\0代表匹配到的全部内容;\\1,\\2..同上
```javascript
//构造函数方式:
var reg = new RegExp("(hdw)123\\1","i");// \\1,代表把第一个原子组hdw,再次引用一遍;
alert(reg.test("hdw123hdw"));//有反向引用的时候,必须是hdw123hdw才能匹配成功,没反向引用的时候hdw123既可
//字面量方式:
var reg = /(hdw)(haha)123\2/i; //把第二个原子组再次引用一次;
alert(reg.test("hdwhaha123haha")); //true;
//可自定义原子分组名称:
/\b(?<Word>\w+)\b\s+\k<Word>\b/ // /\b(\w+)\b\s+\1\b/ 命名:(?<Word>\w+)(或者把尖括号换成'也行:(?'Word'\w+)) 替换:\k<Word>或者\k'Word'
```
## 断言匹配
用于查找在某些内容(**但并不包括这些内容**)之前或之后的东西,也就是说它们像`\b,^,$`那样用于指定一个位置,这个位置应该满足一定的条件(即断言),因此它们也被称为零宽断言。
断言用来声明一个应该为真的事实。正则表达式中只有当断言为真时才会继续进行匹配。
### 零宽断言
- `(?=exp)` 后面必须是以exp结束的
- `(?<=exp)` 前面必须是以exp开始的
```javascript
/\b\w+(?=ing\b)/ //匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I'm singing while you're dancing.时,它会匹配sing和danc
/(?<=\bre)\w+\b/ //会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。
/(?<=\s)\d+(?=\s)/ //匹配以空白符间隔的数字(再次强调,不包括这些空白符)。
```
```php
$str3 = "nixi8.comjyhnixi8.cndsdf";
preg_match("/nixi8(?=\.com)/",$str3,$arr);
/*
结果
Array
(
[0] => nixi8 -> 断言匹配不包括断言部分;
)
*/
```
### 负向零宽断言
- `/(?!exp)/` 后面必须不是以exp结束的
- `/(?<!exp)/` 前面必须不是以exp开始的
```
/\b\w*q[^u]\w*\b/ //匹配包含后面不是字母u的字母q的单词。但是如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为[^u]总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的[^u]将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的\w*\b将会匹配下一个单词,于是\b\w*q[^u]\w*\b就能匹配整个Iraq fighting。负向零宽断言能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:\b\w*q(?!u)\w*\b。
/\d{3}(?!\d)/ //匹配三位数字,而且这三位数字的后面不能是数字;
/\b((?!abc)\w)+\b/ //匹配不包含连续字符串abc的单词。
/(?<![a-z])\d{7}/ //匹配前面不是小写字母的七位数字。
/(?<=<(\w+)>).*(?=<\/\1>)/ //不包含属性的简单HTML标签内里的内容,(?<=<(\w+)>)指定了这样的前缀:被尖括号括起来的单词(比如可能是<b>),然后是.*(任意的字符串),最后是一个后缀(?=<\/\1>)。注意后缀里的\/,它用到了前面提过的字符转义;\1则是一个反向引用,引用的正是捕获的第一组,前面的(\w+)匹配的内容,这样如果前缀实际上是<b>的话,后缀就是</b>了。整个表达式匹配的是<b>和</b>之间的内容(再次提醒,不包括前缀和后缀本身)
```
### 量词
可以使用一些元字符,重复表示一些原子或元字符
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次
> 量词的匹配默认情况下是尽量多的匹配,也即是贪婪匹配,变贪婪匹配为吝啬匹配可以用下面的方式;也就是对有选择匹配多或少的情况下再加一个问号,代表尽量少的匹配;
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复
```javascript
var reg = /a.*b/;
var reg1 = /a.*?b/;
var str = 'assbaab'; //reg匹配全部,reg1匹配'assb'
```
## 限定匹配的边界
^ 匹配字符串的开始
$ 匹配字符串的结束,忽略换行符
\b 匹配单词的边界
\B 匹配除单词边界以外的边界部分
```javascript
//构造函数方式:
var reg = new RegExp("(hdw)123\\1","i");// \\1,代表把第一个原子组hdw,再次引用一遍;
alert(reg.test("hdw123hdw"));//有反向引用的时候,必须是hdw123hdw才能匹配成功,没反向引用的时候hdw123既可
//字面量方式:
var reg = /here/i;
alert(reg.test("hereaaa is a word"))//true;
//\b的匹配
var reg = /\bhere\b/i; //加上\b边界符,代表匹配整个单词,可以理解为单词前后需要有空格;
alert(reg.test("hereaaa is a word"))//false;
alert(reg.test("here is a word"))//true;
var reg = /\Bhere\B/i; //加上\B边界符,代表匹配单词前后非字母的;
alert(reg.test("hereaaa is a word"))//false;
alert(reg.test("2here5 is a word"))//true;
var reg = /\bhi\b.*\bLucy\b/i; //先是一个单词hi,然后是任意个任意字符(但不能是换行),最后是Lucy这个单词。
var reg = /\ba\w*\b/ //匹配以字母a开头的单词——先是某个单词开始处(\b),然后是字母a,然后是任意数量的字母或数字(\w*),最后是单词结束处(\b)。
var reg = /\b\w{6}\b/ //匹配刚好6个字符的单词。
/\b(\w+)\b\s+\1\b/ //可以用来匹配重复的单词,像go go, 或者kitty kitty。
```
## 模式修正符
i 不区分大小写字母的匹配
m 将字符串视为多行,匹配的字符串默认是一行,如果加了m的这个修饰符的话,^和$的意义就变成了匹配行的开始处和结束处。
g 全局匹配,找到所有匹配项,注意在php中g的模式是没有的,replace默认就是全部匹配的,preg_match_all为全匹配;
x 模式中的空白忽略不计
U 匹配到最近的字符串
e 将替换的字符串作为表达使用
```php
$auth = '喂喂??嘿嘿??';
$query = preg_replace('/^.+\?/U','',$auth); //?嘿嘿?? 不加U 全部替换掉了;
```
## 字符转义
要匹配在正则中已有意义的符号 `. * [ ] $ ( ) + ? \ ^ { } |`,就必须全部加上 `\`;
> 注意↑↑,如果是在原子表或原子分组中, `. ?` 不用加上`\`;
## 注释
另外,小括号的另一种用途是通过语法(?#comment)来包含注释。例如:
2[0-4]\d(?#200-249)|25[0-5](?#250-255)|[01]?\d\d?(?#0-199)。
再匹配的时候启用“忽略模式里的空白符”选项 可以匹配到这种更详细注释的↓↓
```
/
(?<= # 断言要匹配的文本的前缀
<(\w+)> # 查找尖括号括起来的字母或数字(即HTML/XML标签)
) # 前缀结束
.* # 匹配任意文本
(?= # 断言要匹配的文本的后缀
<\/\1> # 查找尖括号括起来的内容:前面是一个"/",后面是先前捕获的标签
) # 后缀结束
/x
```
常用正则例子:
```
/0\d{2}-\d{8}/ //以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字.\d后面的{2}({8})的意思是前面\d必须连续重复匹配2次(8次)。
/\(?0\d{2}[) -]?\d{8}/ //首先是一个转义字符\(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字(\d{2}),然后是)或-或空格中的一个,它出现1次或不出现(?),最后是8个数字(\d{8}),但是它也有可能匹配错误的字符串 010)12345678或(022-87654321;所以要用分支条件 |
/0\d{2}-\d{8}|0\d{3}-\d{7}/ //主要匹配两种以连字号分隔的电话号码:一种是以0开头的三位区号,8位本地号(如010-12345678),一种是以0开头的4位区号,7位本地号(0376-2233445)。
/\(?0\d{2}\)?[- ]?\d{8}|0\d{2}[- ]?\d{8}/ //匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。
//使用分支条件在匹配时注意匹配的顺序,因为一旦匹配满足就会停止匹配;
/\d{5}-\d{4}|\d{5}/ //美国邮编的规则是5位数字,或者用连字号间隔的9位数字,如果你把它改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。
//限定原子组,匹配多个字符;
/(\d{1,3}\.){3}\d{1,3}/ //简单的IP地址匹配,要理解这个表达式,请按下列顺序分析它:\d{1,3}匹配1到3位的数字,(\d{1,3}\.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3}),不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址;
/((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)/ //01.02.03.04这个其实也是
```
## 不同编程语言对应的正则操作
### javascript部分
#### 创建
1.通过构造函数创建 reg=new RegExp("正则表达式”,"模式修正符")
```javascript
var reg = new RegExp("houdun");
var stat = reg.test("houdunwang"); //返回的是布尔值,成功就为true;
alert(stat);
```
2.通过字面量方式创建
```javascript
var reg = /houdun/i; //注意没有双引号
var stat = reg.test("houdunwang");
alert(stat);
```
#### 配对方法
1.test方法
`RegExp.test(str)` – 返回一个 Boolean 值,它指出在被查找的字符串中是否存在模式
2.exec方法
`RegExp.exec()` - 在字符串中匹配正则,成功返回数组,失败返回null
数组包含的属性:
- input -被匹配的字符串
- index -子串位置
如果正则表达式没有设置g,那么exec方法不会对正则表达式有任何的影响,如果设置了g,那么exec执行之后会更新正则表达式的lastIndex属性,表示本次匹配后,所匹配字符串的下一个字符的索引,下一次再用这个正则表达式匹配字符串的时候就会从上次的lastIndex属性开始匹配。
```javascript
var str = "Visit W3School, W3School is a place to study web technology.";
var patt = new RegExp("W3School","g"); //有设置g,代表全局索引,可以用循环来重复匹配;如果没有g,那只能配对一个;
var result;
//result = patt.exec(str);
//console.log('result',result); //result ["W3School", index: 6, input: "Visit W3School, W3School is a place to study web technology."]
while ((result = patt.exec(str)) != null) {
document.write(result);
document.write("<br />");
document.write(patt.lastIndex);
document.write("<br />");
}
/*
↑↑反复从str匹配patt:
W3School
14
W3School
24
*/
```
#### 字符串处理
> str.search(regexp)
regexp为正则表达式,反回索引位置,不支持全局索引(即g修饰符无效)找到即停止搜索
```javascript
var str = "www.houdunwang.com";
alert(str.search(/hou/)); //4,返回找到的位置;
```
> str.replace(正则或字符串,替换的新内容)
支持全局g修饰符,如果模式不是全局,当匹配到一个以后将不会继续匹配,反之则会继续往下匹配。返回替换后的字符串;
```
var str = "www.houdunwang.com";
var preg = /w{3}/;
alert(str.replace(preg,'bbs')); //把www.houdunwang.com替换成bbs.houdunwang.com;
var str = "you haha,i haha";
var preg = /haha/g; //加上全局g以后,可反复匹配,不加上g就只能匹配一个,匹配到就停止匹配;
alert(str.replace(preg,'wuwu'));
```
> str.split(字符串或正则,[限定返回的数组元素个数])
拆分字符串,参数可以为字符串或正则表达式
```javascript
var str = "zhou@xiao&gang#haha";
var preg = /[@&#]/;
var r = str.split(preg);
console.log('str',r);//["zhou", "xiao", "gang", "haha"]
```
### php部分
一,创建
```php
"/reg/" '#reg#' '@reg@' //和js不同,必须要加引号
```
二,配对方法
`int|array preg_match( 正则, 被匹配的字符串 [, array matches ] )`
关于第三参数:可选,存储匹配结果的数组,$matches[0] 将包含与整个模式匹配的文本,$matches[1] 将包含与第一个捕获的括号中的子模式所匹配的文本,以此类推
```php
$preg = "/haha/";
$str = "you haha,my haha";
$r = preg_match($preg,$str); //成功返回1,失败返回0
preg_match("/(zh.*u)\s*(g.*g)/", "zhou ganggang", $matches);
print_r($matches);//Array ( [0] => zhou ganggang [1] => zhou [2] => ganggang ),0是与整个模式匹配的结果,1是第一个括号,2是第二括号.....
$url1 = "http://www.126.com";
$url2 = "http://www.sina.com.cn";
$preg = "/http:\/\/www\.\w+\.(com\.cn|com)/i";
preg_match($preg,$url2,$arr);//把$url2与正则匹配的结果返回给$arr;
print_r($arr);//Array ( [0] => http://www.sina.com.cn [1] => com.cn )
```
`preg_match_all($preg,$str,$arr)` //preg_match满足一次就停止匹配,而preg_match_all会重复匹配;
```php
$str='网站:http://www.hdw.com;论坛:http://bbs.hdw.com;shop.hdw.cn;域名hdw.com';
$preg='/(?<!http:\/\/)(?:\w*)\.?\w+\.(?:com\.cn|com|cn)/is';
preg_match_all($preg,$str,$arr);
/*array的结果
Array(
[0] => Array
(
[0] => ww.hdw.com -> www.hdw.com,要从第二个字符开始才满足匹配条件,定义第一个字符就会不出现;
[1] => bs.hdw.com
[2] => shop.hdw.cn
[3] => hdw.com
)
)
*/
```
三,字符串处理
1. `preg_split($preg,$str);` 用\$preg分割\$str,返回分割结果的数组;
```php
$str ="1.jpg@2.jpg@3.jpg#4.jpg";
$preg="/[@#]/";
$arr = preg_split($preg,$str);
print_r($arr);//返回其数组;
```
2\. preg_replace(正则,要替换的,被替换的);返回替换后的字符串;
```php
$str = "网站www.nixi8.com论坛bbs.nixi8.com网名NIXI8";
$preg = "/(nixi8)/i";
$new_str = preg_replace($preg,"<span style='color:red'>\\1</span>",$str);//把上面的nixi8描红,本函数与js略有不同,不用加g就是全局匹配;并且\\1必须是两斜杠;
$str = "后盾官网http://www.houdunwang.com后盾论坛https://bbs.houdunwang.com";
$preg = "/(?<!http:\/\/)(?:bbs|www)\.(houdunwang)\.com/isU";
$new_str = preg_replace($preg,'\\1.com',$str);//匹配所有不以http//开始的,bbs或者www.houdunwang.com -忽略大写写i,忽略空白s,匹配到最近的字符串U;然后替换成houdunwang.com
echo $new_str;
#为没有http://的网址加上http:// ↓↓
$str='网站:http://www.nixi8.com;论坛:bbs.zxg.com;商城shop.zxg.cn;域名vip.zxg.com';
$preg='/(?<!http:\/\/)(?:www|bbs|shop|vip)\.?\w+\.(?:com\.cn|com|cn)/i';
$result = preg_replace($preg,"http://\\0",$str);
```
3\. `array preg_grep ( string $pattern , array $input [, int $flags = 0 ] )`
```php
$subjects = array(
"Mechanical Engineering", "Medicine",
"Social Science", "Agriculture",
"Commercial Science", "Politics"
);
$alonewords = preg_grep("/^[a-z]*$/i",$subjects);//匹配以字母开头并且以字母结束的字符串,不忽略空格,所以^$在遇到空格的时候就会生效;返回的是一个数组,保持原有的索引;有第三参数0|1,默认为0,如果为1的时候就返回相反的结果,也就是不匹配时的结果;
print_r($alonewords);
/*
Array
(
[1] => Medicine
[3] => Agriculture
[5] => Politics
)
*/
```
4\. `mixed preg_replace_callback ( mixed pattern, callback callback, mixed subject [, int limit] )` 可以看做是一个有条件处理的preg_replace函数;
```php
$text = "April fools day is 04/01/2002\n";
$text.= "Last christmas was 12/24/2001\n";
function next_year($matches) {
// 通常:$matches[0] 是完整的匹配项
// $matches[1] 是第一个括号中的子模式的匹配项
// 以此类推
return $matches[1].($matches[2]+1);
}
echo preg_replace_callback(
"|(\d{2}/\d{2}/)(\d{4})|",
"next_year",
$text); //April fools day is 04/01/2003 Last christmas was 12/24/2002
```