Introduction To .NET Regular Expression

試想以下的情境:

  • 公司最近要研發一套類似Google的網頁搜索引擎,你負責開發網頁擷取器核心,其中最棘手的部分是要具備由一個網頁再延伸至其所超連結出去的其他網頁…
  • 行銷人員拿來一份十萬筆客戶地址檔,請你解析出縣市、郵遞區號、地址三個欄位轉入CRM系統,但要命的是原始資料雜亂不已,有的郵遞區號在前,有的在中間…
  • 你加入了檢索引擎過濾器(Filter)的開發團隊,目標是將各式資料檔中的純文字部份提取出來,你的第一件任務是簡單的HTML檔案解析,但是,HTML原始碼千變萬化…

以上的挑戰當然不是每位程式開發者都有機會遭遇,但應該沒有人會否認能寫出這等程式的傢伙還真有兩把刷子。

好像還是有些人不太曉得Regular Expression的威力,簡單來說,針對複雜的文字串處理(例如: 將一篇文章中所有出現的URL都挑出來),Regular Express vs 土法鍊鋼,會是 兩行 vs 200行 的強烈對比。

以下這篇文章是我小時候投稿RUN!PC雜誌的作品(即使現在寫RegEx,忘了語法時我也是先查這篇,足見其實用性),介紹.NET中一群好用但常被遺忘的類別--Regular Expression,並展示如何讓文字處理程式的發展工作單純一點、輕鬆一些。Regular Expression的技巧學好了,應用無窮(連UltraEditor、Grep等Command Utility都看到到它的蹤跡),保證值回票值,Check It Out!

文章下載

歡迎推文分享:
Published 30 June 2007 08:40 AM 由 Jeffrey
Filed under: ,
Views: 45,613



意見

# steve said on 30 June, 2007 10:12 AM

Regular Expression真的很難懂

後來找到這個網站

http://regexlib.com/

有蠻多現成的library可以查詢直接用 :P

# Julian said on 08 July, 2007 03:27 AM

工作上有時候會用到Regular Expression

每次都是上網剪剪貼貼,對它一直是一知半解

感謝李大哥的提供的文章

看完後觀念清楚很多

# William said on 24 October, 2007 01:07 AM

小弟最近試著用RegEx來抓取程式中的巢狀結構中的內容,如

一段程式碼中,含有:

int fib (int Num)

{

   if (Num ==1)

      return 1;

   else if (Num == 0)

             return 0;

   ...

   ...

}

想將整個function (或class、for loop等),作用範圍內的文字全部抓出來,以進行分析,但一直無法找到math巢狀結構的作法,不知大哥是否能夠提示一下小弟可能的作法

小弟使用c#來寫,但在msdn中對這方面的介紹實在太少,且沒有足夠的範例

還請您不吝賜教

# Jeffrey said on 24 October, 2007 04:45 AM

我能想到的是用剝洋蔥的做法,先找到最外層的一對{及},接著再從{ }中間夾的內容中找到最外一層的{ },此時可能有多個,每個都要探索下去(有點像列出所有子目錄下的所有檔案,可能需要用遞迴比較好寫),接著用一些特徵來判定這是不是一個function, class, for loop。

你如果真的需要範例,可以再提明確一點的需求,例如: 來源的文字是什麼,要抓出什麼文字,我可以找時間寫一小段Code示意。

不過如果你想做到各種奇怪的文字組合都能辨識,工程會大得嚇人,此類程式我有時只會做到80%的涵蓋率,餘下的20%機車特例加入一些人工/半人工處理,整體成本才會最低。(如果你想開發的是產品,則另當別論)

# William said on 25 October, 2007 03:12 AM

感謝您的指教

小弟目前想做的事類似程式碼比對,因此會想要將兩個程式,先依function名稱抽出成為各別的blcok,舉例來說:

程式A:

main()

{

    printf ("hello");

}

fun1()

{

  ...

  ...

}

fun2()

{

  ...

  ...

}

程式B:

main()

{

  int count=0;

  printf ("hello world");

  for (i=0; i<10 ; i++)

  {

      count++;

  }

}

fun1()

{

  ...

  ...

}

fun2()

{

  ...

  ...

}

小弟目前想做的事是要將各個function分別抓出,並儲存於string中,例如在程式A中設一string array,內容為:

strA[0]="main(){printf ("hello");}"

strA[1]="fun1(){ ...  ...}"

strA[2]="fun2(){ ...  ...}"

程式B中:

strB[0]="main(){   int count=0;   printf ("hello world");   for (i=0; i<10 ; i++)   {       count++;   }}"

strB[1]="fun1(){ ...  ...}"

strB[2]="fun2(){ ...  ...}"

而從每一個function中,若存在有其它巢狀結構,如for、while、if、…等,則再繼續抽取出來

目前想做到這種功能,要利用stack或counter功能,抓到"{"

時push或counter + 1,抓到"}"時pop或counter - 1,來保証對稱。

因為讀了大哥的說明,覺得這樣的功能應該能用RegEx完成,但實際使用上,卻不知如何下手,因為不知如何偵測對稱的符號,如:()、{}、<>、…等

自己找了些資料,應是與群組建構有關,相關的說明實在不足,小弟實在不知若何實作

這是目前初步的想法,實際的情況會更複雜些,主要問題還是在於如何限制抓取的資料,達到抓出整個function、for、…等的完整內容

