搞定PHP面試 - 正規表示式知識點整理

白菜1031發表於2019-01-08

一、簡介

1. 什麼是正規表示式

正規表示式(Regular Expression)就是用某種模式去匹配一類字串的一種公式。
正規表示式使用單個字串來描述、匹配一系列匹配某個句法規則的字串。
正規表示式是繁瑣的,但它是強大的,學會之後的應用會讓你除了提高效率外,會給你帶來絕對的成就感。只要認真閱讀本教程,加上應用的時候進行一定的參考,掌握正規表示式不是問題。
許多程式設計語言都支援利用正規表示式進行字串操作。

2. 正規表示式的作用

分割,查詢,匹配,替換字串

3. PHP中的正規表示式

在PHP中有兩套正規表示式函式庫,兩者功能相似,只是執行效率略有差異:

一套是由 PCRE(Perl Compatible Regular Expression) 庫提供的。使用“preg_”為字首命名的函式;
一套由 POSIX(Portable Operating System Interface of Unix )擴充套件提供的。使用以“ereg_”為字首命名的函式;

PCRE來源於Perl語言,而Perl是對字串操作功能最強大的語言之一,PHP的最初版本就是由Perl開發的產品。
PCRE語法支援更多特性,比POSIX語法更強大。因此,本文主要介紹 PCRE 語法的正規表示式

4. 正規表示式的組成

在PHP中,一個正規表示式分為三個部分:分隔符、表示式和模式修飾符。

分隔符

分隔符可以使用除字母、數字、反斜線(\)和空白字元之外的任意 ascii 字元。
最常用的分隔符有正斜線(/)、hash符號(#) 以及取反符號(~)。

表示式

有一些特殊字元和非特殊的字串組成。是決定正規表示式匹配規則的主要部分。

模式修飾符

用於開啟和關閉某些特定的功能/模式。

二、分隔符

1. 分隔符的選擇

當使用 PCRE 函式的時候,正規表示式必須由分隔符閉合包裹。
分隔符可以使用除字母、數字、反斜線(\)和空白字元之外的任意 ascii 字元。
最常用的分隔符有正斜線(/)、hash符號(#) 以及取反符號(~)。

/foo bar/ (合法)
#^[^0-9]$# (合法)
+php+    (合法)
%[a-zA-Z0-9_-]%    (合法)
#[a-zA-Z0-9_-]/    (非法,兩邊的分隔符不同)
a[a-zA-Z0-9_-]a    (非法,分隔符不能是字母)
\[a-zA-Z0-9_-]\    (非法,分隔符不能是反斜線(`\`))

除了上面提到的分隔符,也可以使用括號樣式的分隔符,左括號和右括號分別作為開始和結束 分隔符。

{this is a pattern}

2. 分隔符的使用

如果分隔符 在正規表示式中使用,它必須使用反斜線(\)進行轉義。
果分隔符經常在正規表示式內出現, 最好使用其他分隔符來提高可讀性。

/http:\/\//
#http://#

需要將一個字串放入正規表示式中使用時,可以用 preg_quote() 函式對其進行轉義。 它的第二個引數(可選)可以用於指定需要被轉義的分隔符。

//在這個例子中,preg_quote($word) 用於保持星號和正斜槓(/)原文涵義,使其不使用正規表示式中的特殊語義。
$textBody = "This book is */very/* difficult to find.";
$word = "*/very/*";
$reg = "/" . preg_quote($word, '/') . "/";

echo $reg; // 輸出 '/\*\/very\/\*/'

echo preg_replace ($reg, "<i>" . $word . "</i>", $textBody); // 輸出 'This book is <i>*/very/*</i> difficult to find.'

可以在結束分隔符後面增加模式修飾符來影響匹配效果。
下面的例子是一個大小寫不敏感的匹配

#[a-z]#i

三、元字元

1. 轉義符

字元 描述
\ 將下一個字元標記為一個特殊字元、或一個原義字元、或一個 向後引用。
例如,'n' 匹配字元 "n"。'n' 匹配一個換行符。序列 '\' 匹配 "" 而 "(" 則匹配 "("。

2. 定位符

字元 描述
^ 匹配輸入字串的開始位置 (或在多行模式下是行首)
$ 匹配輸入字串的結束位置 (或在多行模式下是行尾)
\b 匹配一個單詞邊界,即字與空格間的位置
\B 非單詞邊界匹配

3. 限定符

字元 描述
* 匹配前面的子表示式零次或多次。
例如,zo 能匹配 "z" 以及 "zoo"。 等價於{0,}。
+ 匹配前面的子表示式一次或多次。
例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等價於 {1,}。
? 當該字元作為量詞,表示匹配前面的子表示式零次或一次。
例如,"do(es)?" 可以匹配 "do" 或 "does" 。? 等價於 {0,1}。
{n} n 是一個非負整數。匹配確定的 n 次。
例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的兩個 o。
{n,} n 是一個非負整數。至少匹配n 次。
例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等價於 'o+'。'o{0,}' 則等價於 'o*'。
{n,m} m 和 n 均為非負整數,其中n <= m。最少匹配 n 次且最多匹配 m 次。
例如,"o{1,3}" 將匹配 "fooooood" 中的前三個 o。'o{0,1}' 等價於 'o?'。請注意在逗號和兩個數之間不能有空格。

4. 通用字元

字元 描述
\d 匹配一個數字字元。等價於 [0-9]
\D 匹配一個非數字字元。等價於 [^0-9]
\w 匹配字母、數字、下劃線。等價於 [A-Za-z0-9_]
\W 匹配非字母、數字、下劃線。等價於 [^A-Za-z0-9_]
\s 匹配任何空白字元,包括空格、製表符、換頁符等等。等價於 [ \f\n\r\t\v]
\S 匹配任何非空白字元。等價於 [^ \f\n\r\t\v]
. 匹配除換行符(n、r)之外的任何單個字元。
要匹配包括 'n' 在內的任何字元,請使用像"(.
n)"的正規表示式。

5. 非列印字元

字元 描述
\n 匹配一個換行符。等價於 x0a 和 cJ。
\r 匹配一個回車符。等價於 x0d 和 cM。
\t 匹配一個製表符。等價於 x09 和 cI。

6. 多選分支符

字元 描述
| 豎線字元 | 可以匹配多選一的情況。
例如,'z|food' 能匹配 "z" 或 "food"。'(z|f|g)ood' 則匹配 "zood"、"food"或 "good"。

7. 字元組

字元 描述
[x|y] 匹配 x 或 y。
例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 則匹配 "zood" 或 "food"。
[xyz] 字符集合。匹配所包含的任意一個字元。
例如, [abc] 可以匹配 "plain" 中的 'a'。
[^xyz] 負值字符集合。匹配未包含的任意字元。
例如, [^abc] 可以匹配 "plain" 中的'p'、'l'、'i'、'n'。
[a-z] 字元範圍。匹配指定範圍內的任意字元。
例如,[a-z] 可以匹配 'a' 到 'z' 範圍內的任意小寫字母字元。
[^a-z] 負值字元範圍。匹配任何不在指定範圍內的任意字元。
例如,[^a-z] 可以匹配任何不在 'a' 到 'z' 範圍內的任意字元。

8. 非貪婪匹配符

字元 描述
? 當該字元緊跟在任何一個其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 後面時,匹配模式是非貪婪的。
非貪婪模式儘可能少的匹配所搜尋的字串,而預設的貪婪模式則儘可能多的匹配所搜尋的字串。
例如,對於字串 "oooo",'o+?' 將匹配單個 "o",而 'o+' 將匹配所有 'o'。

9. ( )分組

字元 描述
(pattern) 匹配 pattern 並獲取這一匹配。要匹配圓括號字元,請使用 \(\)
(?:pattern) 匹配 pattern 但不獲取匹配結果,也就是說這是一個非獲取匹配,不進行儲存供以後使用。這在使用 "或" 字元 (|) 來組合一個正規表示式的各個部分是很有用。
例如, 'industr(?:y|ies) 就是一個比 'industry|industries' 更簡略的表示式。
(?=pattern) 正向肯定預查(look ahead positive assert),在任何匹配pattern的字串開始處匹配查詢字串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以後使用。
例如,"Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。預查不消耗字元,也就是說,在一個匹配發生後,在最後一次匹配之後立即開始下一次匹配的搜尋,而不是從包含預查的字元之後開始。
(?!pattern) 正向否定預查(negative assert),在任何不匹配pattern的字串開始處匹配查詢字串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以後使用。
例如"Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。預查不消耗字元,也就是說,在一個匹配發生後,在最後一次匹配之後立即開始下一次匹配的搜尋,而不是從包含預查的字元之後開始。
(?<=pattern) 反向(look behind)肯定預查,與正向肯定預查類似,只是方向相反。
例如,"(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。
(?<!pattern) 反向否定預查,與正向否定預查類似,只是方向相反。
例如"(?<!95|98|NT|2000)Windows"能匹配"3.1Windows"中的"Windows",但不能匹配"2000Windows"中的"Windows"。

四、模式修飾符

1. i(不區分大小寫)

如果設定了這個修飾符,正規表示式中的字母會進行大小寫不敏感匹配。

2. m(多行模式)

預設情況下,PCRE 認為目標字串是由單行字元組成的(然而實際上它可能會包含多行)。
"行首"元字元 (^) 僅匹配字串的開始位置, 而"行末"元字元 ($) 僅匹配字串末尾, 或者最後的換行符(除非設定了 D 修飾符)。

當這個修飾符設定之後,“行首”元字元 (^) 和“行末”元字元 ($) 就會匹配目標字串中任意換行符之前或之後,另外,還分別匹配目標字串的最開始和最末尾位置。

如果目標字串 中沒有 "n" 字元,或者正規表示式中沒有出現 ^$,設定這個修飾符不產生任何影響。

3. s(點號通配模式)

預設情況下,點號(.)不匹配換行符。
如果設定了這個修飾符,正規表示式中的點號元字元匹配所有字元,包含換行符。

4. U(貪婪模式)

這個修飾符與前面提到的 ? 作用相同,使正規表示式預設為非貪婪匹配,通過量詞後緊跟 ? 的方式可以使其轉為貪婪匹配。

在非貪婪模式,通常不能匹配超過 pcre.backtrack_limit 的字元。

貪婪模式

$str = '<b>abc</b><b>def</b>';
$pattern = '/<b>.*</b>/';
preg_replace($pattern, '\\1', $str);

.*會匹配 abc</b><b>def

非貪婪模式

方法一、使用 ? 轉為非貪婪模式

$str = '<b>abc</b><b>def</b>';
$pattern = '/<b>.*?</b>/';
preg_replace($pattern, '\\1', $str);

.*會分別匹配 abcdef

方法二、使用修飾符 U 轉為非貪婪模式

$str = '<b>abc</b><b>def</b>';
$pattern = '/<b>.*</b>/U';
preg_replace($pattern, '\\1', $str);

5. u(支援UTF-8轉義表達)

此修正符使正規表示式和目標字串都被認為是 utf-8 編碼。
無效的目標字串會導致 preg_* 函式什麼都匹配不到;無效的正規表示式字串會導致 E_WARNING 級別的錯誤。

$str = '中文';

$pattern = '/^[\x{4e00}-\x{9fa5}]+$/u';

if (preg_match($pattern, $str)) {
    echo '該字串全是中文';
} else {
    echo '該字串不全是中文';
}

6. D(結尾限制)

預設情況下,如果使用 $ 限制結尾字元,當字串以一個換行符結尾時, $符號還會匹配該換行符(但不會匹配之前的任何換行符)。
如果設定這個修飾符,正規表示式中的 $ 符號僅匹配目標字串的末尾。
如果設定了修飾符 m,這個修飾符被忽略。

7. x

如果設定了這個修飾符,正規表示式中的沒有經過轉義的或不在字元類中的空白資料字元總會被忽略, 並且位於一個未轉義的字元類外部的#字元和下一個換行符之間的字元也被忽略。

8. A

如果設定了這個修飾符,正規表示式被強制為"錨定"模式,也就是說約束匹配使其僅從 目標字串的開始位置搜尋。

9. S

當一個正規表示式需要多次使用的時候,為了得到匹配速度的提升,值得花費一些時間對其進行一些額外的分析。
如果設定了這個修飾符,這個額外的分析就會執行。
當前,這種對一個正規表示式的分析僅僅適用於非錨定模式的匹配(即沒有單獨的固定開始字元)。

五、反向引用

使用 ( ) 標記的開始和結束的多個原子,不僅是一個獨立的單元,也是一個子表示式。
在一個 ( ) 中的子表示式外面,反斜線緊跟一個大於 0 的數字,就是對之前出現的某個子表示式的後向引用。
後向引用用於重複搜尋前面某個 ( ) 中的子表示式匹配的文字。

1. 在正規表示式中使用反向引用

(sens|respons)e and \1ibility 將會匹配 ”sense and sensibility” 和 ”response and responsibility”, 而不會匹配 ”sense and responsibility”

2. 在PCRE函式中使用反向引用

<?php
$str = '<b>abc</b><b>def</b>';
$pattern = '/<b>(.*)<\/b><b>(.*)<\/b>/';
$replace = preg_replace($pattern, '\\1', $str);
echo $replace . "\n";

$replace = preg_replace($pattern, '\\2', $str);
echo $replace . "\n";

輸出:

abc
def

六、正規表示式常用PCRE函式

PHP官網的講解已經很詳細了,這裡不再做多餘的論述

執行正規表示式匹配 preg_match()

執行正規表示式全域性匹配 preg_match_all()

執行一個正規表示式的搜尋和替換 preg_replace()

執行一個正規表示式搜尋並且使用一個回撥進行替換 preg_replace_callback()

執行多個正規表示式搜尋並且使用對應回撥進行替換 preg_replace_callback_array()

通過一個正規表示式分隔字串 preg_split()

七、應用實踐

1. 正規表示式匹配中文

UTF-8漢字編碼範圍是 0x4e00-0x9fa5
在ANSI(GB2312)環境下,0xb0-0xf70xa1-0xfe

UTF-8要使用 u模式修正符 使模式字串被當成 UTF-8
在ANSI(GB2312)環境下,要使用chr將Ascii碼轉換為字元

UTF-8

<?php

$str = '中文';

$pattern = '/[\x{4e00}-\x{9fa5}]/u';

preg_match($pattern, $str, $match);

var_dump($match);

ANSI(GB2312)

<?php

$str = '中文';

$pattern = '/['.chr(0xb0).'-'.chr(0xf7).']['.chr(0xa1).'-'.chr(0xfe).']/';

preg_match($pattern, $str, $match);

var_dump($match);

2. 正規表示式匹配頁面中所有img標籤中的src的值。

<?php

$str = '<img alt="高清大圖" id="color" src="color.jpg" />';

$pattern = '/<img.*?src="(.*?)".*?\/?>/i';

preg_match($pattern, $str, $match);

var_dump($match);

相關文章