其實Linux系統中處理文本的工具還有很多,功能也遠比我們所能想象到的任何工具都要強大。要想真正掌握它們,就必須要掌握一項基本技能——正則表達式。
正則表達式已經無處不在了,無論是網絡文章,還是很多圖書,亦或是教材也都在講解正則表達式,我們也堅信正在讀本書的你也早已熟稔正則表達式的運用。但是,我們依然要介紹它,只是換了一個角度,以期望初學者能有一個好的開端,行家裏手們能夠加深入的理解它,因爲要熟練使用Linux是離不開正則表達式。
對於初學者來講,一定會追問到底什麼是正則表達式。這是一個很難回答的問題。但是我們可以通過一個應用實例來讓大家明白。一般地,我們在處理字符串的時候後有需要判斷是否存在某個子串的情況,比如要在字符串“prefix=/usr”中來判斷是否擁有“prefix=”這個字串,可以使用逐一比對的方式來完成這個功能。這看起來很好,而且屢試不爽。還能進一步演進算法,實現提取諸如“prefix”的“值”這樣的需求。顯然只需要判斷字符串中有“prefix=”這個子串並確認它的位置就能夠提取出“/usr”這個子串了。看來變量取值算法也是很“簡單”的啊!但是別驕傲,當你遇到“exec-prefix=/libexec prefix=/usr”這樣的字符串時,你怎麼取“prefix”的值呢?你會說,稍微變化一下算法就行了。那遇到的字符串更復雜怎麼辦?對於這類需求,我們可以變換一下思路,即找到“K=V”這樣形式的子串會更好。或許現在你覺得思路又開闊了。但是彆着急,還有更復雜的在等着你。比如字符串“3.1416 * 100 ^ 2”,要提取出所有數字。似乎你又有思路了,無外乎提取連續的0至9,附帶+、-號以及“.”的字串,但是這就能完成任務了?如果再變成“3.1416 * 1.3E2 ^ 2”這樣了呢?反正變化很多。如果你要還是保持前面的思路,我保證你使用幾十萬行代碼的if-else都滿足不了需求。估計你現在一定會想,如果有一門語言只需要編寫少量的代碼就能夠滿足上面的所有需求的話,一定要學會它。這樣你就不怕老闆在這個地方的各種刁難。那我告訴你,還真有一門這樣的語言,它就是——正則表達式。一些簡單的正則表達式,就能夠滿足上面的這些需求,比如:
prefix=
[ \t]*[a-zA-Z]+=[ \t]*[a-zA-Z/]+[ \t]*
[+-]?[0-9]+(\.[0-9]*)?([Ee][+-]?[0-9]+)?
我們說上面的這些方方塊塊、花花草草就是正表達式了,它們具體都是什麼含義呢?
爲了搞清楚這個問題,我們首先要對正則表達式所要處理的文本進行一下精確定義。這個定義是:文本是指字符串的集合,其中的字符來自於一個有限的字符集合。也就是說,文本是由一個有限的字符集構成的,但是文本本身既可以是有窮集合,也可以是無窮集合。就比如屬於文本的源代碼文件,就是滿足某種語言語法的全體字符串的集合,但是不同的源代碼全部算在一起顯然就是一個無窮的集合。當然,也可以有非常簡單的文本,比如只含有一個字母“a”的文本,如果用集合了表示的話就是{a}。按照這個定義,正則表達式就是來描述任意文本的一種特殊表達式,而且擁有兩個基本要素:
l表達式ε表示一個文本,僅包含一個長度爲0的字符串,也可以理解爲{NULL}。通常將NULL記作ε;
l對字符集中任意字符a,表達式a表示僅有一個字符a的文本,即{a}
以及三種基本運算規則:
l兩個正則表達式的並,記作X|Y,表示的文本是正則表達式X所表示的文本與正則表達式Y所表示的文本的並集。比如a|b所得的文本就是{a,b},類似於加法;
l兩個正則表達式的連接,記作XY,表示的文本是將X文本中的每個字符串後面連接上Y文本中的每一個字符串之後,再把所有這種連接的結果組成一種新的文本。比如X=a|b,Y=c|d,那麼XY所表示的文本就是{ac,bc,ad,bd}。因爲X是{a, b},而Y是{c,d},連接運算取X文本的每個字符串接上Y文本的每一個字符串,最後得到了4種連接結果。這類似於乘法;
l一個正則表達式的克林閉包,記作X*,表示分別將0個、1個、2個……n個X與自己連接,然後再把所有這些求並。也就是說X*=ε|X|XX|XXX|……。比如a*這個正則表達式,就表示的是無窮文本{ε,a,aa,aaa,……}。這相當於任意次重複一個語言。
以上三種運算寫在一起時,克林閉包的優先級高於連接運算,而連接運算的優先級高於並運算。這就是正則表達式的全部規則。完全不難理解吧?
在正則表達式的實際使用中,如果只是提供上述三種運算很多時候會使得正則表達式被書寫的十分複雜,爲了簡化正則表達式又引入了一些擴展運算。這些擴展運算都是基於三種基本運算的,它們是:
l[]方括號表示括號內的字符做並運算,同時支持範圍描述符“-”。比如[abcd]就等於a|b|c|d,等價於[a-d]。
l由於方括號中支持範圍描述“-”,如果要使用“-”字符,則需要將它放在方括號的開頭,如[-abc]等於-|a|b|c。
l方括號中以^字符開頭,表示在字符集中排除方括號中的所有字符之後,所剩字符的並運算。比如[^ab]則表示除了ab以外的所有字符求並。
lX?表示X|ε。這就代表X與空字符串之間可選。
lX+表示XX*。這等於限制了X至少要重複1次。
現在我們就可以理解一下我們前面給出的這幾個正則表達式了。
第一個正則表達式是“prefix=”,這就是一個連接運算,由“p”、“r”、“e”、“f”、“i”、“x”、“=”這幾個字符連接而成。
第二個正則表達式則是提取出“K=V”這樣形式的子串,取“=”號右側子串就等同於取值了。而且限定了用空格來區分“K=V”結構的邊界。[ \t]就是描述空格的正則表達式。
第三個正則表達式是提取數字,包含浮點數和科學計數法。比較困惑的是“()”圓括號的出現。這是因爲很多正則表達式工具默認都使用單一字符作爲字串邊界,爲了擴大子串的邊界,可以使用“()”來明確限定,所以也被稱爲子表達式。但是這種子表達式並不屬於標準正則表達式的範疇,所以會遇到不支持的情況。
好了,這部份的內容就算結束了,這是純理論的,希望這些內容能夠幫助到大家。本書的後續內容還會遇到一些有關正則表達式的內容,那些就更加偏重於實踐了。本書之所以要這樣組織,主要是考慮到正則表達式的派系過於繁雜,不同情況下所使用的工具對正則表達式派系的支持可能不太一樣,只有遇到合適的場景才能更好的體會的這些差異來。其實接下來的內容就已經是這樣的了。