PHP基礎系列之正規表示式(一)

未進化的類人猿發表於2019-02-16

正規表示式的定義

正規表示式就是描述字元排列模式的一種自定義的語法規則。
由於正規表示式本身具有一套非常完整的、可以編寫模式的語法體系,提供了一種靈活且直觀的字串處理方法,故正規表示式也稱為模式表示式。

正規表示式的特點

  1. 正規表示式並不是PHP特有的,JavaScript、Java、Perl、MySQL中都可以應用到正規表示式。
  2. 正規表示式通過構建具有特定規則的模式,與輸入的字串資訊進行比較,從而實現字串的匹配、查詢、替換及分割等操作。

例子:

"/^https?://(([a-zA-Z0-9_-])+(.)?)*(:d+)?(/((.)?(?)?=?&?[a-zA-Z0-9_-](?)?)*)*$/i"                // 匹配網址URL的正規表示式
"/<(S*?)[^>]*>.*?</\1>|<.*?/>/i"                                // 匹配HTML標記的正規表示式
"/w+([-+.]w)+*@w+([-.]w+)*.w+([-.]w+)*/"                    // 匹配E-mail地址的正規表示式
"/^<imgs+[^>]*s*srcs*=s*([`]?)(?<url>S+)`?[^>l ]*>$/"        // 匹配img標籤

注意

  1. 正規表示式是由普通字元與具有特殊意義的字元組成字串
  2. 效率問題,如果可以使用字串函式完成任務,那儘量不使用正規表示式函式
  3. 對於一些複雜操作,如表單驗證,計算髮布文章中有多少個句子,抓取網頁中某種格式的句子等使用正規表示式以及相關函式能夠更好地實現
  4. 正規表示式具有一定的編寫規則(語義),是一種模式
  5. 若正規表示式不與 相應的正規表示式函式使用,那麼正規表示式僅僅是字串,要達成匹配、查詢、替換以及分割功能,正規表示式必須與相應的正規表示式函式配套使用。

PHP中兩套支援正規表示式的函式庫

  1. PCRE(Perl Compatible Regular Expression)

    由PCRE庫提供,與Perl語言相容的正規表示式、使用”preg_”為字首命名的函式,而且表示式都應被包含在定界符中。

  2. POSIX(Portable Operation System Interface)

    自從PHP5.3.0開始廢棄.使用一套”ereg_”為字首命名的函式。

注意

1. 兩套 函式庫功能類似,執行效率不同,一般來說,實現相同功能,使用 PCRE庫提供的正規表示式效率略佔優勢。
2. PCRE 函式需要模式以定界符閉合。
3. 不像POSIX,PCRE 擴充套件沒有專門用於大小寫不敏感匹配的函式。
   取而代之的是,支援使用i (PCRE_CASELESS) 模式修飾符完成同樣的工作。 其他模式修飾符同樣可用於改變匹配策略。
4. POSIX 函式從最左面開始尋找最長的匹配,但是 PCRE 在第一個合法匹配後停止。如果字串 
   不匹配這沒有什麼區別,但是如果匹配,兩者在結果和速度上都會有差別。 為了說明
   這個不同, 考慮下面的例子(來自Jeffrey Friedl 的《精通正規表示式》一書)。 
   使用模式 one(self)?(selfsufficient)? 在字串oneselfsufficient 上匹配,
   PCRE 會匹配到oneself,但是使用 POSIX,結果將是整個字串 oneselfsufficient。
   兩個子串都匹配原始字串,但是 POSIX 將 最長的作為結果。

函式對比

POSIX PCRE
ereg_replace() preg_replace()
ereg() preg_match()
eregi_replace() preg_replace()
eregi() preg_match()
split() preg_split()
spliti() preg_split()
sql_regcase() 無對等函式
可參照PHP Manual

正規表示式語法規則

正規表示式描述了一種字串的匹配模式,通過這個模式在特定的函式中對字串進行匹配、查詢、替換以及分割等操作。

