Linux系統程式設計(16)——正規表示式入門

尹成發表於2014-07-26

 

字元是計算機軟體處理文字時最基本的單位,可能是字母,數字,標點符號,空格,換行符,漢字等等。字串是0個或更多個字元的序列。文字也就是文字,字串。說某個字串匹配某個正規表示式,通常是指這個字串裡有一部分(或幾部分分別)能滿足表示式給出的條件。

 

在編寫處理字串的程式或網頁時,經常會有查詢符合某些複雜規則的字串的需要。正規表示式就是用於描述這些規則的工具。換句話說,正規表示式就是記錄文字規則的程式碼。

 

很可能你使用過Windows下用於檔案查詢的萬用字元(wildcard),也就是*和?。如果你想查詢某個目錄下的所有的Word文件的話,你會搜尋*.doc。在這裡,*會被解釋成任意的字串。和萬用字元類似,正規表示式也是用來進行文字匹配的工具,只不過比起萬用字元,它能更精確地描述你的需求——當然,代價就是更復雜——比如你可以編寫一個正規表示式,用來查詢所有以0開頭,後面跟著2-3個數字,然後是一個連字號“-”,最後是7或8位數字的字串(像010-12345678或0376-7654321)。

 

1、如果需要更精確的說法,\b匹配這樣的位置:它的前一個字元和後一個字元不全是(一個是,一個不是或不存在)\w。

2、換行符就是'\n',ASCII編碼為10(十六進位制0x0A)的字元。

學習正規表示式的最好方法是從例子開始,理解例子之後再自己對例子進行修改,實驗。下面給出了不少簡單的例子,並對它們作了詳細的說明。

假設你在一篇英文小說裡查詢hi,你可以使用正規表示式hi。

這幾乎是最簡單的正規表示式了,它可以精確匹配這樣的字串:由兩個字元組成,前一個字元是h,後一個是i。通常,處理正規表示式的工具會提供一個忽略大小寫的選項,如果選中了這個選項,它可以匹配hi,HI,Hi,hI這四種情況中的任意一種。

不幸的是,很多單詞裡包含hi這兩個連續的字元,比如him,history,high等等。用hi來查詢的話,這裡邊的hi也會被找出來。如果要精確地查詢hi這個單詞的話,我們應該使用\bhi\b。

\b是正規表示式規定的一個特殊程式碼(好吧,某些人叫它元字元,metacharacter),代表著單詞的開頭或結尾,也就是單詞的分界處。雖然通常英文的單詞是由空格,標點符號或者換行來分隔的,但是\b並不匹配這些單詞分隔字元中的任何一個,它只匹配一個位置。

假如你要找的是hi後面不遠處跟著一個Lucy,你應該用\bhi\b.*\bLucy\b。

這裡,.是另一個元字元,匹配除了換行符以外的任意字元。*同樣是元字元,不過它代表的不是字元,也不是位置,而是數量——它指定*前邊的內容可以連續重複出現任意次以使整個表示式得到匹配。因此,.*連在一起就意味著任意數量的不包含換行的字元。現在\bhi\b.*\bLucy\b的意思就很明顯了:先是一個單詞hi,然後是任意個任意字元(但不能是換行),最後是Lucy這個單詞。

 

如果同時使用其它元字元,我們就能構造出功能更強大的正規表示式。比如下面這個例子:

0\d\d-\d\d\d\d\d\d\d\d


匹配這樣的字串:以0開頭,然後是兩個數字,然後是一個連字號“-”,最後是8個數字(也就是中國的電話號碼。當然,這個例子只能匹配區號為3位的情形)。

這裡的\d是個新的元字元,匹配一位數字(0,或1,或2,或……)。-不是元字元,只匹配它本身——連字元或者減號。

為了避免那麼多煩人的重複,我們也可以這樣寫這個表示式:0\d{2}-\d{8}。這裡\d後面的{2}({8})的意思是前面\d必須連續重複匹配2次(8次)。

 