以上是小弟的問題,請您指教

# Jeffrey said on 25 October, 2007 10:35 AM

To William, 我做了一個簡單的示範(http://blog2.darkthread.net/blogs/darkthreadtw/archive/2007/10/26/1197.aspx),再玩下去就是苦工了,恕我先睡了,哈!

我想我的Sample中有提示了處理類似問題的要點,應該對你會有些幫助。

# kennyshu said on 25 October, 2007 11:10 AM

想請教一下 ".*?" 這三個符號連起來是什麼意思呢?

第一個"."我知道是什麼意思,不過後面兩個次數修飾字元連在一起我就不知該如何解釋了…

# William said on 26 October, 2007 03:18 AM

感謝大哥的指點,先試著做做看

若小弟之後有什麼想法或心得,再和大家分享

kennyshu:

有關.*?},配合著(?ims),"."抓取所有的字元(包括 \n),所以{.*}便是抓取"{"、"}"內所有的字元,至於?,實際看結果的話,它能限制抓到一組最外層的{}組合,不加的話便無法區別不同的function,小弟才淺,無法解釋原因。

但以下面的例子來測試的時候,會少抓到一個"}"。

所以現在的動作實際上是從 "{" 開始,一直抓到碰到第一個 "}"為止。(第二個程式只是讓語法的效果更清楚些)

慨念上來說,這便是大哥先前所提到的,「剝洋蔥」的做法。

main()

{

 int count=0;

 printf ("hello world");

 for (i=0; i<10; i++)

 {

     count++;

 }

}

-------------------------------------------------

以這個例子來測試,會看得更明顯

main()

{

 int count=0;

 printf ("hello world");

 for (i=0; i<10; i++)

 {

     count++;

 }

 printf ("hello world2");

for (i=0; i<10; i++)

 {

     count++;

 }

}

# kennyshu said on 26 October, 2007 10:49 AM

感謝William的指導~

您的問題:只抓到一層{},我已經發現並且回覆在Jeffrey的示範網頁那邊了,我自己測試過無論有幾層都可以完整抓到,不過還沒測試Jeffrey前輩所言的特殊案例。如果發現我寫的有問題請留言回覆,我會在測試看看~

至於您這個範例,試試看"(?ims)\S+[(].*?[)]\s*{.*?}\s+}"

# William said on 22 April, 2008 03:32 AM

最近又突然用RegEX試了一下程式分析,拿了別人的程式來試,發現一個有點麻煩的問題。

有些editor在編輯時,斷行使用\r\n、但有些只要\n就行了,這會造成比對時的錯誤,因為比對時遇到\n時並不會當成新的一行,請問大哥有遇到過相關的問題嗎?

另外,我在讀入程式檔時,有時在bebug時會想看到讀入字串中的逸出字元,如\n、\t\、\r等,但VS2005似乎沒辦法讓我很容易的看到…,請問有什麼辦法解決嗎?

還請大哥撥空指導

# Jeffrey said on 22 April, 2008 04:14 AM

To William, 關於\r\n與\n的差異問題,我慣用的手法是先對檔案做一次前置處理,Replace("\r\n", "\n"),這樣,不管來源為何,一律用\n來分列即可。

你問的第二個問題我看不太懂,不知你想看到這些Escape字元的用意及後續應用為何?

# ddman said on 28 October, 2010 11:07 PM

""原文片段

比對字串中如有用到. $ ^ { [ ( | ) * + ? \等字串,要記得用方括號夾起來[]。例如: 要比對NT$, 要寫成NT[$]。

""原文片段

一般好像都是用NT\$

#said on 27 June, 2014 09:49 AM

你好

初次接觸regex,但有問題無法在google上找到,所以在此留言

下方是我的原始碼

使用VC++2010 express

#include "stdafx.h"

#using <System.dll>

using namespace System;

using namespace System::Text::RegularExpressions;

int main(array<System::String ^> ^args)

{

Regex^ rx = gcnew Regex( "\s");

   Console::WriteLine(L"Hello World");

   return 0;

}

下方是

建置方案後產生訊息

1>------ 已開始建置: 專案: program0627-4, 組態: Debug Win32 ------

1>  stdafx.cpp

1>  AssemblyInfo.cpp

1>  program0627-4.cpp

1>program0627-4.cpp(11): warning C4129: 's' : 無法辨識的字元逸出序列

1>  正在產生程式碼...

1>  .NETFramework,Version=v4.0.AssemblyAttributes.cpp

1>  program0627-4.vcxproj -> C:\Documents and Settings\Administrator\my documents\visual studio 2010\Projects\program0627-4\Debug\program0627-4.exe

========== 建置: 1 成功、0 失敗、0 最新、0 略過 ==========

提問

program0627-4.cpp(11): warning C4129: 's' : 無法辨識的字元逸出序列

因為waining C4129,我字串的搜索結果都和我預期的不一樣.

可以幫我解答?

# Jeffrey said on 27 June, 2014 08:08 PM

to 許,

我想問題出在 Regex^ rx = gcnew Regex( "\s");

應寫成 Regex("\\s");

#said on 28 June, 2014 05:46 AM

謝謝你的幫忙!

原來我在網頁regexlib.com/RETester.aspx.

#said on 28 June, 2014 05:54 AM

謝謝你的幫忙!

原來我在regex tester網頁寫的規則

是不能直接貼在VS上

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<June 2007>
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication