熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

C#正則表達式編程(四):正則表達式

2022-06-13   來源: .NET編程 

  提供了功能強大靈活而又高效的方法來處理文本正則表達式的全面模式匹配表示法使您可以快速分析大量文本以找到特定的字符模式提取編輯替換或刪除文本子字符串或將提取的字符串添加到集合以生成報告對於處理字符串(例如 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_saveasp
   
    :: GET /userbudingasp
   
    :: GET /upfile_flashasp
   
    :: GET /cpphp
   
    :: GET /sqldataphp
   
    :: GET /
   
    :: GET /l ;
   
    /// <summary>
   
    /// 分析IIS日志提取客戶端訪問的時間URLIP地址及服務器響應代碼
   
    /// </summary>
   
    public void AnalyzeIISLog()
   
    {
   
    //提取訪問時間URLIP地址及服務器響應代碼的正則表達式
   
    //大家可以看到關於提取時間部分的子表達式比較復雜因為做了比較嚴格的時間匹配限制
   
    //注意為了簡化起見沒有對客戶端IP格式進行嚴格驗證因為 IIS訪問日志中也不會出現不符合要求的IP地址
   
    Regex regex = new Regex(@(([]|[]|[])([][]){})\s(GET)\s([^\s]+)\s(\d{}(\\d{}){})\s(\d{}) RegexOptionsNone)
   
    MatchCollection matchCollection = regexMatches(text)
   
    for (int i = ; i < matchCollectionCount; i++)
   
    {
   
    Match match = matchCollection[i];
   
    ConsoleWriteLine(Match[{}]======================== i)
   
    for (int j = ; j < matchGroupsCount; j++)
   
    {
   
    ConsoleWriteLine(Groups[{}]={} j matchGroups[j]Value)
   
    }
   
    }
   
    }
   
    這段代碼的輸出結果如下
   
    Match[]========================
   
    Groups[]=:: GET /admin_saveasp
   
    Groups[]=::
   
    Groups[]=
   
    Groups[]=:
   
    Groups[]=GET
   
    Groups[]=/admin_saveasp
   
    Groups[]=
   
    Groups[]=
   
    Groups[]=
   
    Match[]========================
   
    Groups[]=:: GET /userbudingasp
   
    Groups[]=::
   
    Groups[]=
   
    Groups[]=:
   
    Groups[]=GET
   
    Groups[]=/userbudingasp
   
    Groups[]=
   
    Groups[]=
   
    Groups[]=
   
    Match[]========================
   
    Groups[]=:: GET /upfile_flashasp
   
    Groups[]=::
   
    Groups[]=
   
    Groups[]=:
   
    Groups[]=GET
   
    Groups[]=/upfile_flashasp
   
    Groups[]=
   
    Groups[]=
   
    Groups[]=
   
    Match[]========================
   
    Groups[]=:: GET /cpphp
   
    Groups[]=::
   
    Groups[]=
   
    Groups[]=:
   
    Groups[]=GET
   
    Groups[]=/cpphp
   
    Groups[]=
   
    Groups[]=
   
    Groups[]=
   
    Match[]========================
   
    Groups[]=:: GET /sqldataphp
   
    Groups[]=::
   
    Groups[]=
   
    Groups[]=:
   
    Groups[]=GET
   
    Groups[]=/sqldataphp
   
    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{})在代碼中就可以按照 matchGroups[phone]的方式訪問這樣代碼更直觀編碼也更靈活針對剛才的對IIS日志的分析我們采用命名捕獲組的代碼如下
   
    private String text= @:: GET /admin_saveasp
   
    :: GET /userbudingasp
   
    :: GET /upfile_flashasp
   
    :: GET /cpphp
   
    :: GET /sqldataphp
   
    :: 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{}) RegexOptionsNone)
   
    MatchCollection matchCollection = regexMatches(text)
   
    for (int i = ; i < matchCollectionCount; i++)
   
    {
   
    Match match = matchCollection[i];
   
    ConsoleWriteLine(Match[{}]======================== i)
   
    ConsoleWriteLine(time:{} matchGroups[time])
   
    ConsoleWriteLine(url:{} matchGroups[url])
   
    ConsoleWriteLine(ip:{} matchGroups[ip])
   
    ConsoleWriteLine({} matchGroups[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 RegexOptionsIgnoreCase)
   
    Regex regex = new Regex(def RegexOptionsIgnoreCase)
   
    采用(?i)這種形式的稱之為內聯模式顧名思義就是在正則表達式中已經體現了正則表達式選項這些內聯字符與RegexOptions的對應如下
   
    IgnoreCase:內聯字符為i指定不區分大小寫的匹配
   
    Multiline:內聯字符為m指定多行模式更改 ^ 和 $ 的含義以使它們分別與任何行的開頭和結尾匹配而不只是與整個字符串的開頭和結尾匹配
   
    ExplicitCapture:內聯字符為n指定唯一有效的捕獲是顯式命名或編號的 (?<name>…) 形式的組這允許圓括號充當非捕獲組從而避免了由 (?…) 導致的語法上的笨拙
   
    Singleline:內聯字符為s指定單行模式更改句點字符 () 的含義以使它與每個字符(而不是除 \n 之外的所有字符)匹配
   
    IgnorePatternWhitespace:內聯字符為x指定從模式中排除非轉義空白並啟用數字符號 (#) 後面的注釋(有關轉義空白字符的列表請參見字符轉義) 請注意空白永遠不會從字符類中消除
   
    舉例說明
   
    RegexOptions option=RegexOptionsIgnoreCase|RegexOptionsSingleline;
   
    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
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.