在linux下我們用grep在一個檔案中找出包含某些字串的行,比如在標頭檔案中找出一個巨集定義。其實grep還可以找出符合某個模式(Pattern)的一類字串。例如找出所有符合xxxxx@xxxx.xxx模式的字串(也就是email地址),要求x字元可以是字母、數字、下劃線、小數點或減號,email地址的每一部分可以有一個或多個x字元,例如abc.d@ef.com、1_2@987-6.54,當然符合這個模式的不全是合法的email地址,但至少可以做一次初步篩選,篩掉a.b、c@d等肯定不是email地址的字串。再比如,找出所有符合yyy.yyy.yyy.yyy模式的字串(也就是IP地址),要求y是0-9的數字,IP地址的每一部分可以有1-3個y字元。

 

如果要用grep查詢一個模式,如何表示這個模式,這一類字串,而不是一個特定的字串呢?從這兩個簡單的例子可以看出,要表示一個模式至少應該包含以下資訊:

 

字元類(Character Class):如上例的x和y,它們在模式中表示一個字元,但是取值範圍是一類字元中的任意一個。

 

數量限定符(Quantifier):郵件地址的每一部分可以有一個或多個x字元,IP地址的每一部分可以有1-3個y字元

 

各種字元類以及普通字元之間的位置關係:例如郵件地址分三部分,用普通字元@和.隔開,IP地址分四部分,用.隔開,每一部分都可以用字元類和數量限定符描述。為了表示位置關係,還有位置限定符(Anchor)的概念,將在下面介紹。

 

規定一些特殊語法表示字元類、數量限定符和位置關係,然後用這些特殊語法和普通字元一起表示一個模式,這就是正規表示式(Regular Expression)。例如email地址的正規表示式可以寫成[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+\.[a-zA-Z0-9_.-]+,IP地址的正規表示式可以寫成[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}。下一節介紹正規表示式的語法,我們先看看正規表示式在grep中怎麼用。例如有這樣一個文字檔案testfile:

 

192.168.1.1
1234.234.04.5678
123.4234.045.678

abcde查詢其中包含IP地址的行:

$ egrep'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' testfile
192.168.1.1
1234.234.04.5678

egrep相當於grep -E,表示採用Extended正規表示式語法。grep的正規表示式有Basic和Extended兩種規範,它們之間的區別下一節再解釋。另外還有fgrep命令,相當於grep -F,表示只搜尋固定字串而不搜尋正規表示式模式,不會按正規表示式的語法解釋後面的引數。

 

注意正規表示式引數用單引號括起來了,因為正規表示式中用到的很多特殊字元在Shell中也有特殊含義(例如\),只有用單引號括起來才能保證這些字元原封不動地傳給grep命令,而不會被Shell解釋掉。

 

192.168.1.1符合上述模式,由三個.隔開的四段組成,每段都是1到3個數字,所以這一行被找出來了,可為什麼1234.234.04.5678也被找出來了呢?因為grep找的是包含某一模式的行,這一行包含一個符合模式的字串234.234.04.567。相反,123.4234.045.678這一行不包含符合模式的字串,所以不會被找出來。

 

grep是一種查詢過濾工具,正規表示式在grep中用來查詢符合模式的字串。其實正規表示式還有一個重要的應用是驗證使用者輸入是否合法,例如使用者通過網頁表單提交自己的email地址,就需要用程式驗證一下是不是合法的email地址,這個工作可以在網頁的Javascript中做,也可以在網站後臺的程式中做,例如PHP、Perl、Python、Ruby、Java或C,所有這些語言都支援正規表示式,可以說,目前不支援正規表示式的程式語言實在很少見。除了程式語言之外,很多UNIX命令和工具也都支援正規表示式,例如grep、vi、sed、awk、emacs等等。“正規表示式”就像“變數”一樣,它是一個廣泛的概念,而不是某一種工具或程式語言的特性

 

 

相關文章