在 shell 腳本中成功應(yīng)用 sed 編輯器和 gawk 程序的關(guān)鍵在于熟練掌握正則表達(dá)式。正則表達(dá)式并非易事,從海量數(shù)據(jù)中提取出特定信息往往是一項(xiàng)復(fù)雜的任務(wù),因此可能需要耗費(fèi)一些功夫。本章將詳細(xì)探討如何在 sed 編輯器和 gawk 程序中運(yùn)用正則表達(dá)式,從而實(shí)現(xiàn)對(duì)數(shù)據(jù)的精確過(guò)濾。
正則表達(dá)式的定義
要理解正則表達(dá)式,首先需要明確它們的本質(zhì)。本節(jié)將介紹正則表達(dá)式的概念,并探討在 Linux 中如何運(yùn)用這一強(qiáng)大工具。
定義
正則表達(dá)式是你所定義的模式模板(pattern template),Linux 工具可以用它來(lái)過(guò)濾文本。Linux 工具(比如 sed 編輯器或 gawk 程序)能夠在處理數(shù)據(jù)時(shí)使用正則表達(dá)式對(duì)數(shù)據(jù)進(jìn)行模式匹配。如果數(shù)據(jù)匹配模式,它就會(huì)被接受并進(jìn)一步處理;如果數(shù)據(jù)不匹配模式,它就會(huì)被濾掉。
正則表達(dá)式模式利用通配符來(lái)描述數(shù)據(jù)流中的一個(gè)或多個(gè)字符。Linux 中有很多場(chǎng)景都可以使用通配符來(lái)描述不確定的數(shù)據(jù)。在本書(shū)之前你已經(jīng)看到過(guò)在 Linux 的 ls 命令中使用通配符列出文件和目錄的例子。
星號(hào)通配符允許你只列出滿足特定條件的文件,例如:
复制代码
- $?ls?-al?da* -rw-r--r--????1?rich?????rich???????????45?Nov?26?12:42?data -rw-r--r--????1?rich?????rich???????????25?Dec??4?12:40?data.tst -rw-r--r--????1?rich?????rich??????????180?Nov?26?12:42?data1 -rw-r--r--????1?rich?????rich???????????45?Nov?26?12:44?data2 -rw-r--r--????1?rich?????rich???????????73?Nov?27?12:31?data3 -rw-r--r--????1?rich?????rich???????????79?Nov?28?14:01?data4 -rw-r--r--????1?rich?????rich??????????187?Dec??4?09:45?datatest $
da*參數(shù)會(huì)讓 ls 命令只列出名字以 da 開(kāi)頭的文件。文件名中 da 之后可以有任意多個(gè)字符(包括什么也沒(méi)有)。ls 命令會(huì)讀取目錄中所有文件的信息,但只顯示跟通配符匹配的文件的信息。
正則表達(dá)式通配符模式的工作原理與之類似。正則表達(dá)式模式含有文本或特殊字符,為 sed 編輯器和 gawk 程序定義了一個(gè)匹配數(shù)據(jù)時(shí)采用的模板。可以在正則表達(dá)式中使用不同的特殊字符來(lái)定義特定的數(shù)據(jù)過(guò)濾模式。
正則表達(dá)式的類型
使用正則表達(dá)式最大的問(wèn)題在于有不止一種類型的正則表達(dá)式。Linux 中的不同應(yīng)用程序可能會(huì)用不同類型的正則表達(dá)式。這其中包括編程語(yǔ)言(Java、perl 和 Python)、Linux 實(shí)用工具(比如 sed 編輯器、gawk 程序和 grep 工具)以及主流應(yīng)用(比如 mysql 和 postgresql 數(shù)據(jù)庫(kù)服務(wù)器)。
正則表達(dá)式是通過(guò)正則表達(dá)式引擎(regular expression engine)實(shí)現(xiàn)的。正則表達(dá)式引擎是一套底層軟件,負(fù)責(zé)解釋正則表達(dá)式模式并使用這些模式進(jìn)行文本匹配。在 Linux 中,有兩種流行的正則表達(dá)式引擎:
- POSIX 基礎(chǔ)正則表達(dá)式(basic regular expression,BRE)引擎
- POSIX 擴(kuò)展正則表達(dá)式(extended regular expression,ERE)引擎
大多數(shù) Linux 工具都至少符合 POSIX BRE 引擎規(guī)范,能夠識(shí)別該規(guī)范定義的所有模式符號(hào)。遺憾的是,有些工具(比如 sed 編輯器)只符合了 BRE 引擎規(guī)范的子集。這是出于速度方面的考慮導(dǎo)致的,因?yàn)?sed 編輯器希望能盡可能快地處理數(shù)據(jù)流中的文本。
POSIX BRE 引擎通常出現(xiàn)在依賴正則表達(dá)式進(jìn)行文本過(guò)濾的編程語(yǔ)言中。它為常見(jiàn)模式提供了高級(jí)模式符號(hào)和特殊符號(hào),比如匹配數(shù)字、單詞以及按字母排序的字符。gawk 程序用 ERE 引擎來(lái)處理它的正則表達(dá)式模式。
由于實(shí)現(xiàn)正則表達(dá)式的方法太多,很難用一個(gè)簡(jiǎn)潔的描述來(lái)涵蓋所有可能的正則表達(dá)式。后續(xù)幾節(jié)將會(huì)討論最常見(jiàn)的正則表達(dá)式,并演示如何在 sed 編輯器和 gawk 程序中使用它們。
定義 BRE 模式
最基本的 BRE 模式是匹配數(shù)據(jù)流中的文本字符。本節(jié)將會(huì)演示如何在正則表達(dá)式中定義文本以及會(huì)得到什么樣的結(jié)果。
純文本
前面演示了如何在 sed 編輯器和 gawk 程序中用標(biāo)準(zhǔn)文本字符串來(lái)過(guò)濾數(shù)據(jù)。通過(guò)下面的例子來(lái)復(fù)習(xí)一下。
复制代码
第一個(gè)模式定義了一個(gè)單詞 test。sed 編輯器和 gawk 程序腳本用它們各自的 print 命令打印出匹配該正則表達(dá)式模式的所有行。由于 echo 語(yǔ)句在文本字符串中包含了單詞 test,數(shù)據(jù)流文本能夠匹配所定義的正則表達(dá)式模式,因此 sed 編輯器顯示了該行。
第二個(gè)模式也定義了一個(gè)單詞,這次是 trial。因?yàn)?echo 語(yǔ)句文本字符串沒(méi)包含該單詞,所以正則表達(dá)式模式?jīng)]有匹配,因此 sed 編輯器和 gawk 程序都沒(méi)打印該行。
你可能注意到了,正則表達(dá)式并不關(guān)心模式在數(shù)據(jù)流中的位置。它也不關(guān)心模式出現(xiàn)了多少次。一旦正則表達(dá)式匹配了文本字符串中任意位置上的模式,它就會(huì)將該字符串傳回 Linux 工具。
關(guān)鍵在于將正則表達(dá)式模式匹配到數(shù)據(jù)流文本上。重要的是記住正則表達(dá)式對(duì)匹配的模式非常挑剔。第一條原則就是:正則表達(dá)式模式都區(qū)分大小寫(xiě)。這意味著它們只會(huì)匹配大小寫(xiě)也相符的模式。
复制代码
- $?echo?"This?is?a?test"?|?sed?-n?'/this/p' $ $?echo?"This?is?a?test"?|?sed?-n?'/This/p' This?is?a?test $
第一次嘗試沒(méi)能匹配成功,因?yàn)?this 在字符串中并不都是小寫(xiě),而第二次嘗試在模式中使用大寫(xiě)字母,所以能正常工作。
在正則表達(dá)式中,你不用寫(xiě)出整個(gè)單詞。只要定義的文本出現(xiàn)在數(shù)據(jù)流中,正則表達(dá)式就能夠匹配。
复制代码
- $?echo?"The?books?are?expensive"?|?sed?-n?'/book/p' The?books?are?expensive $
盡管數(shù)據(jù)流中的文本是 books,但數(shù)據(jù)中含有正則表達(dá)式 book,因此正則表達(dá)式模式跟數(shù)據(jù)匹配。當(dāng)然,反之正則表達(dá)式就不成立了。
复制代码
- $?echo?"The?book?is?expensive"?|?sed?-n?'/books/p' $
完整的正則表達(dá)式文本并未在數(shù)據(jù)流中出現(xiàn),因此匹配失敗,sed 編輯器不會(huì)顯示任何文本。
你也不用局限于在正則表達(dá)式中只用單個(gè)文本單詞,可以在正則表達(dá)式中使用空格和數(shù)字。
复制代码
- $?echo?"This?is?line?number?1"?|?sed?-n?'/ber?1/p' This?is?line?number?1 $
在正則表達(dá)式中,空格和其他的字符并沒(méi)有什么區(qū)別。
复制代码
- $?echo?"This?is?line?number1"?|?sed?-n?'/ber?1/p' $
如果你在正則表達(dá)式中定義了空格,那么它必須出現(xiàn)在數(shù)據(jù)流中。甚至可以創(chuàng)建匹配多個(gè)連續(xù)空格的正則表達(dá)式模式。
复制代码
- $?cat?data1 This?is?a?normal?line?of?text. This?is??a?line?with?too?many?spaces. $?sed?-n?'/??/p'?data1 This?is??a?line?with?too?many?spaces. $
單詞間有兩個(gè)空格的行匹配正則表達(dá)式模式。這是用來(lái)查看文本文件中空格問(wèn)題的好辦法。
特殊字符
在正則表達(dá)式模式中使用文本字符時(shí),有些事情值得注意。在正則表達(dá)式中定義文本字符時(shí)有一些特例。有些字符在正則表達(dá)式中有特別的含義。如果要在文本模式中使用這些字符,結(jié)果會(huì)超出你的意料。
正則表達(dá)式識(shí)別的特殊字符包括:
复制代码
- .*[]^${}+?|()
隨著本章內(nèi)容的繼續(xù),你會(huì)了解到這些特殊字符在正則表達(dá)式中有何用處。不過(guò)現(xiàn)在只要記住不能在文本模式中單獨(dú)使用這些字符就行了。果要用某個(gè)特殊字符作為文本字符,就必須轉(zhuǎn)義。在轉(zhuǎn)義特殊字符時(shí),你需要在它前面加一個(gè)特殊字符來(lái)告訴正則表達(dá)式引擎應(yīng)該將接下來(lái)的字符當(dāng)作普通的文本字符。這個(gè)特殊字符就是反斜線()。舉個(gè)例子,如果要查找文本中的美元符,只要在它前面加個(gè)反斜線。
复制代码
- $?cat?data2 The?cost?is?$4.00 $?sed?-n?'/$/p'?data2 The?cost?is?$4.00 $
由于反斜線是特殊字符,如果要在正則表達(dá)式模式中使用它,你必須對(duì)其轉(zhuǎn)義,這樣就產(chǎn)生了兩個(gè)反斜線。
复制代码
- $?echo?"?is?a?special?character"?|?sed?-n?'//p' ?is?a?special?character $
最終,盡管正斜線不是正則表達(dá)式的特殊字符,但如果它出現(xiàn)在 sed 編輯器或 gawk 程序的正則表達(dá)式中,你就會(huì)得到一個(gè)錯(cuò)誤。
复制代码
- $?echo?"3?/?2"?|?sed?-n?'///p' sed:?-e?expression?#1,?char?2:?No?previous?regular?expression $
要使用正斜線,也需要進(jìn)行轉(zhuǎn)義。
复制代码
- $?echo?"3?/?2"?|?sed?-n?'///p' 3?/?2 $
現(xiàn)在 sed 編輯器能正確解釋正則表達(dá)式模式了,一切都很順利。
錨字符
默認(rèn)情況下,當(dāng)指定一個(gè)正則表達(dá)式模式時(shí),只要模式出現(xiàn)在數(shù)據(jù)流中的任何地方,它就能匹配。有兩個(gè)特殊字符可以用來(lái)將模式鎖定在數(shù)據(jù)流中的行首或行尾。
脫字符(^)定義從數(shù)據(jù)流中文本行的行首開(kāi)始的模式。如果模式出現(xiàn)在行首之外的位置,正則表達(dá)式模式則無(wú)法匹配。要用脫字符,就必須將它放在正則表達(dá)式中指定的模式前面。
复制代码
- $?echo?"The?book?store"?|?sed?-n?'/^book/p' $ $?echo?"Books?are?great"?|?sed?-n?'/^Book/p' Books?are?great $
脫字符會(huì)在每個(gè)由換行符決定的新數(shù)據(jù)行的行首檢查模式。
复制代码
- $?cat?data3 This?is?a?test?line. this?is?another?test?line. A?line?that?tests?this?feature. Yet?more?testing?of?this $?sed?-n?'/^this/p'?data3 this?is?another?test?line. $
只要模式出現(xiàn)在新行的行首,脫字符就能夠發(fā)現(xiàn)它。
如果你將脫字符放到模式開(kāi)頭之外的其他位置,那么它就跟普通字符一樣,不再是特殊字符了:
复制代码
- $?echo?"This?^?is?a?test"?|?sed?-n?'/s?^/p' This?^?is?a?test $
由于脫字符出現(xiàn)在正則表達(dá)式模式的尾部,sed 編輯器會(huì)將它當(dāng)作普通字符來(lái)匹配。
?
如果指定正則表達(dá)式模式時(shí)只用了脫字符,就不需要用反斜線來(lái)轉(zhuǎn)義。但如果你在模式中先指定了脫字符,隨后還有其他一些文本,那么你必須在脫字符前用轉(zhuǎn)義字符。
跟在行首查找模式相反的就是在行尾查找。特殊字符美元符($)定義了行尾錨點(diǎn)。將這個(gè)特殊字符放在文本模式之后來(lái)指明數(shù)據(jù)行必須以該文本模式結(jié)尾。
复制代码
- $?echo?"This?is?a?good?book"?|?sed?-n?'/book$/p' This?is?a?good?book $?echo?"This?book?is?good"?|?sed?-n?'/book$/p' $
使用結(jié)尾文本模式的問(wèn)題在于你必須要留意到底要查找什么。
复制代码
- $?echo?"There?are?a?lot?of?good?books"?|?sed?-n?'/book$/p' $
將行尾的單詞 book 改成復(fù)數(shù)形式,就意味著它不再匹配正則表達(dá)式模式了,盡管 book 仍然在數(shù)據(jù)流中。要想匹配,文本模式必須是行的最后一部分。
在一些常見(jiàn)情況下,可以在同一行中將行首錨點(diǎn)和行尾錨點(diǎn)組合在一起使用。在第一種情況中,假定你要查找只含有特定文本模式的數(shù)據(jù)行。
复制代码
- $?cat?data4 this?is?a?test?of?using?both?anchors I?said?this?is?a?test this?is?a?test I'm?sure?this?is?a?test. $?sed?-n?'/^this?is?a?test$/p'?data4 this?is?a?test $
sed 編輯器忽略了那些不單單包含指定的文本的行。
第二種情況乍一看可能有些怪異,但極其有用。將兩個(gè)錨點(diǎn)直接組合在一起,之間不加任何文本,這樣過(guò)濾出數(shù)據(jù)流中的空白行。考慮下面這個(gè)例子。
复制代码
- $?cat?data5 This?is?one?test?line. This?is?another?test?line. $?sed?'/^$/d'?data5 This?is?one?test?line. This?is?another?test?line. $
定義的正則表達(dá)式模式會(huì)查找行首和行尾之間什么都沒(méi)有的那些行。由于空白行在兩個(gè)換行符之間沒(méi)有文本,剛好匹配了正則表達(dá)式模式。sed 編輯器用刪除命令 d 來(lái)刪除匹配該正則表達(dá)式模式的行,因此刪除了文本中的所有空白行。這是從文檔中刪除空白行的有效方法。
點(diǎn)號(hào)字符
特殊字符點(diǎn)號(hào)用來(lái)匹配除換行符之外的任意單個(gè)字符。它必須匹配一個(gè)字符,如果在點(diǎn)號(hào)字符的位置沒(méi)有字符,那么模式就不成立。來(lái)看一些在正則表達(dá)式模式中使用點(diǎn)號(hào)字符的例子。
复制代码
- $?cat?data6 This?is?a?test?of?a?line. The?cat?is?sleeping. That?is?a?very?nice?hat. This?test?is?at?line?four. at?ten?o'clock?we'll?go?home. $?sed?-n?'/.at/p' data6 The?cat?is?sleeping. That?is?a?very?nice?hat. This?test?is?at?line?four. $
你應(yīng)該能夠明白為什么第一行無(wú)法匹配,而第二行和第三行就可以。第四行有點(diǎn)復(fù)雜。注意,我們匹配了 at,但在 at 前面并沒(méi)有任何字符來(lái)匹配點(diǎn)號(hào)字符。其實(shí)是有的!在正則表達(dá)式中,空格也是字符,因此 at 前面的空格剛好匹配了該模式。第五行證明了這點(diǎn),將 at 放在行首就不會(huì)匹配該模式了。
字符組
點(diǎn)號(hào)特殊字符在匹配某個(gè)字符位置上的任意字符時(shí)很有用。但如果你想要限定待匹配的具體字符呢?在正則表達(dá)式中,這稱為字符組(character class)。可以定義用來(lái)匹配文本模式中某個(gè)位置的一組字符。如果字符組中的某個(gè)字符出現(xiàn)在了數(shù)據(jù)流中,那它就匹配了該模式。
使用方括號(hào)來(lái)定義一個(gè)字符組。方括號(hào)中包含所有你希望出現(xiàn)在該字符組中的字符。然后你可以在模式中使用整個(gè)組,就跟使用其他通配符一樣。這需要一點(diǎn)時(shí)間來(lái)適應(yīng),但一旦你適應(yīng)了,效果可是令人驚嘆的。下面是個(gè)創(chuàng)建字符組的例子。
复制代码
- $?sed?-n?'/[ch]at/p'?data6 The?cat?is?sleeping. 這里用到的數(shù)據(jù)文件和點(diǎn)號(hào)特殊字符例子中的一樣,但得到的結(jié)果卻不一樣。這次我們成功濾掉了只包含單詞at的行。匹配這個(gè)模式的單詞只有cat和hat。還要注意以at開(kāi)頭的行也沒(méi)有匹配。字符組中必須有個(gè)字符來(lái)匹配相應(yīng)的位置。 That?is?a?very?nice?hat. $
這里用到的數(shù)據(jù)文件和點(diǎn)號(hào)特殊字符例子中的一樣,但得到的結(jié)果卻不一樣。這次我們成功濾掉了只包含單詞 at 的行。匹配這個(gè)模式的單詞只有 cat 和 hat。還要注意以 at 開(kāi)頭的行也沒(méi)有匹配。字符組中必須有個(gè)字符來(lái)匹配相應(yīng)的位置。
在不太確定某個(gè)字符的大小寫(xiě)時(shí),字符組會(huì)非常有用。
复制代码
- $?echo?"Yes"?|?sed?-n?'/[Yy]es/p' Yes $?echo?"yes"?|?sed?-n?'/[Yy]es/p' yes $
可以在單個(gè)表達(dá)式中用多個(gè)字符組。
复制代码
- $?echo?"Yes"?|?sed?-n?'/[Yy][Ee][Ss]/p' Yes $?echo?"yEs"?|?sed?-n?'/[Yy][Ee][Ss]/p' yEs $?echo?"yeS"?|?sed?-n?'/[Yy][Ee][Ss]/p' yeS $
正則表達(dá)式使用了 3 個(gè)字符組來(lái)涵蓋了 3 個(gè)字符位置含有大小寫(xiě)的情況。
字符組不必只含有字母,也可以在其中使用數(shù)字。
复制代码
- $?cat?data7 This?line?doesn't?contain?a?number. This?line?has?1?number?on?it. This?line?a?number?2?on?it. This?line?has?a?number?4?on?it. $?sed?-n?'/[0123]/p'?data7 This?line?has?1?number?on?it. This?line?a?number?2?on?it. $
這個(gè)正則表達(dá)式模式匹配了任意含有數(shù)字 0、1、2 或 3 的行。含有其他數(shù)字以及不含有數(shù)字的行都會(huì)被忽略掉。
可以將字符組組合在一起,以檢查數(shù)字是否具備正確的格式,比如電話號(hào)碼和郵編。但當(dāng)你嘗試匹配某種特定格式時(shí),必須小心。這里有個(gè)匹配郵編出錯(cuò)的例子。
复制代码
- $?cat?data8 60633 46201 223001 4353 22203 $?sed?-n?' >/[0123456789][0123456789][0123456789][0123456789][0123456789]/p >'?data8 60633 46201 223001 22203 $
這個(gè)結(jié)果出乎意料。它成功過(guò)濾掉了不可能是郵編的那些過(guò)短的數(shù)字,因?yàn)樽詈笠粋€(gè)字符組沒(méi)有字符可匹配。但它也通過(guò)了那個(gè)六位數(shù),盡管我們只定義了 5 個(gè)字符組。
記住,正則表達(dá)式模式可見(jiàn)于數(shù)據(jù)流中文本的任何位置。經(jīng)常有匹配模式的字符之外的其他字符。如果要確保只匹配五位數(shù),就必須將匹配的字符和其他字符分開(kāi),要么用空格,要么像這個(gè)例子中這樣,指明它們就在行首和行尾。
复制代码
- $?sed?-n?' >?/^[0123456789][0123456789][0123456789][0123456789][0123456789]$/p >?'?data8 60633 46201 22203 $
現(xiàn)在好多了!本章隨后會(huì)看到如何進(jìn)一步進(jìn)行簡(jiǎn)化。
字符組的一個(gè)極其常見(jiàn)的用法是解析拼錯(cuò)的單詞,比如用戶表單輸入的數(shù)據(jù)。你可以創(chuàng)建正則表達(dá)式來(lái)接受數(shù)據(jù)中常見(jiàn)的拼寫(xiě)錯(cuò)誤。
复制代码
- $?cat?data9 I?need?to?have?some?maintenence?done?on?my?car. I'll?pay?that?in?a?seperate?invoice. After?I?pay?for?the?maintenance?my?car?will?be?as?good?as?new. $?sed?-n?' /maint[ea]n[ae]nce/p /sep[ea]r[ea]te/p '?data9 I?need?to?have?some?maintenence?done?on?my?car. I'll?pay?that?in?a?seperate?invoice. After?I?pay?for?the?maintenance?my?car?will?be?as?good?as?new. $
本例中的兩個(gè) sed 打印命令利用正則表達(dá)式字符組來(lái)幫助找到文本中拼錯(cuò)的單詞 maintenance 和 separate。同樣的正則表達(dá)式模式也能匹配正確拼寫(xiě)的 maintenance。
排除型字符組
在正則表達(dá)式模式中,也可以反轉(zhuǎn)字符組的作用。可以尋找組中沒(méi)有的字符,而不是去尋找組中含有的字符。要這么做的話,只要在字符組的開(kāi)頭加個(gè)脫字符。
复制代码
- $?sed?-n?'/[^ch]at/p'?data6 This?test?is?at?line?four. $
通過(guò)排除型字符組,正則表達(dá)式模式會(huì)匹配 c 或 h 之外的任何字符以及文本模式。由于空格字符屬于這個(gè)范圍,它通過(guò)了模式匹配。但即使是排除,字符組仍然必須匹配一個(gè)字符,所以以 at 開(kāi)頭的行仍然未能匹配模式。
區(qū)間
你可能注意到了,我之前演示郵編的例子的時(shí)候,必須在每個(gè)字符組中列出所有可能的數(shù)字,這實(shí)在有點(diǎn)麻煩。好在有一種便捷的方法可以讓人免受這番勞苦。可以用單破折線符號(hào)在字符組中表示字符區(qū)間。只需要指定區(qū)間的第一個(gè)字符、單破折線以及區(qū)間的最后一個(gè)字符就行了。根據(jù) Linux 系統(tǒng)采用的字符集,正則表達(dá)式會(huì)包括此區(qū)間內(nèi)的任意字符。現(xiàn)在你可以通過(guò)指定數(shù)字區(qū)間來(lái)簡(jiǎn)化郵編的例子。
复制代码
- $?sed?-n?'/^[0-9][0-9][0-9][0-9][0-9]$/p'?data8 60633 46201 45902 $
這樣可是節(jié)省了不少的鍵盤(pán)輸入!每個(gè)字符組都會(huì)匹配 0~9 的任意數(shù)字。如果字母出現(xiàn)在數(shù)據(jù)中的任何位置,這個(gè)模式都將不成立。
同樣的方法也適用于字母。
复制代码
- $?sed?-n?'/[c-h]at/p'?data6 The?cat?is?sleeping. That?is?a?very?nice?hat. $
新的模式[c-h]at 匹配了首字母在字母 c 和字母 h 之間的單詞。這種情況下,只含有單詞 at 的行將無(wú)法匹配該模式。
還可以在單個(gè)字符組指定多個(gè)不連續(xù)的區(qū)間。
复制代码
- $?sed?-n?'/[a-ch-m]at/p'?data6 The?cat?is?sleeping. That?is?a?very?nice?hat. $
該字符組允許區(qū)間 a~c、h~m 中的字母出現(xiàn)在 at 文本前,但不允許出現(xiàn) d~g 的字母。
复制代码
- $?echo?"I'm?getting?too?fat."?|?sed?-n?'/[a-ch-m]at/p' $
該模式不匹配 fat 文本,因?yàn)樗鼪](méi)在指定的區(qū)間。
特殊的字符組
除了定義自己的字符組外,BRE 還包含了一些特殊的字符組,可用來(lái)匹配特定類型的字符。下面介紹了可用的 BRE 特殊的字符組。
- [[:alpha:]] 匹配任意字母字符,不管是大寫(xiě)還是小寫(xiě)
- [[:alnum:]] 匹配任意字母數(shù)字字符 0~9、A~Z 或 a~z
- [[:blank:]] 匹配空格或制表符
- [[:digit:]] 匹配 0~9 之間的數(shù)字
- [[:lower:]] 匹配小寫(xiě)字母字符 a~z
- [[:upper:]] 匹配任意大寫(xiě)字母字符 A~Z
- [[:print:]] 匹配任意可打印字符
- [[:punct:]] 匹配標(biāo)點(diǎn)符號(hào)
- [[:space:]] 匹配任意空白字符:空格、制表符、NL、FF、VT 和 CR
可以在正則表達(dá)式模式中將特殊字符組像普通字符組一樣使用。
复制代码
- $?echo?"abc"?|?sed?-n?'/[[:digit:]]/p' $ $?echo?"abc"?|?sed?-n?'/[[:alpha:]]/p' abc $?echo?"abc123"?|?sed?-n?'/[[:digit:]]/p' abc123 $?echo?"This?is,?a?test"?|?sed?-n?'/[[:punct:]]/p' This?is,?a?test $?echo?"This?is?a?test"?|?sed?-n?'/[[:punct:]]/p' $
使用特殊字符組可以很方便地定義區(qū)間。如可以用[[:digit:]]來(lái)代替區(qū)間[0-9]。
星號(hào)
在字符后面放置星號(hào)表明該字符必須在匹配模式的文本中出現(xiàn) 0 次或多次。
复制代码
- $?echo?"ik"?|?sed?-n?'/ie*k/p' ik $?echo?"iek"?|?sed?-n?'/ie*k/p' iek $?echo?"ieek"?|?sed?-n?'/ie*k/p' ieek $?echo?"ieeek"?|?sed?-n?'/ie*k/p' ieeek $?echo?"ieeeek"?|?sed?-n?'/ie*k/p' ieeeek $
這個(gè)模式符號(hào)廣泛用于處理有常見(jiàn)拼寫(xiě)錯(cuò)誤或在不同語(yǔ)言中有拼寫(xiě)變化的單詞。舉個(gè)例子,如果需要寫(xiě)個(gè)可能用在美式或英式英語(yǔ)中的腳本,可以這么寫(xiě):
复制代码
- $?echo?"I'm?getting?a?color?TV"?|?sed?-n?'/colou*r/p' I'm?getting?a?color?TV $?echo?"I'm?getting?a?colour?TV"?|?sed?-n?'/colou*r/p' I'm?getting?a?colour?TV $
模式中的 u*表明字母 u 可能出現(xiàn)或不出現(xiàn)在匹配模式的文本中。類似地,如果你知道一個(gè)單詞經(jīng)常被拼錯(cuò),你可以用星號(hào)來(lái)允許這種錯(cuò)誤。
复制代码
- $?echo?"I?ate?a?potatoe?with?my?lunch."?|?sed?-n?'/potatoe*/p' I?ate?a?potatoe?with?my?lunch. $?echo?"I?ate?a?potato?with?my?lunch."?|?sed?-n?'/potatoe*/p' I?ate?a?potato?with?my?lunch. $
在可能出現(xiàn)的額外字母后面放個(gè)星號(hào)將允許接受拼錯(cuò)的單詞。
另一個(gè)方便的特性是將點(diǎn)號(hào)特殊字符和星號(hào)特殊字符組合起來(lái)。這個(gè)組合能夠匹配任意數(shù)量的任意字符。它通常用在數(shù)據(jù)流中兩個(gè)可能相鄰或不相鄰的文本字符串之間。
复制代码
- $?echo?"this?is?a?regular?pattern?expression"?|?sed?-n?' >?/regular.*expression/p' this?is?a?regular?pattern?expression $
可以使用這個(gè)模式輕松查找可能出現(xiàn)在數(shù)據(jù)流中文本行內(nèi)任意位置的多個(gè)單詞。
星號(hào)還能用在字符組上。它允許指定可能在文本中出現(xiàn)多次的字符組或字符區(qū)間。
复制代码
- $?echo?"bt"?|?sed?-n?'/b[ae]*t/p' bt $?echo?"bat"?|?sed?-n?'/b[ae]*t/p' bat $?echo?"bet"?|?sed?-n?'/b[ae]*t/p' bet $?echo?"btt"?|?sed?-n?'/b[ae]*t/p' btt $ $?echo?"baat"?|?sed?-n?'/b[ae]*t/p' baat $?echo?"baaeeet"?|?sed?-n?'/b[ae]*t/p' baaeeet $?echo?"baeeaeeat"?|?sed?-n?'/b[ae]*t/p' baeeaeeat $?echo?"baakeeet"?|?sed?-n?'/b[ae]*t/p' $
只要 a 和 e 字符以任何組合形式出現(xiàn)在 b 和 t 字符之間(就算完全不出現(xiàn)也行),模式就能夠匹配。如果出現(xiàn)了字符組之外的字符,該模式匹配就會(huì)不成立。
擴(kuò)展正則表達(dá)式
POSIX ERE 模式包括了一些可供 Linux 應(yīng)用和工具使用的額外符號(hào)。gawk 程序能夠識(shí)別 ERE 模式,但 sed 編輯器不能。
?
記住,sed 編輯器和 gawk 程序的正則表達(dá)式引擎之間是有區(qū)別的。gawk 程序可以使用大多數(shù)擴(kuò)展正則表達(dá)式模式符號(hào),并且能提供一些額外過(guò)濾功能,而這些功能都是 sed 編輯器所不具備的。但正因?yàn)槿绱耍琯awk 程序在處理數(shù)據(jù)流時(shí)通常才比較慢。
本節(jié)將介紹可用在 gawk 程序腳本中的較常見(jiàn)的 ERE 模式符號(hào)。
問(wèn)號(hào)
問(wèn)號(hào)類似于星號(hào),不過(guò)有點(diǎn)細(xì)微的不同。問(wèn)號(hào)表明前面的字符可以出現(xiàn) 0 次或 1 次,但只限于此。它不會(huì)匹配多次出現(xiàn)的字符。
复制代码
- $?echo?"bt"?|?gawk?'/be?t/{print?$0}' bt $?echo?"bet"?|?gawk?'/be?t/{print?$0}' bet $?echo?"beet"?|?gawk?'/be?t/{print?$0}' $ $?echo?"beeet"?|?gawk?'/be?t/{print?$0}' $
如果字符 e 并未在文本中出現(xiàn),或者它只在文本中出現(xiàn)了 1 次,那么模式會(huì)匹配。
與星號(hào)一樣,你可以將問(wèn)號(hào)和字符組一起使用。
复制代码
- $?echo?"bt"?|?gawk?'/b[ae]?t/{print?$0}' bt $?echo?"bat"?|?gawk?'/b[ae]?t/{print?$0}' bat $?echo?"bot"?|?gawk?'/b[ae]?t/{print?$0}' $ $?echo?"bet"?|?gawk?'/b[ae]?t/{print?$0}' bet $?echo?"baet"?|?gawk?'/b[ae]?t/{print?$0}' $ $?echo?"beat"?|?gawk?'/b[ae]?t/{print?$0}' $ $?echo?"beet"?|?gawk?'/b[ae]?t/{print?$0}' $
如果字符組中的字符出現(xiàn)了 0 次或 1 次,模式匹配就成立。但如果兩個(gè)字符都出現(xiàn)了,或者其中一個(gè)字符出現(xiàn)了 2 次,模式匹配就不成立。
加號(hào)
加號(hào)是類似于星號(hào)的另一個(gè)模式符號(hào),但跟問(wèn)號(hào)也有不同。加號(hào)表明前面的字符可以出現(xiàn) 1 次或多次,但必須至少出現(xiàn) 1 次。如果該字符沒(méi)有出現(xiàn),那么模式就不會(huì)匹配。
复制代码
- $?echo?"beeet"?|?gawk?'/be+t/{print?$0}' beeet $?echo?"beet"?|?gawk?'/be+t/{print?$0}' beet $?echo?"bet"?|?gawk?'/be+t/{print?$0}' bet $?echo?"bt"?|?gawk?'/be+t/{print?$0}' $
如果字符 e 沒(méi)有出現(xiàn),模式匹配就不成立。加號(hào)同樣適用于字符組,與星號(hào)和問(wèn)號(hào)的使用方式相同。
复制代码
- $?echo?"bt"?|?gawk?'/b[ae]+t/{print?$0}' $ $?echo?"bat"?|?gawk?'/b[ae]+t/{print?$0}' bat $?echo?"bet"?|?gawk?'/b[ae]+t/{print?$0}' bet $?echo?"beat"?|?gawk?'/b[ae]+t/{print?$0}' beat $?echo?"beet"?|?gawk?'/b[ae]+t/{print?$0}' beet $?echo?"beeat"?|?gawk?'/b[ae]+t/{print?$0}' beeat $
這次如果字符組中定義的任一字符出現(xiàn)了,文本就會(huì)匹配指定的模式。
使用花括號(hào)
ERE 中的花括號(hào)允許你為可重復(fù)的正則表達(dá)式指定一個(gè)上限。這通常稱為間隔(interval)。可以用兩種格式來(lái)指定區(qū)間。
- 正則表達(dá)式準(zhǔn)確出現(xiàn) m 次。
- m, n:正則表達(dá)式至少出現(xiàn) m 次,至多 n 次。
這個(gè)特性可以精確調(diào)整字符或字符集在模式中具體出現(xiàn)的次數(shù)。
?
如果你的 gawk 版本過(guò)老,gawk 程序不會(huì)識(shí)別正則表達(dá)式間隔。必須額外指定 gawk 程序的–re- interval 命令行選項(xiàng)才能識(shí)別正則表達(dá)式間隔。
這里有個(gè)使用簡(jiǎn)單的單值間隔的例子。
复制代码
- $?echo?"bt"?|?gawk?--re-interval?'/be{1}t/{print?$0}' $ $?echo?"bet"?|?gawk?--re-interval?'/be{1}t/{print?$0}' bet $?echo?"beet"?|?gawk?--re-interval?'/be{1}t/{print?$0}' $
通過(guò)指定間隔為 1,限定了該字符在匹配模式的字符串中出現(xiàn)的次數(shù)。如果該字符出現(xiàn)多次,模式匹配就不成立。
很多時(shí)候,同時(shí)指定下限和上限也很方便。
复制代码
- $?echo?"bt"?|?gawk?--re-interval?'/be{1,2}t/{print?$0}' $ $?echo?"bet"?|?gawk?--re-interval?'/be{1,2}t/{print?$0}' bet $?echo?"beet"?|?gawk?--re-interval?'/be{1,2}t/{print?$0}' beet $?echo?"beeet"?|?gawk?--re-interval?'/be{1,2}t/{print?$0}' $
在這個(gè)例子中,字符 e 可以出現(xiàn) 1 次或 2 次,這樣模式就能匹配;否則,模式無(wú)法匹配
間隔模式匹配同樣適用于字符組。
复制代码
- $?echo?"bt"?|?gawk?--re-interval?'/b[ae]{1,2}t/{print?$0}' $ $?echo?"bat"?|?gawk?--re-interval?'/b[ae]{1,2}t/{print?$0}' bat $?echo?"bet"?|?gawk?--re-interval?'/b[ae]{1,2}t/{print?$0}' bet $?echo?"beat"?|?gawk?--re-interval?'/b[ae]{1,2}t/{print?$0}' beat $?echo?"beet"?|?gawk?--re-interval?'/b[ae]{1,2}t/{print?$0}' beet $?echo?"beeat"?|?gawk?--re-interval?'/b[ae]{1,2}t/{print?$0}' $ $?echo?"baeet"?|?gawk?--re-interval?'/b[ae]{1,2}t/{print?$0}' $ $?echo?"baeaet"?|?gawk?--re-interval?'/b[ae]{1,2}t/{print?$0}' $
如果字母 a 或 e 在文本模式中只出現(xiàn)了 1~2 次,則正則表達(dá)式模式匹配;否則,模式匹配失敗。
管道符號(hào)
管道符號(hào)允許你在檢查數(shù)據(jù)流時(shí),用邏輯 OR 方式指定正則表達(dá)式引擎要用的兩個(gè)或多個(gè)模式。如果任何一個(gè)模式匹配了數(shù)據(jù)流文本,文本就通過(guò)測(cè)試。如果沒(méi)有模式匹配,則數(shù)據(jù)流文本匹配失敗。
使用管道符號(hào)的格式如下:
复制代码
- expr1|expr2|...
這里有個(gè)例子。
复制代码
- $?echo?"The?cat?is?asleep"?|?gawk?'/cat|dog/{print?$0}' The?cat?is?asleep $?echo?"The?dog?is?asleep"?|?gawk?'/cat|dog/{print?$0}' The?dog?is?asleep $?echo?"The?sheep?is?asleep"?|?gawk?'/cat|dog/{print?$0}' $
這個(gè)例子會(huì)在數(shù)據(jù)流中查找正則表達(dá)式 cat 或 dog。正則表達(dá)式和管道符號(hào)之間不能有空格,否則它們也會(huì)被認(rèn)為是正則表達(dá)式模式的一部分。
管道符號(hào)兩側(cè)的正則表達(dá)式可以采用任何正則表達(dá)式模式(包括字符組)來(lái)定義文本。
复制代码
- $?echo?"He?has?a?hat."?|?gawk?'/[ch]at|dog/{print?$0}' He?has?a?hat. $
這個(gè)例子會(huì)匹配數(shù)據(jù)流文本中的 cat、hat 或 dog。
管道符號(hào)
正則表達(dá)式模式也可以用圓括號(hào)進(jìn)行分組。當(dāng)你將正則表達(dá)式模式分組時(shí),該組會(huì)被視為一個(gè)標(biāo)準(zhǔn)字符。可以像對(duì)普通字符一樣給該組使用特殊字符。舉個(gè)例子:
复制代码
- $?echo?"Sat"?|?gawk?'/Sat(urday)?/{print?$0}' Sat $?echo?"Saturday"?|?gawk?'/Sat(urday)?/{print?$0}' Saturday $
結(jié)尾的 urday 分組以及問(wèn)號(hào),使得模式能夠匹配完整的 Saturday 或縮寫(xiě) Sat。
將分組和管道符號(hào)一起使用來(lái)創(chuàng)建可能的模式匹配組是很常見(jiàn)的做法。
复制代码
- $?echo?"cat"?|?gawk?'/(c|b)a(b|t)/{print?$0}' cat $?echo?"cab"?|?gawk?'/(c|b)a(b|t)/{print?$0}' cab $?echo?"bat"?|?gawk?'/(c|b)a(b|t)/{print?$0}' bat $?echo?"bab"?|?gawk?'/(c|b)a(b|t)/{print?$0}' bab $?echo?"tab"?|?gawk?'/(c|b)a(b|t)/{print?$0}' $ $?echo?"tac"?|?gawk?'/(c|b)a(b|t)/{print?$0}' $
模式(c|b)a(b|t)會(huì)匹配第一組中字母的任意組合以及第二組中字母的任意組合
正則表達(dá)式實(shí)戰(zhàn)
現(xiàn)在你已經(jīng)了解了使用正則表達(dá)式模式的規(guī)則和一些簡(jiǎn)單的例子,該把理論用于實(shí)踐了。隨后幾節(jié)將會(huì)演示 shell 腳本中常見(jiàn)的一些正則表達(dá)式例子。
目錄文件計(jì)數(shù)
讓我們先看一個(gè) shell 腳本,它會(huì)對(duì) PATH 環(huán)境變量中定義的目錄里的可執(zhí)行文件進(jìn)行計(jì)數(shù)。要這么做的話,首先你得將 PATH 變量解析成單獨(dú)的目錄名。前面介紹過(guò)如何顯示 PATH 環(huán)境變量。
复制代码
- $?echo?$PATH?/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/?local/games $
根據(jù) Linux 系統(tǒng)上應(yīng)用程序所處的位置,PATH 環(huán)境變量會(huì)有所不同。關(guān)鍵是要意識(shí)到 PATH 中的每個(gè)路徑由冒號(hào)分隔。要獲取可在腳本中使用的目錄列表,就必須用空格來(lái)替換冒號(hào)。現(xiàn)在你會(huì)發(fā)現(xiàn) sed 編輯器用一條簡(jiǎn)單表達(dá)式就能完成替換工作。
复制代码
- $?echo?$PATH?|?sed?'s/:/?/g' /usr/local/sbin?/usr/local/bin?/usr/sbin?/usr/bin?/sbin?/bin?/usr/games?/usr/local/games $
分離出目錄之后,你就可以使用標(biāo)準(zhǔn) for 語(yǔ)句中來(lái)遍歷每個(gè)目錄。
复制代码
- mypath=$(echo?$PATH?|?sed?'s/:/?/g') for?directory?in?$mypath do ????... done
一旦獲得了單個(gè)目錄,就可以用 ls 命令來(lái)列出每個(gè)目錄中的文件,并用另一個(gè) for 語(yǔ)句來(lái)遍歷每個(gè)文件,為文件計(jì)數(shù)器增值。
這個(gè)腳本的最終版本如下。
复制代码
- $?cat?countfiles #!/bin/bash #?count?number?of?files?in?your?PATH mypath=$(echo?$PATH?|?sed?'s/:/?/g') count=0 for?directory?in?$mypath do ????check=$(ls?$directory) ????for?item?in?$check ????????do ????????????count=$[?$count?+?1?] ????????done ????echo?"$directory?-?$count" ????count=0 done $?./countfiles /usr/local/sbin?-?0 /usr/local/bin?-?2 /usr/sbin?-?213 /usr/bin?-?1427 /sbin?-?186 /bin?-?152 /usr/games?-?5 /usr/local/games?–?0 $
現(xiàn)在我們開(kāi)始體會(huì)到正則表達(dá)式背后的強(qiáng)大之處了!
驗(yàn)證電話號(hào)碼
前面的例子演示了在處理數(shù)據(jù)時(shí),如何將簡(jiǎn)單的正則表達(dá)式和 sed 配合使用來(lái)替換數(shù)據(jù)流中的字符。正則表達(dá)式通常用于驗(yàn)證數(shù)據(jù),確保腳本中數(shù)據(jù)格式的正確性。
一個(gè)常見(jiàn)的數(shù)據(jù)驗(yàn)證應(yīng)用就是檢查電話號(hào)碼。數(shù)據(jù)輸入表單通常會(huì)要求填入電話號(hào)碼,而用戶輸入格式錯(cuò)誤的電話號(hào)碼是常有的事。在美國(guó),電話號(hào)碼有幾種常見(jiàn)的形式:
复制代码
- (123)456-7890 (123)?456-7890 123-456-7890 123.456.7890
這樣用戶在表單中輸入的電話號(hào)碼就有 4 種可能。正則表達(dá)式必須足夠強(qiáng)大,才能處理每一種情況。
在構(gòu)建正則表達(dá)式時(shí),最好從左手邊開(kāi)始,然后構(gòu)建用來(lái)匹配可能遇到的字符的模式。在這個(gè)例子中,電話號(hào)碼中可能有也可能沒(méi)有左圓括號(hào)。這可以用如下模式來(lái)匹配:
复制代码
- ^(?
脫字符用來(lái)表明數(shù)據(jù)的開(kāi)始。由于左圓括號(hào)是個(gè)特殊字符,因此必須將它轉(zhuǎn)義成普通字符。問(wèn)號(hào)表明左圓括號(hào)可能出現(xiàn),也可能不出現(xiàn)。
緊接著就是 3 位區(qū)號(hào)。在美國(guó),區(qū)號(hào)以數(shù)字 2 開(kāi)始(沒(méi)有以數(shù)字 0 或 1 開(kāi)始的區(qū)號(hào)),最大可到 9。要匹配區(qū)號(hào),可以用如下模式。
复制代码
- [2-9][0-9]{2}
這要求第一個(gè)字符是 2~9 的數(shù)字,后跟任意兩位數(shù)字。在區(qū)號(hào)后面,收尾的右圓括號(hào)可能存在,也可能不存在。
复制代码
- )?
在區(qū)號(hào)后,存在如下可能:有一個(gè)空格,沒(méi)有空格,有一條單破折線或一個(gè)點(diǎn)。你可以對(duì)它們使用管道符號(hào),并用圓括號(hào)進(jìn)行分組。
复制代码
- (|?|-|.)
第一個(gè)管道符號(hào)緊跟在左圓括號(hào)后,用來(lái)匹配沒(méi)有空格的情形。你必須將點(diǎn)字符轉(zhuǎn)義,否則它會(huì)被解釋成可匹配任意字符。
緊接著是 3 位電話交換機(jī)號(hào)碼。這里沒(méi)什么需要特別注意的。
复制代码
- [0-9]{3}
在電話交換機(jī)號(hào)碼之后,你必須匹配一個(gè)空格、一條單破折線或一個(gè)點(diǎn)。
复制代码
- (?|-|.)
最后,必須在字符串尾部匹配 4 位本地電話分機(jī)號(hào)。
复制代码
- [0-9]{4}$
完整的模式如下。
复制代码
- ^(?[2-9][0-9]{2})?(|?|-|.)[0-9]{3}(?|-|.)[0-9]{4}$
你可以在 gawk 程序中用這個(gè)正則表達(dá)式模式來(lái)過(guò)濾掉不符合格式的電話號(hào)碼。現(xiàn)在你只需要在 gawk 程序中創(chuàng)建一個(gè)使用該正則表達(dá)式的簡(jiǎn)單腳本,然后用這個(gè)腳本來(lái)過(guò)濾你的電話薄。腳本如下,可以將電話號(hào)碼重定向到腳本來(lái)處理。
复制代码
- $?cat?isphone #!/bin/bash #?script?to?filter?out?bad?phone?numbers gawk?--re-interval?'/^(?[2-9][0-9]{2})?(|?|-|.)[0-9]{3}(?|-|.)[0-9]{4}$/{print?$0}' $ $?echo?"317-555-1234"?|?./isphone 317-555-1234 $?echo?"000-555-1234"?|?./isphone $?echo?"312?555-1234"?|?./isphone 312?555-1234 $
或者也可以將含有電話號(hào)碼的整個(gè)文件重定向到腳本來(lái)過(guò)濾掉無(wú)效的號(hào)碼。
复制代码
- $?cat?phonelist 000-000-0000 123-456-7890 212-555-1234 (317)555-1234 (202)?555-9876 33523 1234567890 234.123.4567 $?cat?phonelist?|?./isphone 212-555-1234 (317)555-1234 (202)?555-9876 234.123.4567 $
只有匹配該正則表達(dá)式模式的有效電話號(hào)碼才會(huì)出現(xiàn)。
解析郵件地址
如今這個(gè)時(shí)代,電子郵件地址已經(jīng)成為一種重要的通信方式。驗(yàn)證郵件地址成為腳本程序員的一個(gè)不小的挑戰(zhàn),因?yàn)猷]件地址的形式實(shí)在是千奇百怪。郵件地址的基本格式為:
复制代码
- username@hostname
username 值可用字母數(shù)字字符以及以下特殊字符:
- 點(diǎn)號(hào)
- 單破折線
- 加號(hào)
- 下劃線
在有效的郵件用戶名中,這些字符可能以任意組合形式出現(xiàn)。郵件地址的 hostname 部分由一個(gè)或多個(gè)域名和一個(gè)服務(wù)器名組成。服務(wù)器名和域名也必須遵照嚴(yán)格的命名規(guī)則,只允許字母數(shù)字字符以及以下特殊字符:
- 點(diǎn)號(hào)
- 下劃線
服務(wù)器名和域名都用點(diǎn)分隔,先指定服務(wù)器名,緊接著指定子域名,最后是后面不帶點(diǎn)號(hào)的頂級(jí)域名。
頂級(jí)域名的數(shù)量在過(guò)去十分有限,正則表達(dá)式模式編寫(xiě)者會(huì)嘗試將它們都加到驗(yàn)證模式中。然而遺憾的是,隨著互聯(lián)網(wǎng)的發(fā)展,可用的頂級(jí)域名也增多了。這種方法已經(jīng)不再可行。
從左側(cè)開(kāi)始構(gòu)建這個(gè)正則表達(dá)式模式。我們知道,用戶名中可以有多個(gè)有效字符。這個(gè)相當(dāng)容易。
复制代码
- ^([a-zA-Z0-9_-.+]+)@
這個(gè)分組指定了用戶名中允許的字符,加號(hào)表明必須有至少一個(gè)字符。下一個(gè)字符很明顯是@,沒(méi)什么意外的。
hostname 模式使用同樣的方法來(lái)匹配服務(wù)器名和子域名。
复制代码
- ([a-zA-Z0-9_-.]+)
這個(gè)模式可以匹配文本:
复制代码
- server server.subdomain server.subdomain.subdomain
對(duì)于頂級(jí)域名,有一些特殊的規(guī)則。頂級(jí)域名只能是字母字符,必須不少于二個(gè)字符(國(guó)家或地區(qū)代碼中使用),并且長(zhǎng)度上不得超過(guò)五個(gè)字符。下面就是頂級(jí)域名用的正則表達(dá)式模式。
复制代码
- .([a-zA-Z]{2,5})$
將整個(gè)模式放在一起會(huì)生成如下模式。
复制代码
- ^([a-zA-Z0-9_-.+]+)@([a-zA-Z0-9_-.]+).([a-zA-Z]{2,5})$
這個(gè)模式會(huì)從數(shù)據(jù)列表中過(guò)濾掉那些格式不正確的郵件地址。現(xiàn)在可以創(chuàng)建腳本來(lái)實(shí)現(xiàn)這個(gè)正則表達(dá)式了。
复制代码
- $?echo?"rich@here.now"?|?./isemail rich@here.now $?echo?"rich@here.now."?|?./isemail $ $ echo?"rich@here.n"?|?./isemail $ $?echo?"rich@here-now"?|?./isemail $ $?echo?"rich.blum@here.now"?|?./isemail rich.blum@here.now $?echo?"rich_blum@here.now"?|?./isemail rich_blum@here.now $?echo?"rich/blum@here.now"?|?./isemail $ $?echo?"rich#blum@here.now"?|?./isemail $ $?echo?"rich*blum@here.now"?|?./isemail $