正規表示式作為一個匹配的模板,是由定界符,原子(普通字元,例如a-z)、有特殊功能的字元(稱為元字元,例如*、+、?等),
以及模式修正符等部分組成的文字模式。

例子:

"/^https?//(([a-zA-Z0-9_-])+(.)?)*(d+)?(/((.)?(?)?=?&?[a-zA-Z0-9_-](?)?)*)*$/i/"

或者

`/^<a.*?(?|\t|\r|\n)?href=[`"]?(.+?)[`"]?(?(?|\t|\r|\n)+.*?)?>(.+?)</a.*?>$/sim`


1. 定界符中使用的是兩個斜線"/",將模式放在它之間說明。
2. 原子用到了<、a、href、=、`、"、/、>等普通字元和	、
、
等轉義字元。
3. 元字元使用了[]、()、|、.、?、*、+等具有特殊含義的字元。
4. 用到模式修正符是在定界符最後一個斜線之後的三個字元"s"、"i"、"m"。

定界符

定界符為PCRE不同於POSIX的特點之一。

除了字母、數字和反斜線以外的字元皆可為定界符

例子:
{ }# #||!!

/</w+>/              // 使用反斜線作為定界符號 合法
|(d{3})-d+|Sm     // 使用豎線"|"作為定界符號 合法
!^(?i)PHP[34]!        // 使用感嘆號"!"作為定界符 合法
{^s+(s+)?$}       // 使用花括號"{}"作為定界符號 合法
/href=`(.*)`        // 非法定界符,缺少結束定界符
1-d3-d3-d4/      // 非法定界符號,缺少起始定界符 


注意

如果沒有特殊要求,一般使用//作為定界符

定界符是成對的,有開始符號,也有結束符號。

原子

原子是正規表示式中最基本的組成單位,而且在每個模式中最少要包含一個原子。

原子是所有那些未顯式指定為元字元的列印(可以在螢幕上輸出的字元)和非列印字元(看不到的)組成的。
簡單地說,能夠在正規表示式中單獨使用的字元就可稱為原子

將其劃分為五類

1. 普通字元作為原子

普通字元是編寫正規表示式時最常見的原子,包括所有的大寫和小寫字母字元、所有數字等,例如:a-z、A-Z、0-9。

2. 非列印字元(看不到的)作為原子

原子字元                    x09 含義描述

+ cx        匹配由x指明的控制符。
             例如,cM匹配一個Control-M或者回車符。x的值必須為A-Z或a-z之一,
             否則,將c視為一個原義的`c`字元。

+ f         匹配一個換頁符。等價於x0c和cL

+ 
         匹配一個換行符。等價於x0a和cJ

+ 
         匹配一個回車符。等價於x0d和cM

+ 	         匹配一個製表符。等價於x09和cI

+ v         匹配一個垂直製表符。等價於x0b和cK

3.通用字元型別做為原子

前面介紹的原子,都是一個原子只能匹配一個字元。但是有時候我們需要一個原子可以匹配一類字元。

在正規表示式中可以直接使用一些代表範圍的原子

原子字元            含義描述

d                     匹配任意一個十進位制數字,等價於[0-9]

D                    匹配任意一個除十進位制數字以外的字元,等價於[^0-9]

w                    匹配任意一個數字、字母、下劃線,等價於[0-9a-zA-Z_]

W                    匹配除數字、字母、下劃線以外的任意一個字元,等價於[^0-9a-zA-Z_]

s                    匹配任意一個空白字元,等價於[f	
v
]

S                    匹配除空白字元以外的任何一個字元,等價於[^	f
v
]

4. 自定義原子表([])作為原子

有些時候,上面六個通用字元並不能滿足我們的需求,我們需要自定義一類原子,比如說奇數(1,3,5,7,9).
所以這時候需要我們自定義一類原子(稱之為類原子),使用原子表”[]”就可以定義一組彼此平等的原子,
只能匹配原子表中一個字元。

1表示匹配除表內原子外的任意字元,通常稱之為排除原子表。

