程式扎記: [Perl 學習手冊] CH09 : 以正規表示式處理文字

標籤

2010年9月4日 星期六

[Perl 學習手冊] CH09 : 以正規表示式處理文字


前言 :
目前為止我們只知道如何進行樣式比對, 但正規表示式也可以用來改變文字. 透過接下來的介紹, 你就可以先用樣式比對出一段文字, 接著把它們換掉.

以 s/// 進行置換 :
如果你要進行搜尋並取代 (search and replace), 你可以使用 Perl 的 s/// 置換運算符. 此運算符會對所指定的樣式進行比對, 然後把比對到的部分代換成所指定的字串 :
  1. $_ = "He's out bowling with John tonight.";  
  2. s/John/Peter/; # 用 Peter 取代 John  
  3. print "$_\n";  
如果比對失敗, 什麼事也不會發生, 變數也不受影響. 當然樣式 (Pattern) 與代換字串還可以更複雜. 底下的代換字串用到了第一個比對變數, 也就是 $1 :
  1. $_ = "He's out bowling with John tonight.";  
  2. s/with (\w+)/against $1 team/;  
  3. print "$_\n";  
s/// 會傳回有用的布林值 ; 它在替換成功時為真, 否則為假.

- 以 /g 進行全域替換
在上面例子中, 即使有其他可以代換的部分, s/// 還是只會比對一次代換. 當然這是預定的行為. 你可以使用 /g 修飾符讓 s/// 進行所有可能, 互不重疊的代換 :
  1. $_ = "home, sweet home!";  
  2. s/home/cave/g;  
  3. print "$_\n";  
一個常見的全域代換應用是縮減空白, 也就是將任何連續的空白轉換成單一空格 :
  1. $_ = "Input data\t may have       extra whitespace.";  
  2. s/\s+/ /g;  
接著你可能想問怎麼縮減開頭與結尾的空白, 這很簡單參考如下 :
s/^s+//; # 將開頭的空白代換成空字串
s/\s$//; # 將結尾的空白代換成空字串

至於簡潔的寫法則是使用擇一比對的技巧並配合上 /g 修飾符, 參考如下 :
s/^\s+|\s+$//g # 去掉頭尾的空白

