何謂 Regular Expression?
Regular expression (後文簡稱 RE)常見用來在某段文字中搜尋需要的字串。而 RE 的作法就是藉由“樣版(pattern)”來做比對。
RE 是一種由字元組成的樣式,用來比對資料,看看究竟符合或不符合這個樣式,然後可做進一步的處理。比如說:電子郵件位址的樣式,可寫成: .+\@.+。
RE 也可以稱為是樣式(Pattern),本身自成一種小型的程式語言。
例子
熟悉以前 DOS 作業系統的人都知道,‘*’和‘?’字元在顯示目錄的命令中分別用來代表零或多個任意字元,以及單一一個任意字元(一定要有一個)。
所以當我們使用像是“text?.*”的樣版時,下面列出的檔案都匹配該樣版:
- textf.txt
- text1.asp
- text9.html
而下列的檔案名稱則不符合:
- text.txt
- text.asp
- text.html
上面的例子展現了 RE 的運作模式。
為何要使用 RE
常見的 RE 應用範圍包括了:
- 將某份 HTML 檔案中的某些特定的標籤移除。
- 檢查電子郵件的位址是否合法。
基本上我們可以對字串進行下列的 RE 操作:
- 驗證某個樣版:在一串字串中搜尋,檢查是否該樣版符合某部分子字串;回傳 true 或 false。
- 從字串中取出子字串:在字串中搜尋某個字串,並將它取出。
- 取代某個子字串:在字串中搜尋滿足範本的字串,並將它以其它字串取代之。
哪些地方在應用 RE
在文件處理方面,RE 經常可以發揮強大的功能。只要談到 RE,大家第一個聯想到的多半是 Perl 這個語言。RE 為 Perl 語言的基礎,因此其內建就支援 RE。其它的程式語言也可以藉由使用外部函式庫來支援使用 RE:
- VBScript(5.x 以上):可藉由 RegExp 物件來使用 RE。
- JScript(Version 5.x 以上):也是藉由 RegExp 物件來使用 RE。
- C++ 經由 Regex++ 函式庫和 PCRE(Perl Compatible Regular Expression)函式庫來支援。
- Java 內建支援 RE。
- Microsoft .NET framework 內建支援 RE(經由使用 System.Text.RegularExpression 命名空間)。
- PHP 內建 Perl 相容的函式,或是使用 POSIX 延伸的 RE 函式。
本文的程式碼以 Perl 語言為基準,至於其它語言因為它們彼此設計上的不同,語法稍有差異,但是大多大同小異。
在 Perl 中使用 RE
這裡我們簡單的說明要如何在 Perl 中使用 RE。
依樣版搜尋字串
expression =~ m/pattern/[switches]
在字串表示式中搜尋符合‘pattern’的子字串出現位置,並回傳該子字串(分別儲存於變數 $1}、$2、$3...中)。其中的“m”代表“match”。
例子
$test = "this is just one test"; $test =~ m/(o.e)/;
會回傳“one”於變數 $1 中。
替換字串
expression =~ s/pattern/new text/[switches]
在 expression 字串中搜尋符合 pattern 的子字串,並以 new text 替換找尋到的子字萬,其中的 s 代表 substitute。
例子
$test = "this is just one test"; $test =~ s/one/my/
將會以 my 替換 one,因此會將 this is just my test 字串存放於 $test 變數中。
Regular Expression 基本語法
類似於 C++ 中的跳脫字元,RE 的 meta character 必須以 \ 加以跳脫,比如若是要指定中括弧([),我們必須以 \[ 表示(這裡提到的跟你使用的程式語言有關;這裡是針對 Perl 來說)。
重要的 Meta Character 列表
Character | 描述 |
---|---|
\ | 標示下一個字元為一個特殊的字元(special character)、字母(literal)、backreference 或是 octal escape。比如,‘n’相當於去比對字元“n”。‘\n’則代表比對換行字元,‘ ’會配對到“\”等等。 |
. | 除了換行字元(\n)外,比對所有字元。若是要包含換行字元 |
Character 種類(Class)
一個字元種類(character class)是由一個或多的字元所組成的群組,這些群組內的字元被以包含於 [...] 來表示。比如說,“B[iu]rma”將會配對到 Birma 或是 Burma;也就是說,B 後面跟隨著 i 或是 u,後面再加上 rma。
換言之,字元種類代表:配對任何在該種類中的單一字元(match any single character of that class)。
RE 中也存在一些相反的字元種類:negotiated character class;其代表,配對任何一種不存在於該種類中的單一字元。比如說‘[^1-6]’將會比對任意字元,除了數字 1 到 6。
數量詞(Quantifier)
假如我們無法確定到底會有多少個字元,我們可以使用數量詞來指明某個字元可以出現的次數;我們可以使用像“Hel+o”,來代表 He 後面接著一或多個 l,後面再加上一個 o。
字元 | 描述 |
---|---|
* | 出現零次或多次以上;比如‘zo*’會配對到 z 或 zoo;* 相等於 ‘{0,}’。 |
+ | 出現一次或多次;比如‘zo+’配對到‘zo’和 zoo,但是不包括 z;+ 相等於‘{1,}’。 |
? | 出現零次或一次。比如‘do(es)?’配對在 do 或 does 中的 do;?; 相當於‘{0,1}’。 |
{n} | n 為非負整,代表確實的配對次數;比如‘o{2}’不會配對到位於 Bob 中的 o,而會配對到位於 foo 中的兩個 o。 |
{n,} | n 為非負整,至少配對到 n 次;比如‘o{2,}’不會配對到位於 Bob 中的 o,但是會配對到 fooooooood 中所有的 o。‘o{1,}’相當於‘o+’;‘o{0,}’相當於‘o*’。 |
{n,m} | m 和 n 皆為非負整數,其中 n 要小於等於 m;配對至少 n 次,至多 m 次。比如‘o{1,3}’配對到 fooooood 中的前面三個 o;‘o{0,1}’相當於‘o?’。要注意的是,在逗號和數字之間,請不要出現任何的空白。 |
Greedy
要注意到的一件事是,‘*’和‘+’是 greedy;它們會盡量去配對,配對出越多的會優先選取。比如說:
$test = "hello out there, how are you"; $test =~ m/h.*o/
表示:找出以 h 開頭,後面跟著多個任意的字元,最後以 o 結束。你也許會認為它會配對到“hello”,但是事實上,它配對到的是“hello out there, how are yo”,因為 RE 的組態是 greedy,所以它會搜尋直到最後一個“o”,而,這個例子中,就是在 you 中“o”。
你可以在表示式加上一個“?”,明確的指示要使用“ungreedy”方法。下面是一個例子:
$test = "hello out there, how are you"; $test =~ m/h.*?o/
上面的例子會尋找到“hello”;因為這個樣版的意義是去尋找一個“h”,後面接著多個任意字元,直到遇到第一次出現的“o”。
錨點(Anchor)
行起頭和行結尾
要檢查某一行的起始或結尾(或是字串),你可以使用 ^ 和 $ 這兩個 meta character。比如說,“^thing”,會配對到某個以“thing”起始的行。而“thing$”則會配對到某個以 “thing”結尾的行。
字邊界(Word Boundary)
‘\b’和‘\B’分別用來測試字邊界,以及非字邊界。下面這個例子:
$test =~ m/out/
對於“speak out loud”這個句子而言,會配對到“out”,但是也會配對到在“please don't shout at me”句子中的 out。若是要避免這種情況出現,你可以在樣式之前加上一個字邊界的錨點:
$test =~ m/\bout/
這樣,這個樣式只會尋找到以字邊界起始的“out”,而不包含在某個字中的“out”。
置換(alternation)和群組(grouping)
置換允許使用‘|’字元來在兩個或多個可替換的選擇中做選擇。在圓括弧中使用“(...|...|...)”,其允許你群組多個置換。
圓括弧本身用來抓取某個子字串,以便在其後對其做處理,並將它們儲存於 Perl 內建的 $1、$2、... 及 $9 變數。
下面是一個例子:
$test = "I like apples a lot"; $test =~ m/like (apples|pines|bananas)/
將會配對成功,因為“apples”是三個置換人選中的其中之一,因此,會尋找到“like apples”。另外,圓括弧也會抓取到“apples”,並將它儲存於 $1 變數中,作為一個後參考(backreference)。
後參考(backreference)、往前看(lookahead-condition)和往後看(lookbehind-condition)
後參考
RE 一個重要的特性就是,它可以儲存之前配對到的子字串於某個變數中,以便之後處理。這是藉由在圓括弧中放置子字串來達到。這些抓取到的字串會被儲存於 Perl 的內建變數 $1、 $2、... $9。
假如你不需要抓取某個子字串,但是需要用到圓括弧來群組字串,你可以使用“?:”來避免抓取。
例子:
$test = "Today is monday the 18th."; $test =~ m/([0-9]+)th/
會將“18”儲存於 $1 變數中,而
$test = "Today is monday the 18th."; $test =~ m/[0-9]+th/
$1 將不會儲存任何東西,因為並未使用圓括弧。
$test = "Today is monday the 18th."; $test =~ m/(?:[0-9]+)th/
也不會儲存任何東西在 $1 變數中,因為在圓括弧中,有使用“?:”。另一個例子可以使用在置換中:
$test = "Today is monday the 18th."; $test =~ s/ the ([0-9]+)th/, and the day is $1/
儲存於 $test 變數中的會是“Today is monday, and the day is 18.”。
你也可以藉由使用 \1、\2、...\9 在尋找(query)中使用後參考(backreference),參考到之前尋找到的子字串。比如說,下面的例子會移除掉重複的字:
$test = "the house is is big"; $test =~ s/\b(\S+)\b(\s+\1\b)+/$1/
會在 $test 變數中儲存“the house is big”。
往前看和往後看狀態
有時候,我們需要下面幾個例子的配對:「配對它,但是只有在它不是處在另一個東西之前」或是「配對它,但是只有在它並非跟隨在某個東西之後」。若是只有考慮單一一個字元,你可以使用 negotiated character 類別:[^...]。
但是當它有多個字元時,你需要使用所謂的往前看狀態或是往後看狀態。總共有四種可能的類型:
- Positive lookahead-condition '(?=re)' 只配對後面跟隨著 re RE。
- Negative lookahead-condition '(?!re)' 只配對後面不跟隨著 re RE。
- Positive lookbehind-condition '(?<=re)' 只配對前面跟隨著 re RE。
- Negative lookbehind-condition '(?re RE。
範例:
$test = "HTML is a document description-language and not a programming-language"; $test =~ m/(?<=description-)language/
會配對到第一個“language”(description-language),因為前面跟隨著的是“description-”,所以
$test = "HTML is a document description-language and not a programming-language"; $test =~ m/(?會配對到第二個“language”(description-language),因為其並未跟隨在“description”。
更多例子
下面列出更多真實應用的例子。
置換前兩個字:
s/(\S+)(\s+)(\S+)/$3$2$1/尋找 name=value 對:
m/(\w+)\s*=\s*(.*?)\s*$/現在 name 儲存於 $1 中,而 value 則存於 $2。
讀取 YYYY-MM-DD 格式的日期資料:
m/(\d{4})-(\d\d)-(\d\d)/在 $1 中儲存的是 YYYY,在 $2中儲存的是 MM,DD 則存放於 $3。
移除檔名前面的路徑:
s/^.*\///總結
要注意的是,要處理相同的一件事,RE 可以用多種方式來呈現範本,有的表示法執行的速度可能很快,有的可能可讀性較高。
參考
沒有留言:
張貼留言