此外,在原子表中可以使用連字元(-)連線一組按照ASCII碼順序排列的原子,可簡化書寫。

例子

`/[apj]sp/`             //  可以匹配asp、jsp、PSP三種,從原子表中僅選擇一個作為原子
`/[^apj]sp/`            //   可以匹配除了asp、PSP、jsp三種以外的字串,如xsp,ysp,zsp等
`/0[xX][0-9a-fA-F]/`    //   可以匹配一個簡單的十六進位制數,如0x2f、0X3AE或0x4aB等

5. 一些特殊字元和元字元作為原子

在正規表示式中,任何一個符號都可以作為原子使用,但如果該符號在正規表示式中有特殊意義,可以使用轉義字元””
取消它的特殊意義,作為一個普通字元使用。 如 ". * + ? ( <>"

” “轉義字元可以將有意義的字元轉成沒意義的字元,還可以將沒意義的字元轉為有意義的字元

元字元

元字元是用於構建正規表示式的具有特殊含義的字元,如”*”、”+”、”?”等。

元字元不能單獨出現,必須用來修飾原子。

如果要在正規表示式中使用包含元字元本身,為了使其失去特殊含義,則必須在前面加上””進行轉義

正規表示式的元字元

元字元                含義描述

*                     匹配0次,1次或者多次其前面的原子

+                   匹配1次或多次其前的原子

?                   匹配0次或者1次其前的原子

.                  匹配除了換行符外的任意一個字元

|                   匹配兩個或多個分支選擇

{n}                表示其前面的原子恰好出現n次

{n,}               表示其前面的原子出現不少於n次

{n,m}              表示其前面的原子至少出現n次,最多出現n次

^或A              匹配輸入字串的開始位置(或在多行模式下行的開頭,即緊隨一換行符後)

$或              匹配輸入字串(或者在多行模式下行的結尾,即緊隨一換行符後)

                 匹配單詞的邊界

B                 匹配除單詞邊界以外的部分

[]                 匹配方括號中指定的任意一個原子

[^]                匹配除方括號中的原子以外的任意一個字元,排除原子表

()                 匹配其整體為一個原子,即模式單元。可以理解為由多個單個原子組成的大原子

xn                   匹配n,其中n為十六進位制轉義值。十六進位制轉義值必須為確定的兩個數字長。
                   例如,`"x41"`匹配`A`。`"x041"`則等價於`"x04&1"`。正規表示式中可以使用ASCII編碼。

1. 限定符

限定符用來指定正規表示式的一個給定原子必須要出現多少次才能滿足匹配。

總共有"*""+""?""{n}""{n,}""{n,m}"六種限定符,他們之間的區別主要是重複匹配的次數不同。

其中"*""+""{n,}"限定符是貪婪的,因為它們會盡可能地匹配文字。

注意

元字元 "*" 表示0次、1次或多次匹配其前的原子,也可以使用"{0,}"完成同樣的匹配

"+"可以使用"{1,}"表示,

"?"可以使用"{0,1}"表示。

2. 邊界限制(斷言)

用來限定字串或單詞的邊界範圍,以便獲得更準確的匹配結果。元字元"^"("A")"$"("")
分別指字串的開始於結束,而""用於描述字串中每個單詞的前或者後邊界,
與之相反的元字元"B"表示非單詞邊界。

例如:

有一個字串 "this is a test", 使用邊界限制如下:

"/^this/"                匹配此字元是否以字串"this"開始的,匹配成功

"/test$/"                匹配此字元是否以字串"test"結束的,匹配成功

"/is/"                匹配此字串中是否含有單詞"is",因為在字串"is"兩邊都需要有邊界

"/Bis/"                查詢字串"is"時,左邊不能有邊界而右邊必須右邊界,如"this"匹配成功

3. 句號(.)

在字元類以外,模式中的圓點可以匹配目標中的任何一個字元,包括不可列印字元。但不匹配換行符號(預設情況下),相當於"[^
]"
(UNIX系統)或者"[^

]"(Windows系統)。