- 不同的界定符號
就像 m// 與 qw// 一樣, 我們也可以改變 s/// 的界定符號(delimiter). 但是置換運算會用到三個界定符號, 所以情況有點複雜.
若是一般沒有左右之分 (分成對) 的字符, 用法便跟斜線一樣, 只用三個就可以, 範例如下 : (使用 # 當界定符)
s#^https://#http://#;

但如果使用有左右之分的成對字符, 就必須使用兩對 : 一對用於包住樣式, 一對用於包住代換字串. 此狀況下, 包住樣式與字串的界定字符不需要相同. 事實上, 包住字串的界定符甚至可以用非成對的界定符號, 範例如下 :
s{john}{peter};
s{john}(peter);
s#peter#;

- 選項修飾符
不僅是 /g 修飾符, 置換運算符也可以使用我們在一般樣式比對所到的 /i, /x 與 /s 修飾符 :
s#wilma#Wilma#gi ; #將所有 wilMA etc 等 代換成 Wilma
s{_END_.*}{}s; # 將結尾標記 _END_ 之後所有一切刪除.

- 繫結運算符
同在使用 m// 時提到過, 我們可以用繫結運算符為 s/// 選擇不同的目標 :
$file_name =~ s#^.*/##s; # 對目標 $file_name 進行操作

- 大小寫轉換
在置換運算中, 你通常會想讓所代換的單字具有適當的大小寫. 這很容以用 Perl 做到 ; 只要使用到某些倒斜線規避序列就搞定. \U 規避序列會將其後的所有字串轉換成大寫 :
  1. $_ = "I saw John with Peter.";  
  2. s/(john|peter)/\U$1/gi;  
同理 \L 規避序列會將其後的字符轉成小寫. 預設上它們會影響之後全部的(代換)字串, 不然你也可以用 \E 來關閉大小寫轉換功能 :
  1. $_ = "I saw John with Peter.";  
  2. s/(\w+) with (\w+)/\U$2\E with $1/i;  
使用小寫形式 (\u 與 \l) 只會影響之後的第一個字符. 你甚至可以將它們併用. 譬如你可以使用 \u 與 \L 來表示 "全部轉小寫, 但首字母大小" :
$_ = "I saw joHn with peTer.";
s/(\w+) with (\w+)/\u\L$2\E with \u\L$1\E/i;
 # 注意 \E 還是需要

split 函式 :
一個會用到正規表示式的函示 split (split up a string using a regexp delimiter) , 它會根據分隔字符切開一個字串. 這對處理以跳格, 冒號, 空白或是任意符號所分隔的資料相當有用. 只要你能將分隔字符寫成樣式, 就可以使用 split. 它的用法如下 :
@fields = split /seperator/, $string;

split 函式將會以作為分隔符的樣式掃過指定字串 $string, 並且傳回該樣式所分隔出來的一串欄位 (子字串). 下面以冒號為分隔符作為範例 :
@fields = split /:/, "abc:def:g:h"; # 產生 ("abc", "def", "g", "h")

如果兩個分隔符放在一起, 就會產生空的欄位. 這裡有個規則要注意 : split 會保留開頭處的空欄位, 卻會捨棄結尾處的空欄位. 例如 :
@fields = split /:/, ":::a:b:c:::"; # 產生 ("", "", "", "a", "b", "c")

split 預設會以空白字符拆開 $_ :
$_ = "This is a \t test.\n";
my @args = split;
 # 等於 split /\s+/, $_

join 函式 :
join (join a list into a string using a separator) 函式並沒有用到樣式, 而起所達成的效果與 split 恰好相反 ; split 會將字串分解為數個片段 (子字串), 而 join 會將這些片段黏合成一個字串. 它的用法如下所示 :
my $result = john $glue, @pieces;

你可以將 join 的第一個引數想成 黏膠 (glue), 它可以是任何字串. 其餘的引數則是一串片段. join 會把黏膠放進每個片段之間, 並且回傳所得到的字串 :
my $x = join ":", 4, 5, 6, 10, 12; # 得到 4:5:6:10:12

雖然 split 與 join 常常搭配使用, 但是請別忘記 join 的第一個引數是字串, 而不是樣式!

串列語境下的 m// :
使用 split 時, 樣式的意義是分隔符 ; 也就是說資料中沒有用處的部分. 但是有時候寫下想要的部分反而比較簡單. 在串列語境下使用樣式比對運算符 (m//) 時, 如果比對成功, 其傳回值為所有比對變數的內容所構成的串列; 如果比對失敗, 則會傳回空字串 :
  1. $_ = "Hello there, neighbor!";  
  2. my($first, $second, $third) = m/(\S+) (\S+), (\S+)/;  
  3. print "$second is my $third\n";  
這麼一來就可以輕鬆為那些可能會在下一次樣式繼續沿用的 "比對變數" 取一些好記且好用的名字. 你之前在 s/// 的例子中使用的修飾符 \g 同樣也可以在 m// 上使用. 其效果就是讓樣是能夠比對的更多字串. 下面例子具有一對圓括號的樣式, 會在每一次比對成功時傳回一分記憶 :
my $text = "John dropped a 5 ton granite block on Mr. Peter";
my @words = ($text =~ m/([a-z]+)/gi);
print "Result: @words\n";
 # Result: John dropped a ton granite block on Mr Peter

如果不只一對圓括號, 那麼每次成功比對, 就可能傳回一個以上的字串. 假設我們想要把一個字串變成雜湊, 就可以這樣做 :
  1. my $data = "Lee John Ken Wang";  
  2. my %last_name = ($data =~ m/(\w+) (\w+)/g);  
每成功比對一次, 就回傳回一對被記憶下來的值. 這一對的值剛好就成了新雜湊裡的 "鍵/值" 對了.
This message was edited 3 times. Last update was at 28/08/2010 16:55:56

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!