一、簡介
正則表達式這個名詞,相信很多人都聽說過,這個名詞最早起源於1956 年, 一位叫 Stephen Kleene 的美國數學家在 McCulloch 和 Pitts 早期工作的基礎上,發表了一篇標題爲“神經網事件的表示法”的論文,引入了正則表達式的概念。正則表達式就是用來描述他稱爲“正則集的代數”的表達式,因此採用“正則表達式”這個術語。
隨後,發現可以將這一工作應用於使用Ken Thompson 的計算搜索算法的一些早期研究,Ken Thompson是Unix 的主要發明人。正則表達式的第一個實用應用程序就是 Unix 中的qed 編輯器。
Q: 正則表達式,能夠爲我們做什麼呢?
A: 基於文本的編輯器和搜索工具中的一個重要部分。正則表達式可以讓用戶通過使用一系列的特殊字符構建匹配模式,然後把匹配模式與數據文件、程序輸入以及WEB頁面的表單輸入等目標對象進行比較,根據比較對象中是否包含匹配模式,執行相應的程序。
下面我們就一步一步的結合它的語法,來介紹正則表達式的使用。
二、初次接觸正則表達式
我們先來了解正則表達式的一些基本概念。正則表達式作爲一種表示語言,其定義了自己的一套描述方式,來描述各種各樣的字符類。下面摘取msdn中的一段定義。(ms-help: //MS.VSCC/MS.MSDNVS.2052/cpgenref/html/cpconcharacterclasses.htm)
字符轉義表
字符類
含義
.
與除 /n 以外的任何字符匹配。如果通過 Singleline 選項(請參閱正則表達式選項)進行了修改,則句點字符與任何字符匹配。
[aeiou]
與指定字符集中包含的任何單個字符匹配。
[^aeiou]
與不在指定字符集中的任何單個字符匹配。
[0-9a-fA-F]
使用連字號 (–) 允許指定連續字符範圍。
/p{name}
與 name 指定的命名字符類中的任何字符匹配。支持的名稱爲 Unicode 組和塊範圍。例如 Ll£?Nd£?Z£?IsGreek£?IsBoxDrawing。
/P{name}
與在 {name} 中指定的組和塊範圍中未包含的文本匹配。
/w
與任何單詞字符匹配。等效於 Unicode 字符類別
[/p{Ll}/p{Lu}/p{Lt}/p{Lo}/p{Nd}/p{Pc}]。如果通過 ECMAScript 選項指定了符合 ECMAScript 的行爲,則 /w 等同於 [a-zA-Z_0-9]。
/W
與任何非單詞字符匹配。等效於 Unicode 類別 [^/p{Ll}/p{Lu}/p{Lt}/p{Lo}/p{Nd}/p{Pc}]。如果通過 ECMAScript 選項指定了符合 ECMAScript 的行爲,則 /W 等同於 [^a-zA-Z_0-9]。
/s
與任何空白字符匹配。等效於 Unicode 字符類別 [/f/n/r/t/v/x85/p{Z}]。如果通過 ECMAScript 選項指定了符合 ECMAScript 的行爲,則 /s 等同於 [ /f/n/r/t/v]。
/S
與任何非空白字符匹配。等效於 Unicode 字符類別 [^/f/n/r/t/v/x85/p{Z}]。如果通過 ECMAScript 選項指定了符合 ECMAScript 的行爲,則 /S 等同於 [^ /f/n/r/t/v]。
/d
與任何十進制數字匹配。與 Unicode 的 /p{Nd} 和非 Unicode 的 [0-9] 以及 ECMAScript 行爲一樣。
/D
與任何非數字匹配。與 Unicode 的 /P{Nd} 和非 Unicode 的 [^0-9] 以及 ECMAScript 行爲一樣。
上表列舉了,正則表達式中最最基本的語法定義,瞭解這些,我們已經可以定義一些簡單的規則了,例如:
1. 匹配所有的字符
當然是什麼都不用寫(@_@)
2. 匹配所有的英文字符
a) /w
b) [a-zA-Z_0-9]
3. 匹配十進制數字
a) /d
b) [0-9]
看上面的例子,是不是覺得很簡單呢,不過,到目前爲止,這樣寫出來的規則,還有一個很大的缺陷,就是沒有聲明匹配字符的個數?
Q: 我希望要匹配的字符爲5個英文字母
A: ???
光了解上面的知識是,無法解決這個的L。那正則表達式中是如何解決這個問題的呢,我們來看下面這個表:
(ms-help://MS.VSCC/MS.MSDNVS.2052/cpgenref/html/cpconquantifiers.htm)
限定符表
限定符
說明
*
指定零個或更多個匹配;例如 /w* 或 (abc)*。與 {0,} 相同。
+
指定一個或多個匹配;例如 /w+ 或 (abc)+。與 {1,} 相同。
?
指定零個或一個匹配;例如 /w? 或 (abc)?。與 {0,1} 相同。
{n}
指定恰好 n 個匹配;例如 (pizza){2}。
{n,}
指定至少 n 個匹配;例如 (abc){2,}。
{n,m}
指定至少 n 個但不多於 m 個匹配。
*?
指定儘可能少地使用重複的第一個匹配 (lazy *)。
+?
指定儘可能少地使用重複但至少使用一次 (lazy +)。
??
指定使用零次重複(如有可能)或一次重複 (lazy ?)。
{n}?
等效於 {n} (lazy {n})。
{n,}?
指定儘可能少地使用重複,但至少使用 n 次 (lazy {n,})。
{n,m}?
指定介於 n 次和 m 次之間、儘可能少地使用重複 (lazy {n,m})。
上表中列出了,正則表達式的限定方式,配合這些字符的使用,我們就可以很方便的編寫更爲強勁的正則表達式了。
例如:
1. 匹配零個或多個所有的字符
*
2. 匹配一個或多個所有字符
+
3. 匹配零個或多個所有的英文字符
/w*
4. 匹配一個或多個所有的英文字符
[a-zA-Z0-9]+
5. 匹配3個十進制數字
/d{3}
6. 匹配最少3個十進制數字
/d{3,}
7. 匹配3個到6個十進制數字
/d{3,6}
現在我們可以解答上面問題了:
Q: 我希望要匹配的字符爲5個英文字母
A: /w{5}
很高興,我們已解決了上面的問題,不過,新的問題總是在不斷的出現。我如何限制匹配字符出現在哪裏呢?
Q: 我希望匹配以doc開頭的字符串
A: ???
爲了解決這個問題,我們先來看看這個表:
(ms-help://MS.VSCC/MS.MSDNVS.2052/cpgenref/html/cpconatomiczero-widthassertions.htm)
原子零寬度斷言
斷言
說明
^
指定匹配必須出現在字符串的開頭或行的開頭。有關更多信息,請參閱正則表達式選項中的 Multiline 選項。
$
指定匹配必須出現在以下位置:字符串結尾、字符串結尾的 /n 之前或行的結尾。有關更多信息,請參閱正則表達式選項中的 Multiline 選項。
/A
指定匹配必須出現在字符串的開頭(忽略 Multiline 選項)。
/Z
指定匹配必須出現在字符串的結尾或字符串結尾的 /n 之前(忽略 Multiline 選項)。
/z
指定匹配必須出現在字符串的結尾(忽略 Multiline 選項)。
/G
指定匹配必須出現在當前搜索開始的位置(此位置通常是上一次搜索結束位置之後的第一個字符)。例如,請考慮一個由分離的字符組組成的串聯字符串,其中每一組的長度都爲 n 個字符。在每個字符組中搜索匹配時,如果正則表達式在 0、n、2n、3n 等字符位置找到匹配,則該正則表達式成功。僅當匹配出現在定位組邊界上時纔會成功。
/b
指定匹配必須出現在 /w(字母數字)和 /W(非字母數字)字符之間的邊界上。匹配必須出現在單詞邊界上,即出現在由空格分隔的單詞中第一個或最後一個字符上。
/B
指定匹配不得出現在 /b 邊界上。
相信大家都注意到了,在這個表中第一個斷言字符就是我們需要的@_@.
例如,^ 指定當前位置在行或字符串的開頭。因此,正則表達式 ^FTP 只會返回那些在行的開頭出現的字符串“FTP”的匹配項。
看來上面碰到的問題,又可以解決了,讓我們一起來解決上面的問題:
Q: 我希望匹配以doc開頭的字符串
A: ^doc
以上我們初步瞭解了什麼是正則表達式,已經瞭解其最基本的語法,當作熱身@_@,接下來,才正式進入主題,我們會從第二篇開始深入探討正則表達式的使用。
在前一篇文章中,介紹了一些初步的正則表達式的基本概念,相信很多人對正則表達式的基本知識有所瞭解,接下來,我們結合一些實際的編程示例來掩飾說明正則表達式的作用。
首先,我們先看幾個實際的例子:
1. 驗證輸入字符是否全部爲英文字符
java script:
var ex = "^//w+$";
var re = new RegExp(ex,"i");
return re.test(str);
vb script
Dim regEx,flag,ex
ex = "^/w+$"
Set regEx = New RegExp
regEx.IgnoreCase = True
regEx.Global = True
regEx.Pattern = ex
flag = regEx.Test( str )
C#
System.String ex = @"^/w+$";
System.Text.RegularExpressions.Regex reg = new Regex( ex ); bool flag = reg.IsMatch( str );
2. 驗證郵件格式
C#
System.String ex = @"^/w+@/w+/./w+$";
System.Text.RegularExpressions.Regex reg = new Regex( ex );
bool flag = reg.IsMatch( str );
3. 更改日期的格式(用 dd-mm-yy 的日期形式代替 mm/dd/yy 的日期形式)
C#
String MDYToDMY(String input)
{
return Regex.Replace(input,
"//b(?<month>//d{1,2})/(?<day>//d{1,2})/(?<year>//d{2,4})//b",
"${day}-${month}-${year}");
}
4. 從 URL 提取協議和端口號
C#
String Extension(String url)
{
Regex r = new Regex(@"^(?<proto>/w+)://[^/]+?(?<port>:/d+)?/",
RegexOptions.Compiled);
return r.Match(url).Result("${proto}${port}");
}
這裏的例子可能是我們在網頁開發中,通常會碰到的一些正則表達式,尤其在第一個例子中,給出了使用java script,vb script,C#等不同語言的實現方式,大家不難看出,對於不同的語言來說,正則表達式沒有區別,只是正則表達式的實現類不同而已。而如何發揮正則表達式的公用,也要看實現類的支持。
(摘自msdn: Microsoft .NET 框架 SDK 提供大量的正則表達式工具,使您能夠高效地創建、比較和修改字符串,以及迅速地分析大量文本和數據以搜索、移除和替換文本模式。ms-help: //MS.VSCC/MS.MSDNVS.2052/cpgenref/html/cpconregularexpressionslanguageelements.htm)
下面我們逐個來分析這些例子:
1-2,這兩個例子很簡單,只是簡單的驗證字符串是否符合正則表達式規定的格式,其中使用的語法,在第一篇文章中都已經介紹過了,這裏做一下簡單的描述。
第1個例子的表達式: ^/w+$
^ -- 表示限定匹配開始於字符串的開始
/w – 表示匹配英文字符
+ -- 表示匹配字符出現1次或多次
$ -- 表示匹配字符到字符串結尾處結束
驗證形如asgasdfs的字符串
第2個例子的表達式: ^/w+@/w+./w+$
^ -- 表示限定匹配開始於字符串的開始
/w – 表示匹配英文字符
+ -- 表示匹配字符出現1次或多次
@ -- 匹配普通字符@
/. – 匹配普通字符.(注意.爲特殊字符,因此要加上/轉譯)
$ -- 表示匹配字符到字符串結尾處結束
驗證形如[email protected]的郵件格式
第3 個例子中,使用了替換,因此,我們還是先來看看正則表達式中替換的定義:
(ms-help://MS.VSCC/MS.MSDNVS.2052/cpgenref/html/cpconsubstitutions.htm)
替換
字符
含義
$123
替換由組號 123(十進制)匹配的最後一個子字符串。
${name}
替換由 (?<name> ) 組匹配的最後一個子字符串。
$$
替換單個“$”字符。
$&
替換完全匹配本身的一個副本。
$`
替換匹配前的輸入字符串的所有文本。
$'
替換匹配後的輸入字符串的所有文本。
$+
替換最後捕獲的組。
$_
替換整個輸入字符串。
分組構造
(ms-help://MS.VSCC/MS.MSDNVS.2052/cpgenref/html/cpcongroupingconstructs.htm)
分組構造
定義
( )
捕獲匹配的子字符串(或非捕獲組;有關更多信息,請參閱正則表達式選項中的 ExplicitCapture 選項。)使用 () 的捕獲根據左括號的順序從 1 開始自動編號。捕獲元素編號爲零的第一個捕獲是由整個正則表達式模式匹配的文本。
(?<name> )
將匹配的子字符串捕獲到一個組名稱或編號名稱中。用於 name 的字符串不能包含任何標點符號,並且不能以數字開頭。可以使用單引號替代尖括號,例如 (?'name')。
(?<name1-name2> )
平衡組定義。刪除先前定義的 name2 組的定義並在 name1 組中存儲先前定義的 name2 組和當前組之間的間隔。如果未定義 name2 組,則匹配將回溯。由於刪除 name2 的最後一個定義會顯示 name2 的先前定義,因此該構造允許將 name2 組的捕獲堆棧用作計數器以跟蹤嵌套構造(如括號)。在此構造中,name1 是可選的。可以使用單引號替代尖括號,例如 (?'name1-name2')。
(?: )
非捕獲組。
(?imnsx-imnsx: )
應用或禁用子表達式中指定的選項。例如,(?i-s: ) 將打開不區分大小寫並禁用單行模式。有關更多信息,請參閱正則表達式選項。
(?= )
零寬度正預測先行斷言。僅當子表達式在此位置的右側匹配時才繼續匹配。例如,/w+(?=/d) 與後跟數字的單詞匹配,而不與該數字匹配。此構造不會回溯。
(?! )
零寬度負預測先行斷言。僅當子表達式不在此位置的右側匹配時才繼續匹配。例如,/b(?!un)/w+/b 與不以 un 開頭的單詞匹配。
(?<= )
零寬度正回顧後發斷言。僅當子表達式在此位置的左側匹配時才繼續匹配。例如,(?<=19)99 與跟在 19 後面的 99 的實例匹配。此構造不會回溯。
(?<! )
零寬度負回顧後發斷言。僅當子表達式不在此位置的左側匹配時才繼續匹配。
(?> )
非回溯子表達式(也稱爲貪婪子表達式)。該子表達式僅完全匹配一次,然後就不會逐段參與回溯了。(也就是說,該子表達式僅與可由該子表達式單獨匹配的字符串匹配。)
我們還是先簡單的瞭解一下這兩個概念:
分組構造:
最基本的構造方式就是(),在左右括號中括起來的部分,就是一個分組;
更進一步的分組就是形如:(?<name> )的分組方式,這種方式與第一種方式的不同點,就是對分組的部分進行了命名,這樣就可以通過該組的命名來獲取信息;
(還有形如(?= )等等的分組構造,我們這篇的例子中也沒有使用到,下次我們在來介紹)
替換:
上面提到了兩種基本的構造分組方式()以及(?<name> ),通過這兩種分組方式,我們可以得到形如$1,${name}的匹配結果。
這樣說,可能概念上還是有些模糊,我們還是結合上面的例子來說:
第三個例子的正則表達式爲://b(?<month>//d{1,2})/(?<day>//d{1,2})/(?<year>//d{2,4})//b
(解釋一下,爲什麼這裏都是//一起用:這裏是C#的例子,在C#語言中/是轉譯字符,要想字符串中的/不轉譯,就需要使用//或者在整個字符串的開始加上@標記,即上面等價與
@”/b(?<month>/d{1,2})/(?<day>/d{1,2})/(?<year>/d{2,4}/b”)
/b -- 是一種特殊情況。在正則表達式中,除了在 [] 字符類中表示退格符以外,/b 表示字邊界(在 /w 和 /W 字符之間)。在替換模式中,/b 始終表示退格符
(?<month>/d{1,2}) – 構造一個名爲month的分組,這個分組匹配一個長度爲1-2的數字
/ -- 匹配普通的/字符
(?<day>/d{1,2}) --構造一個名爲day的分組,這個分組匹配一個長度爲1-2的數字
/ -- 匹配普通的/字符
(?<year>/d{2,4}/b”) --構造一個名爲year的分組,這個分組匹配一個長度爲2-4的數字
這裏還不能夠看出這些分組的作用,我們接着看這一句
${day}-${month}-${year}
${day} – 獲得上面構造的名爲day的分組匹配後的信息
- -- 普通的-字符
${month} --獲得上面構造的名爲month的分組匹配後的信息
- -- 普通的-字符
${year} --獲得上面構造的名爲year的分組匹配後的信息
舉例來說:
將形如04/02/2003的日期使用例3種的方法替換
(?<month>/d{1,2}) 分組將匹配到04由${month}得到這個匹配值
(?<day>/d{1,2}) 分組將匹配到02由${day}得到這個匹配值
(?<year>/d{1,2}) 分組將匹配到2003由${year}得到這個匹配值
瞭解了這個例子後,我們在來看第4個例子就很簡單了。
第4個例子的正則
^(?<proto>/w+)://[^/]+?(?<port>:/d+)?/
^ -- 表示限定匹配開始於字符串的開始
(?<proto>/w+) – 構造一個名爲proto的分組,匹配一個或多個字母
: -- 普通的:字符
// -- 匹配兩個/字符
[^/] – 表示這裏不允許是/字符
+? – 表示指定儘可能少地使用重複但至少使用一次匹配
(?<port>:/d+) – 構造一個名爲port的分組,匹配形如:2134(冒號+一個或多個數字)
? – 表示匹配字符出現0次或1次
/ -- 匹配/字符
最後通過${proto}${port}來獲取兩個分組構造的匹配內容
(有關Regex對象的用法,參考
ms-help://MS.VSCC/MS.MSDNVS.2052/cpref/html/frlrfSystemTextRegularExpressionsRegexMembersTopic.htm)
好了,本次介紹的幾個例子,也講得差不多了,希望大家有所收穫,下次,在就一些特殊的要求,進一步探討正則表達式的實現。
前面的文章中,介紹了正則表達式的基本語法,以及一些簡單的例子。但這些並不是我們會遇到的全部問題,有些時候我們不得不編寫一些較爲複雜的正則表達式來解決我們的實際問題。
這裏,我先提幾個問題,然後,我們逐個運用正則表達式的知識來解決。
1. 符合兩種條件之一,都成立,例如:是純數字或者純字符
123(true),hello(true),234.test23(false)
2. 要得到不以數字開頭的字符組合
如:How2234do>you234do,希望得到How和you而不是do,do
3. 得到以數字開頭的字符組合
上例中,得到do和do
4. 要得到不以數字結尾的字符組合
還是上面的情況,要得到的是Ho,do,yo,do
5. 得到以數字結尾的字符組合
同上例,得到Ho,do,yo,do
6. 不允許字符中ab同時出現
例:nihaoma(true),above(false),agoodboy(true)
下面我們開始着手解決這些問題:
第一個:符合兩種條件之一,都成立
這種要求可能代表着一種普遍的要求,我們先來看看這個表
替換構造
替換構造
定義
|
與由|(垂直條)字符分隔的術語中的任何一個術語匹配;例如 cat|dog|tiger。使用最左側的成功匹配。
(?(expression)yes|no)
如果表達式在此位置匹配,則與“yes”部分匹配;否則,與“no”部分匹配。“no”部分可省略。表達式可以是任何有效的表達式,但它將變爲零寬度斷言,因此該語法等效於 (?(?=expression)yes|no)。請注意,如果表達式是命名組的名稱或捕獲組編號,則替換構造將解釋爲捕獲測試(在本表的下一行對此進行了描述)。若要避免在這些情況下產生混淆,則可以顯式拼出內部 (?=expression)。
(?(name)yes|no)
如果命名捕獲字符串有匹配,則與“yes”部分匹配;否則,與“no”部分匹配。“no”部分可省略。如果給定的名稱不與此表達式中使用的捕獲組的名稱或編號對應,則替換構造將解釋爲表達式測試(在本表的上一行進行了描述)。
(ms-help://MS.VSCC/MS.MSDNVS.2052/cpgenref/html/cpconalternationconstructs.htm)
在這個表中,我們看到,正則中爲了解決這一類問題,定義了|來表示或者的關係,就好像常見的或運算符一樣,現在我們來看看如何利用|來解決我們的問題。
1. 先爲可選擇的表達式撰寫表達式:
a) 純數字 – [0-9]*
b) 純字母 – [a-zA-Z]*
2. 將可選條件用|連接起來就是我們所需的
^[0-9]*$|^[a-zA-Z]*$
(這裏我特別對兩個條件加上了^和$限定符,這在驗證字符串是否完全符合要求時,是十分必要的,如果不加這兩個限定符,有興趣的朋友可以自己試一下效果。
後面四個問題,其實是一類的,所以我們把它們放在一起處理。接下來我們來解決第二到第四個問題:
首先,我們回顧一下上次介紹的分組構造:
(?= )
零寬度正預測先行斷言。僅當子表達式在此位置的右側匹配時才繼續匹配。例如,/w+(?=/d) 與後跟數字的單詞匹配,而不與該數字匹配。此構造不會回溯。
(?! )
零寬度負預測先行斷言。僅當子表達式不在此位置的右側匹配時才繼續匹配。例如,/b(?!un)/w+/b 與不以 un 開頭的單詞匹配。
(?<= )
零寬度正回顧後發斷言。僅當子表達式在此位置的左側匹配時才繼續匹配。例如,(?<=19)99 與跟在 19 後面的 99 的實例匹配。此構造不會回溯。
(?<! )
零寬度負回顧後發斷言。僅當子表達式不在此位置的左側匹配時才繼續匹配。
可以看到,這個表的這四種規則,正好可以解決我們的問題。
@_@先解決我們的問題再說:
第二例:要得到不以數字開頭的字符組合
(?<!/d)[a-zA-Z]{2,}
(?<!/d) -- 限定字符的開頭不爲數字才匹配
[a-zA-Z]{2,} – 描述匹配2個以上的字母
(注:這是取巧的做法,因爲,按照我們的邏輯How2234do>you234do中的兩個do的o字母也是符合的,不過,這不是我們想要的,當然還有其他的解決辦法,可以根據實際的情況來處理,這裏是爲了講解這個方法@_@)
第三例:得到以數字開頭的字符組合
(?<=/d)[a-zA-Z]+
(?<=/d) – 限定爲數字開頭的字符才匹配
[a-zA-Z]+ -- 描述匹配1個或多個字母
第四例:要得到不以數字結尾的字符組合
[a-zA-Z]+(?!/d)
[a-zA-Z]+ -- 描述匹配1個或多個字母
(?!/d) – 限定不爲數字結尾的字母才匹配
第五例:得到以數字結尾的字符組合
[a-zA-Z]+(?=/d)
[a-zA-Z]+ -- 描述匹配1個或多個字母
(?=/d) – 限定爲數字結尾的字母才匹配
第六例:不允許字符中ab同時出現
^(?!.*?ab).*$
(?!.*?ab) – 限定不允許出現ab相連的字符
.* -- 任意字符