但是如果設定了模式修正符"s",則圓點也會匹配換行符。

處理圓點與處理^和$是完全獨立的,唯一的聯絡是涉及換行

注意

1. `".*?"`或者`".+?"`組合來匹配除換行符以外的任何字串。
   例如:`"/<b>.*?</b>/"`可以匹配以`<b>`,`</b>`標籤開始於結束的任何不包括換行符的任意字串 

4. 模式選擇符(|)

豎線字元"|"用來分隔多選一模式,在正規表示式中匹配兩個或更多的選擇之一。

|的優先順序是最低的,所以應在最後考慮其功能。

例如:LAMP | J2EE表示匹配LAMP,也可以匹配J2EE,由於|其優先順序最低,所以並
不表示匹配`LAMP2EE`或者`LAMJEE`

也可以像這樣使用 "/Linux|Apache|MySQL|PHP/",表示可以從中任意匹配一組。

5. 模式單元

模式單元是使用元字元"()"將多個原子組成大原子使用。

一個模式單元中的表示式將被優先匹配

例子:

`/(very)*good/` //可以匹配good、very good、very very good

6. 後向引用

後向引用是一個正規表示式中一個重要的應用點。

使用()標記的開始和結束的多個原子,不僅僅是一個獨立的單元,也是一個子表示式(也稱之為子模式)。

**對於一個正規表示式模式或部分模式兩邊新增圓括號將導致相關匹配儲存到一個臨時緩衝區中,可以被捕獲供

以後使用**。

所捕獲的每個子匹配都按照正規表示式模式中從左之後所遇到的內容儲存。儲存子匹配的緩衝區編號從1開始,
連續編號直至最大99個表示式。

每個緩衝區都可以使用
訪問,其中n為一個便是特定緩衝區的一位或兩位十進位制數。

例如:"1""2""3"等形式的引用,在正規表示式模式中使用時還需要在前面加上一個反斜線,
將反斜線再次轉義,
例如:"\1""\2""\3"等形式的引用。

如下所示:

`/^d{4}Wd{2}Wd{2$}/`    // 這是一個匹配日期的格式,如2008-08/08或者2008/08-08 等
`/^d{4}(W)d{2}\1d{2$}/`    // 這是一個匹配日期的格式,如2008-08-08或者2008/08/08 等

當你要使用模式單元而又不想儲存匹配結果時,可以使用非捕獲元字元"?:""?=""?!"來忽略對相關匹配的儲存。

注意

在一些正規表示式中,使用非儲存模式單元是必要的,可以改變其後向引用的順序。

`/(Windows)(Linux)\2OS/`          --- 使用"2"再次引用第二個緩衝區中的字串"Linux"
`/(?:Windows)(Linux)\1OS/`        --- 使用"?:"忽略了第一個子表示式的儲存,所以"1"引用的就是"Linux"







7. 模式匹配的優先順序

在使用正規表示式時,需要注意匹配的書序。通常相同優先順序從左到右進行運算,不同優先順序的運算先高後低。

** 順 序 **    ** 元 字 符 **               ** 描 述 **

1                                         轉義符號

2                ()、(?:)、(?=)、[]        模式單元和原子表

3               *、+、?、{n}、{n,}、{n,m}  重複匹配

4               ^、$、、B、A、        邊界限制

5               |                           模式選擇

模式修正符

模式修正符在正規表示式定界符之外使用(最後一個斜線"/"之後)。

模式修正符可以調整正規表示式的解釋,擴充套件了正規表示式在匹配、替換等操作時的某些功能;
而且模式修正符可以組合使用,更增強了正規表示式處理能力

模式修正符可以單個使用,也可以多個組合使用

模式修正符

**模式修正符**                        **功能描述**

i                                 在和模式進行匹配時不區分大小寫

m                                 將字串視為多行。預設開的正則開始`"^"`和`"$"`將目標字串作為單一的一"行"
                                  字元(甚至其中包含有換行符也是如此)。如果在修飾符中加上`"m"`,那麼開始和結束將會指字串的每一行,每一行的開頭就是`"^"`,結尾就是`"$"`。

s                                 如果設定了次修正符,則模式中的圓點元字元`"."`匹配所有的字元,包括換行符。
                                  即將字串視為單行,換行符作為普通字元看待。

x                                 模式中的空白忽略不計,除非它已經被轉義。

e                                 **只有在`preg_replace()`函式中,在替換字串對逆向引用做正常的替換,將其作為
                                  PHP程式碼求值,並且其結果來替換所搜尋的字串**

U                                 本修正符反轉了匹配數量的值使其使其不是預設的重複,
                                  而變成在後面跟上`"?"`才變得重複。**__這和Perl語言不相容__**。也可以通過在模式中設定(U)修正符或者數量符之後跟一個問號`"?"`(例如.*?)來使用此選項。

D                                 模式中的美元元字元僅匹配目標字串的結尾。沒有此選項時,如果最後
                                  一個字元是換行符,則美元符號也會匹配次字元之前的內容。如果設定了m修正符,則忽略此選項

注意:

  1. 模式 “/Web Server/ix”可以用來匹配字串”webserver”,忽略大小寫和空白。
  2. 貪婪匹配,在匹配成功的前提下,儘可能多的去匹配*,+,{n,},.*是貪婪的,例如:/a.*e/去匹配字串”abcd fsdfsdfsesfdfsdfsesdfedfsdfses”,由於`.*`是貪婪的匹配,會從這個字串中匹配出"abcd fsdfsdfsesfdfsdfsesdfedfsdfse",從第一個a開始,知道最後一個字母e結束,都屬於`.*`內容。如果想取消這種貪婪模式,可以使用模式修正符"U"或者在模式中使用.*?,在.*後面加個?。為了相容Perl正則函式,可能會沒有模式修正符"U",我們建議使用在".*"後加"?"來實現懶惰模式(即非貪婪模式)如果"U""?"同時使用,像這樣"/a.*?e/U",則匹配"abcdfsdfsdfsesfdfsdfsesdfedfsdfse",相當於又啟用了貪婪模式。
  3. 惰性模式,在匹配成功的前提下,儘可能少的去匹配(注意點同上)
  4. 模式"/^is/m"可以匹配字串"this
    is
    ais
    test"
    中的`is`,因為使用模式修正符"m"將字串視為多行,第二行出現的"is"匹配成功。預設的正則開始^和結束`$`將目標字串作為單一的一“行”(甚至包含換行符也是如此)。

Note:

看到這裡是不是懵逼了?沒錯,我也懵逼。
不過沒有關係,秉承Learning by doing的準則,
在接下來的教程中,我會一一演示。

更多文章請訪問我的部落格:Noapes

例項

/**
 *  無亂碼擷取中文字元
 * @param $str
 * @param int $start
 * @param $length
 * @param string $charset
 * @param bool|true $suffix
 * @return string|void
 */

function msubstr($str, $start=0, $length, $charset="utf-8", $suffix=true)
{
    if(function_exists("mb_substr"))
        return mb_substr($str, $start, $length, $charset);
    elseif(function_exists(`iconv_substr`)) {
        return iconv_substr($str,$start,$length,$charset);
    }
    $re[`utf-8`]   = "/[x01-x7f]|[xc2-xdf][x80-xbf]|[xe0-xef][x80-xbf]{2}|[xf0-xff][x80-xbf]{3}/";
    $re[`gb2312`] = "/[x01-x7f]|[xb0-xf7][xa0-xfe]/";
    $re[`gbk`]    = "/[x01-x7f]|[x81-xfe][x40-xfe]/";
    $re[`big5`]   = "/[x01-x7f]|[x81-xfe]([x40-x7e]|xa1-xfe])/";
    preg_match_all($re[$charset], $str, $match);
    $slice = join("",array_slice($match[0], $start, $length));
    if($suffix) return $slice;
    return $slice;
}

echo msubstr(`哈哈哈你好啊啊`,2,3,`gb2312`);



  1. sdf

相關文章