# 正則表達式
[TOC]
## 運算順序
1. `( )` 圓括號因為是內存處理所以最高
2. `* ? + { }` 重復匹配內容其次
3. `^ $ \b` 邊界處理第三
4. `|` 條件處理第四 最后按照從左到右來匹配
## 原子
原子是正則表達式中的最小的元素,包括英文、標點符號等。**代表只匹配一個**
\d 匹配任意一個數字 [0-9]
\D 與除了數字以外的任何一個字符匹配 [^0-9]
\w 與任意一個英文字母,數字或下劃線匹配 [a-z0-9A-Z_]
\W 除了字母,數字或下劃線外與任何一個字符匹配 [a-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
```