2011年11月30日 星期三

正規表示法(Regular Expressions)

正規表示法(Regular Expressions)

正規表示法並不是Perl所獨有的,但在Perl裡卻經常被拿來使用。

這也是為什麼很多人聽到Perl,直覺都會想到它的文字處理能力。

正規表示法。簡單來說就是比對樣式(pattern),

依照需求而去「尋找」我們所要的字串,並對這些字串加以處理。

比對符號是用等號波浪號(=~)來表示,

一樣我們直接從例子來看會比較清楚:

$string = "No pain, no gain.";# 給予一段字串 並存到變數裡

if ($string =~ /gain/)# 當$string裡面有gain時

{

print "match!";# 印出match訊息

}

else # 若否

{

print "sorry!";# 印出sorry訊息

}

執行一次結果就會是: match!


知道簡單的比對樣式(pattern)後,

來看看一些不一樣的用法吧!

有的時候我們需要找的東西不只一種,

比方說我要找美妝用品,但是只想看兩種品項的文章,

就可以把if內的條件寫成這樣:

if ($string =~ /(化妝水|面膜)/)# 當$string裡面有化妝水 或是(|符號)面膜

當然,也可以指定說,關鍵字一定都要符合的,

像是我只要某些牌子的手機:

if ($string =~ /Apple/ && $string =~ /phone/)# 當$string裡面有Apple 並且(&&符號)有phone

如此一來,找到的東西就只會同時有Apple以及phone。

值得一提的是,因為Perl正規表示法中,

樣式比對要考慮到大小寫、空格、換行、特殊字元等,

所以上面那行程式碼,萬一遇到apple的a是小寫時,

比對就會失敗,

要避免這個問題,我們可以使用一些「字符集合」

我們使用中括號([])表示 「或者」 的意思:

if ($string =~ /[aA]pple/ && $string =~ /[pP]hone/)# 首字大小寫都允許

如此一來,就可以避開首字大小寫的問題。


不過,如果每個字首都要定義大小寫,恐怕會寫成:

$string=~ /(/a|b|c|d|.......|z|A|B|C|D|......|_|)/

整串寫完真的會很壯觀呢!

當然,「字符集合」仍然有更方便的用法。

$string=~ /[a-zA-Z]/

短短這段字符集合就可以取代上面那段看起來很壯觀的程式碼,

表示大小寫都可以算比對成功。

