提供了功能強大靈活而又高效的方法來處理文本正則表達式的全面模式匹配表示法使您可以快速分析大量文本以找到特定的字符模式提取編輯替換或刪除文本子字符串或將提取的字符串添加到集合以生成報告對於處理字符串(例如 HTML 處理日志文件分析和 HTTP 標頭分析)的許多應用程序而言正則表達式是不可缺少的工具正則表達式是一個非常有用的技術有人曾稱之為能讓程序員不至於丟掉飯碗的十大技術之一可見它的重要性
熟悉DOS或者命令行的朋友或許已經用過類似的功能比如我們要查找D盤下所有的低於Word版本的Word文件(因為低於Word版本的Word文件的文件後綴是doc而Word版本的Word文件的文件後綴是docx)我們可以在命令行下執行這個命名
dir D:\*doc
當然如果想查找D盤下任意級子目錄下的所有此類文件就應該執行dir /s D:\*doc了
注意正則表達式並不是在中獨有的東東實際上在其它語言中早就實現了比如(可能很多人沒有聽說過這個編程語言十年前大學期間我曾經學過一點皮毛)其它的編程語言及等也支持正則表達式正則表達式差不多像SQL語言一樣成為標准了同樣和SQL類似在不同的廠商那裡對SQL標准支持的程度並不完全一樣正則表達式也是如此大部分內的正則表達式可以跨語言使用但是在各語言中也會有細微的區別這一點是需要我們注意的
正則表達式元字符
正則表達式語言由兩種基本字符類型組成原義(正常)文本字符和元字符元字符使正則表達式具有處理能力元字符既可以是放在[]中的任意單個字符(如[a]表示匹配單個小寫字符a)也可以是字符序列(如[ad]表示匹配abcd之間的任意一個字符而\w表示任意英文字母和數字及下劃線)下面是一些常見的元字符
元字符 說明
匹配除 \n 以外的任何字符(注意元字符是小數點)
[abcde]
匹配 abcde之中的任意一個字符
[ah] 匹配a到h之間的任意一個字符
[^fgh] 不與fgh之中的任意一個字符匹配
\w 匹配大小寫英文字符及數字到之間的任意一個及下劃線相當於[azAZ_]
\W 不匹配大小寫英文字符及數字到之間的任意一個相當於[^azAZ_]
\s
匹配任何空白字符相當於[ \f\n\r\t\v]
\S 匹配任何非空白字符相當於[^\s]
\d 匹配任何到之間的單個數字相當於[]
\D 不匹配任何到之間的單個數字相當於 [^]
[\ue\ufa] 匹配任意單個漢字(這裡用的是Unicode編碼表示漢字的)
正則表達式限定符
上面的元字符都是針對單個字符匹配的要想同時匹配多個字符的話還需要借助限定符下面是一些常見的限定符(下表中n和m都是表示整數並且<n<m)
限定浮 說明
* 匹配到多個元字符相當於{}
? 匹配到個元字符相當於{}
{n} 匹配n個元字符
{n} 匹配至少n個元字符
{nm} 匹配n到m個元字符
+ 匹配至少個元字符相當於{}
\b 匹配單詞邊界
^ 字符串必須以指定的字符開始
{$selection}nbsp; 字符串必須以指定的字符結束
說明
()由於在正則表達式中\?*^$+()|{[等字符已經具有一定特殊意義如果需要用它們的原始意義則應該對它進行轉義例如希望在字符串中至少有一個\那麼正則表達式應該這麼寫\\+
()可以將多個元字符或者原義文本字符用括號括起來形成一個分組比如^()[]\d{}$表示任意以開頭的移動號碼
()另外對於中文字符的匹配是采用其對應的 Unicode編碼來匹配的對於單個Unicode字符如\ue表示漢字一 \ufa表示漢字龥在Unicode編碼中這分別是所能表示的漢字的第一個和最後一個的Unicode編碼在Unicode編碼中能表示 個漢字
()關於\b的用法它代表單詞的開始或者結尾以字符串a b d作為示例字符串如果正則表達式是\b\d{}\b則僅能匹配
()可以使用|來表示或的關系例如 [z|j|q]表示匹配zjq之中的任意一個字母
正則表達式分組
將正則表達式的一部分用()括起來就可以形成一個分組也叫一個子匹配或者一個捕獲組例如對於::這樣格式的時間我們可以寫如下的正則表達式
(([])|([])|([])([][]){}
如果以這個作為表達式它將從下面的一段IIS訪問日志中提取出訪問時間(當然分析IIS日志最好的工具是Log Parser這個微軟提供的工具)
:: GET /admin_save
:: GET /userbudingasp
:: GET /upfile_flashasp
:: GET /cpphp
:: GET /sqldataphp
:: GET /
:: GET /l
如果我們想對上面的IIS日志進行分析提取每條日志中的訪問時間訪問頁面客戶端IP及端響應代碼(對應C#中的HttpStatusCode)我們可以按照分組的方式來獲取
代碼如下
private String text= @
:
:
GET /admin_save
asp
:
:
GET /userbuding
asp
:
:
GET /upfile_flash
asp
:
:
GET /cp
php
:
:
GET /sqldata
php
:
:
GET /
:
:
GET /l
;
/// <summary>
/// 分析IIS日志
提取客戶端訪問的時間
URL
IP地址及服務器響應代碼
/// </summary>
public void AnalyzeIISLog()
{
//提取訪問時間
URL
IP地址及服務器響應代碼的正則表達式
//大家可以看到關於提取時間部分的子表達式比較復雜
因為做了比較嚴格的時間匹配限制
//注意為了簡化起見
沒有對客戶端IP格式進行嚴格驗證
因為 IIS訪問日志中也不會出現不符合要求的IP地址
Regex regex = new Regex(@
((
[
]|
[
]|
[
])(
[
][
]){
})\s(GET)\s([^\s]+)\s(\d{
}(\
\d{
}){
})\s(\d{
})
RegexOptions
None)
MatchCollection matchCollection = regex
Matches(text)
for (int i =
; i < matchCollection
Count; i++)
{
Match match = matchCollection[i];
Console
WriteLine(
Match[{
}]========================
i)
for (int j =
; j < match
Groups
Count; j++)
{
Console
WriteLine(
Groups[{
}]={
}
j
match
Groups[j]
Value)
}
}
}
這段代碼的輸出結果如下
Match[
]========================
Groups[
]=
:
:
GET /admin_save
asp
Groups[
]=
:
:
Groups[
]=
Groups[
]=:
Groups[
]=GET
Groups[
]=/admin_save
asp
Groups[
]=
Groups[
]=
Groups[
]=
Match[
]========================
Groups[
]=
:
:
GET /userbuding
asp
Groups[
]=
:
:
Groups[
]=
Groups[
]=:
Groups[
]=GET
Groups[
]=/userbuding
asp
Groups[
]=
Groups[
]=
Groups[
]=
Match[
]========================
Groups[
]=
:
:
GET /upfile_flash
asp
Groups[
]=
:
:
Groups[
]=
Groups[
]=:
Groups[
]=GET
Groups[
]=/upfile_flash
asp
Groups[
]=
Groups[
]=
Groups[
]=
Match[
]========================
Groups[
]=
:
:
GET /cp
php
Groups[
]=
:
:
Groups[
]=
Groups[
]=:
Groups[
]=GET
Groups[
]=/cp
php
Groups[
]=
Groups[
]=
Groups[
]=
Match[
]========================
Groups[
]=
:
:
GET /sqldata
php
Groups[
]=
:
:
Groups[
]=
Groups[
]=:
Groups[
]=GET
Groups[
]=/sqldata
php
Groups[
]=
Groups[
]=
Groups[
]=
Match[
]========================
Groups[
]=
:
:
GET /
Groups[
]=
:
:
Groups[
]=
Groups[
]=:
Groups[
]=GET
Groups[
]=/
Groups[
]=
Groups[
]=
Groups[
]=
Match[
]========================
Groups[
]=
:
:
GET /l
Groups[
]=
:
:
Groups[
]=
Groups[
]=:
Groups[
]=GET
Groups[
]=/l
Groups[
]=
Groups[
]=
Groups[
]=
從上面的輸出結果中我們可以看出在每一個匹配結果中
第
個分組就是客戶端訪問時間(因為索引是從
開始的
所以索引順序為
以下同理)
第
個分組是訪問的URL(索引順序為
)
第
個分組是客戶端IP(索引順序為
)
第
個分組是服務器端響應代碼(索引順序為
)
如果我們要提取這些元素
可以直接按照索引來訪問這些值就可以了
這樣比我們不采用正則表達式要方便多了
命名捕獲組
上面的方法盡管方便
但也有一些不便之處
假如需要提取更多的信息
對捕獲組進行了增減
就會導致捕獲組索引對應的值發生變化
我們就需要重新修改代碼
這也算是一種硬編碼吧
有沒有比較好的辦法呢?答案是有的
那就是采用命名捕獲組
就像我們使用 DataReader訪問數據庫或者訪問DataTable中的數據一樣
可以使用索引的方式(索引同樣也是從
開始)
不過如果變化了select語句中的字段數或者字段順序
按照這種方式獲取數據就需要重新變動
為了適應這種變化
同樣也允許使用字段名作為索引來訪問數據
只要數據源中存在這個字段而不管順序如何都會取到正確的值
在正則表達式中命名捕獲組也可以起到同樣的作用
普通捕獲組表示方式
(正則表達式)
如(\d{
})
命名捕獲組表示方式
(?<捕獲組命名>正則表達式)
如(?<phone>\d{
})
對於普通捕獲組只能采用索引的方式獲取它對應的值
但對於命名捕獲組
還可以采用按名稱的方式訪問
例如(?<phone>\d{
})
在代碼中就可以按照 match
Groups[
phone
]的方式訪問
這樣代碼更直觀
編碼也更靈活
針對剛才的對IIS日志的分析
我們采用命名捕獲組的代碼如下
private String text= @
:
:
GET /admin_save
asp
:
:
GET /userbuding
asp
:
:
GET /upfile_flash
asp
:
:
GET /cp
php
:
:
GET /sqldata
php
:
:
GET /
:
:
GET /l
;
/// <summary>
/// 采用命名捕獲組提取IIS日志裡的相關信息
/// </summary>
public void AnalyzeIISLog
()
{
Regex regex = new Regex(@
(?<time>(
[
]|
[
]|
[
])(
[
][
]){
})\s(GET)\s(?<url>[^\s]+)\s(?<ip>\d{
}(\
\d{
}){
})\s(?<httpCode>\d{
})
RegexOptions
None)
MatchCollection matchCollection = regex
Matches(text)
for (int i =
; i < matchCollection
Count; i++)
{
Match match = matchCollection[i];
Console
WriteLine(
Match[{
}]========================
i)
Console
WriteLine(
time:{
}
match
Groups[
time
])
Console
WriteLine(
url:{
}
match
Groups[
url
])
Console
WriteLine(
ip:{
}
match
Groups[
ip
])
Console
WriteLine(
{
}
match
Groups[
httpCode
])
}
}
這段代碼的執行效果如下
Match[
]========================
time:
:
:
url:
ip:
httpCode:
Match[
]========================
time:
:
:
url:
ip:
httpCode:
Match[
]========================
time:
:
:
url:
ip:
httpCode:
Match[
]========================
time:
:
:
url:
ip:
httpCode:
Match[
]========================
time:
:
:
url:
ip:
httpCode:
Match[
]========================
time:
:
:
url:
ip:
httpCode:
Match[
]========================
time:
:
:
url:
ip:
httpCode:
采用命名捕獲組之後使訪問捕獲組的值更直觀了
而且只要命名捕獲組的值不發生變化
其它的變化都不影響原來的代碼
非捕獲組
如果經常看別人有關正則表達式的源代碼
可能會看到形如(?
子表達式)這樣的表達式
這就是非捕獲組
對於捕獲組我們可以理解
就是在後面的代碼中可以通過索引或者名稱(如果是命名捕獲組)的方式來訪問匹配的值
因為在匹配過程中會將對應的值保存到中
如果我們在後面不需要訪問匹配的值那麼就可以告訴程序不用在內存中保存匹配的值
以便提高效率減少內存消耗
這種情況下就可以使用非捕獲組
例如在剛剛分析IIS日志的時候我們對客戶端提交請求的方式並不在乎
在這裡就可以使用非捕獲組
如下
Regex regex = new Regex(@
(?<time>(
[
]|
[
]|
[
])(
[
][
]){
})\s(?
GET)\s(?<url>[^\s]+)\s(?<ip>\d{
}(\
\d{
}){
})\s(?<httpCode>\d{
})
;
零寬度斷言
關於零寬度斷言有多種叫法
也有叫環視
也有叫預搜索的
我這裡采用的是MSDN中的叫法
關於零寬度斷言有以下幾種
(?= 子表達式)
零寬度正預測先行斷言
僅當子表達式在此位置的右側匹配時才繼續匹配
例如
(?=
) 與跟在
前面的
實例匹配
(?! 子表達式)
零寬度負預測先行斷言
僅當子表達式不在此位置的右側匹配時才繼續匹配
例如
(?!
)與不以
結尾的單詞匹配
所以不與
匹配
(?<= 子表達式)
零寬度正回顧後發斷言
僅當子表達式在此位置的左側匹配時才繼續匹配
例如
(?<=
)
與跟在
後面的
的實例匹配
此構造不會回溯
(?<! 子表達式)
零寬度負回顧後發斷言
僅當子表達式不在此位置的左側匹配時才繼續匹配
例如(?<=
)與不以
開頭的單詞匹配
所以不與
匹配
正則表達式選項
在使用正則表達式時除了使用RegexOptions這個枚舉給正則表達式賦予一些額外的選項之外
還可以在在表達式中使用這些選項
如
Regex regex = new Regex(
(?i)def
)
Regex regex = new Regex(
(?i)def
)
它與下面一句是等效的
Regex regex = new Regex(
def
RegexOptions
IgnoreCase)
Regex regex = new Regex(
def
RegexOptions
IgnoreCase)
采用(?i)這種形式的稱之為內聯模式
顧名思義就是在正則表達式中已經體現了正則表達式選項
這些內聯字符與RegexOptions的對應如下
IgnoreCase:內聯字符為i
指定不區分大小寫的匹配
Multiline:內聯字符為m
指定多行模式
更改 ^ 和 $ 的含義
以使它們分別與任何行的開頭和結尾匹配
而不只是與整個字符串的開頭和結尾匹配
ExplicitCapture:內聯字符為n
指定唯一有效的捕獲是顯式命名或編號的 (?<name>…) 形式的組
這允許圓括號充當非捕獲組
從而避免了由 (?
…) 導致的語法上的笨拙
Singleline:內聯字符為s
指定單行模式
更改句點字符 (
) 的含義
以使它與每個字符(而不是除 \n 之外的所有字符)匹配
IgnorePatternWhitespace:內聯字符為x
指定從模式中排除非轉義空白並啟用數字符號 (#) 後面的注釋
(有關轉義空白字符的列表
請參見字符轉義
) 請注意
空白永遠不會從字符類中消除
舉例說明
RegexOptions option=RegexOptions
IgnoreCase|RegexOptions
Singleline;
Regex regex = new Regex(
def
option)
用內聯的形式表示為
Regex regex = new Regex(
(?is)def
)
說明
其實關於正則表達式還有比較多的內容可以講
比如反向引用
匹配順序及幾種匹配模式的區別和聯系等
不過這些在日常開發中使用不是太多(如果做文本分析處理還是會用到的)
所以暫時不會繼續講了
盡管本系列四篇文章篇幅都不是太長(本人不敢太熬夜了
因為每天
點多就要起床)
不過通過這些基礎的學習仍是可以掌握正則表達式的精華之處的
至於在開發中怎麼樣去用
就要靠我們自己靈活去結合實際情況用了
我個人經驗是如果是用於驗證是否滿足要求
那麼寫正則表達式時寫得嚴格一點
如果是從規范格式的文本中提取數據
則可以寫得寬松一點
比如驗證時間
則必須寫成(?<time>(
[
]|
[
]|
[
])(
[
][
]){
})這種形式
這樣別人輸入
:
:
就不能通過驗證
但是如果是像從上面提到的IIS日志中提取時間
用 (?<time>\d{
}(
\d{
}){
})這種方式也是可以
當然如果寫比較嚴格的驗證比較麻煩時也可以寫比較寬松的格式
然後借助其它手段來驗證
在網上有一個驗證日期的正則表達式
編寫者充分考慮到各個月份天數的不同
甚至平年和閏年
月份天數的不同的情況寫了一個相當復雜的正則表達式來驗證
個人覺得可以結合將文本值轉換成日期的方式來共同驗證
這樣更好理解和接受些
到此
關於正則表達式的文章就暫時寫到這裡了
其它還有一些知識用得不是太多
以後有時間再總結了
From:http://tw.wingwit.com/Article/program/net/201311/12785.html