$string=~ /[!@#$%^&*()_+=]/

而這樣就可以比對特殊字元!

但是問題來了,如果我要比對的是這個連字符號(-)呢?

上面的例子我們看到連字符號(-)已經被拿來當作取出a-z,A-Z的各個字符,

因此會使用到跳脫字元(escape character)

$string=~ /[\-]/

使用反斜線符號(\)表示下一個字元要跳脫(表示不做特殊用途)

因此就可以抓到連字符號。

而如果我們不要某些字,可以會使用插入符號(^)

$string=~ /[^A]/

表示我不要有A字元的樣式,以此類推。


知道使用字符集合增加樣式比對的複雜性後,

Perl的正規表示法其實也有特殊字元可以使用,

像是剛剛這段 $string =~ /[a-zA-Z]/

就可以簡寫成$string =~ /\w/

而常用的一些Perl正規表示法的特殊字元有:

\w:就等同於[a-zA-Z]的字符集合。

\W:就等同於[^a-zA-Z]的字符集合。 (大寫表示的意思)

\s:就等同於空白字元(space)。

\S:就等同於空白字元的字符集合。

\d:就等同於[0~9]的字符集合。

\D:就等同於[^0~9]的字符集合。

當然,你也可以這麼使用,

假設我想比對的樣式,長度在5~10之間,

那麼一樣可以使用修飾字元,

使用的方式就是使用大擴號({})來表示:

$string =~ /\w{5,10}/

如此一來就可以抓到長度5到10的英文字。


再來我們要回來看看正規表示法的修飾字元

一樣我們用例子來說明,

如果我想抓取一段句子中,perl這個字串,

那麼你會遇到一個問題,除了首字大寫(Perl)之外,

句子中可能會有全部小寫(perl),以及全部大寫(PERL)的情況,

那麼表示起來可能又會寫一大段,

這時候修飾字元就派上用場了:

$string=~ /perl/i

注意到了嗎?樣式比對後面加了一個 i 表示 ignore (忽略)

表示我要忽略大小寫,如此一來上面的問題就可簡單處理掉了!

其他修飾字元還有:

$string=~ /perl/s

樣式比對後面加了一個 s 表示我要進行跨行(跨越\n)做比對

$string=~ /perl/g

樣式比對後面加了一個 g 表示我要對樣式進行全面比對(Match globally)

$string=~ /perl/o

樣式比對後面加了一個 o 表示我只要比對一次(Match once)

當然,你也可以把這些修飾字元都一起使用:

$string=~ /perl/io

樣式比對後面加了一個 i 以及 o

表示我想要進行忽略大小寫的樣式比對,並且只要成功比對一次就好。


知道如何比對樣式後,有時我們也會需要取得比對結果,

在Perl的正規表示法裡,有預設變數來讓你取得比對的結果,

預設的變數是使用$1來接比對後的結果,

然而,如果還有其他比對結果,則會用到$2,

如果還有就會持續遞增:像是$1, $2, $3 ...

而要把我們想要取得的樣式存到這些變數裡,

方法也很簡單,就是用小括號()把這些樣式包起來。

一樣我們從例子來說明:

$string = "God helps those who help themselves.";# 給予一段字串 並存到變數裡

if ($string =~ /(God)(.*)(those)/i)# 當$string裡面有(God)以及(任何字元)還有(those)時

# 因為修飾字元有i 所以會忽略大小寫 並且 因為有三個小括號

# 所以成功比對的樣式會依序存入3個預設變數 即是 $1 $2 $3

{

print "$1 \n";# 印出$1以及換行

print "$2 \n";# 印出$2以及換行

print "$3 \n";# 印出$3以及換行

}

執行結果就會看到,

這些比對的樣式依序被抓取出來。

$1裡面存的就會是"God"

$2裡面存的就會是" helps " (空格也會被抓進來)

$3裡面存的就會是"those"


接下來我們來看看更精確的描述正規表示式,

我們會使用到的是定位點

透過定位點,可以表示我要找的樣式是在開頭,或是在結尾。

$string=~ /^perl/

插入符號(^)在這裡表示以這個樣式「開頭」

需要注意的是,在這裡所使用的插入符號(^)跟「字符集合」意思不同

一個表示「開頭」,一個表示「非」,可別搞混了!

$string=~ /perl$/

錢字符號($)在這裡表示以這個樣式「結尾」


再來是正規表示法的比對與替換

如果我們要把比對好的結果作替換,

寫法稍微有點不同,格式會長得像這樣:

$string=~ s/原本的樣式/替換的樣式/

利用三個反斜線夾住要比對以及替換的樣式,

並且在前面加上一個 s 表示 substitute (替換)。

後面一樣可以加上一些修飾字元,像是上面提到的忽略大小寫(i)等等。

我們直接看例子:

$string = "我不喜歡吃苦瓜,我還是不喜歡吃苦瓜。";# 給予一段字串 並存到變數裡

$string =~ s/苦瓜/鹹蛋/gi; # 把苦瓜替換成鹹蛋 全部都換(g)並忽略大小寫(i)

print "$string";# 印出$string

執行結果可以看到所有的苦瓜,都被換成了鹹蛋!


最後是正規表示所用到的萬用字元(.)問題,

Perl的正規表示是貪多比對

即是Perl預設會去找到符合需求的「最大集合」。

用例子看會比較清楚:

$string = "我不喜歡吃苦瓜,我還是不喜歡吃苦瓜,我真的不喜歡吃苦瓜。";

if ($string =~ /我不喜歡(.*)苦瓜/)# 取得夾在 我不喜歡 以及 苦瓜 之中的樣式

{

print "$1 \n";# 印出$1以及換行

}

執行以後會發現,

我們原本希望只抓到"吃",這一個字,

但Perl的預設就是找到「最大集合」,

這就是所謂的貪多比對

如果要避開這個問題,我們會在比對的量詞後面,加上問號(?)來表示不貪多

因此我們把上面的式子改寫成為:

$string = "我不喜歡吃苦瓜,我還是不喜歡吃苦瓜,我真的不喜歡吃苦瓜。";

if ($string =~ /我不喜歡(.*?)苦瓜/)# 用問號表示不貪多比對

{

print "$1 \n";# 印出$1以及換行

}

如此一來,執行結果就會符合我們的期待


正規表示式就到這邊結束,接下來要回到控制敘述的迴圈操作

沒有留言:

張貼留言