鍍金池/ 教程/ Linux/ [^ ] 跟[! ]差在哪? (RE: Regular Expression)
別人echo、你也echo,是問echo知多少?
何為 shell?
你要 if 還是 case 呢?
""(雙引號) 與''(單引號) 差在哪?
exec 跟 source 差在哪?
$(())與$()還有${}差在哪?
() 與 {} 差在哪?
shell and Carriage 關(guān)系
for what? while 與 until 差在哪?
大于小于號差別?
[^ ] 跟[! ]差在哪? (RE: Regular Expression)
[^ ] 跟[! ]差在哪? (wildcard)
特殊符號差異
&& 與 || 差在哪?
var=value 在 export 前后的差在哪?

[^ ] 跟[! ]差在哪? (RE: Regular Expression)

Part-II Regular Expression (正則表達(dá)式)

接下來的Regular Expression(RE) 可是個(gè)大題目,要講的很多。 我這里當(dāng)然不可能講得很全。 只希望能帶給大家一個(gè)基本的入門概念,就很足夠了...

先來考一下英文好了:What is expression? 簡單來說,就是"表達(dá)",也就是人們在溝通的時(shí)候所要陳述的內(nèi)容。

然而,生活中,表達(dá)方要清楚的將意思描述清楚, 而讓接收方完整無誤地領(lǐng)會,可不是件容易的事情。

因而才會出現(xiàn)那么多的"誤會", 真可嘆句"表達(dá)不易"啊......

同樣的情形也發(fā)生在計(jì)算機(jī)的數(shù)據(jù)處理過程中, 尤其是當(dāng)我們在描述一段"文字內(nèi)容"的時(shí)候.... 那么,我們不禁要問: 有何方法可以讓大家的誤會降至最低程度, 而讓表達(dá)的精確度達(dá)到最高程度呢? 答案就是"標(biāo)準(zhǔn)化"了, 也就是我們這里要談的Regular Expression啦...^_^

然而,在進(jìn)入RE介紹之前,不妨先讓我們溫習(xí)一下shell十三問之第4問, 那就是關(guān)于quoting的部分。

關(guān)鍵是要能夠區(qū)分 shell command line上的meta與literal的這兩種不同的字符類型。

然后,我這里也跟你講: RE 表達(dá)式里字符也分meta與literal這兩種。

呵,不知親愛的讀者是否被我搞混亂了呢?... ^_^

這也難怪啦,因?yàn)檫@的確是最容易混淆的地方, 剛學(xué)RE的朋友很多時(shí)候,都死在這里! 因此,請?zhí)貏e小心理解哦...

簡單而言,除非你將RE寫在特定程序使用的腳本里, 否則,我們的RE也是通過 command line輸入的。 然而, 不少RE所使用的meta字符,跟shell 的meta字符是沖突的。

比方說, *``這個(gè)字符,在RE里是一個(gè)modifier(修飾符);而在command line上,確是wildcard(通配符)**。

那么,我們該如何解決這樣的沖突呢? 關(guān)鍵就是看你對shell十三問的第4問中所提的quoting是否足夠理解了!

若你明白到 shell quoting 就是用來在command line上關(guān)閉shell meta這一基本原理, 那你就能很輕松的解決 RE meta與shell meta的沖突問題了: 用shell quoting 關(guān)閉掉shell meta就是了。 就這么簡單... ^_^

再以剛提到*字符為例, 若在command line的path中沒有quoting處理的話, 如abc* 就會被作為wildcard expression來擴(kuò)充及重組了。 若將其置于quoting中,即"abc*",則可以避免wildcard expand的處理。

好了,說了大半天,還沒有進(jìn)入正式的RE介紹呢.... 大家別急,因?yàn)槲业慕虒W(xué)風(fēng)格就是要先建立基礎(chǔ),循序漸進(jìn)的... ^_^ 因此, 我這里還要再啰嗦一個(gè)觀念,才會到RE的說明啦...(哈...別打我...)

當(dāng)我們在談到RE時(shí),千萬別跟wildcard搞混在一起! 尤其是

在command line的位置里,wildcard只作用于argument的path上;
而RE卻只用于"字符串處理" 的程序中,這與路徑名一點(diǎn)關(guān)系也沒有。

Tips: RE 所處理的字符串,通常是指純文本或通過stdin讀進(jìn)的內(nèi)容。

okay,夠了夠了,我已看到一堆人開始出現(xiàn)不耐煩的樣子了... ^_^ 現(xiàn)在,就讓我們登堂入室,揭開RE的神秘面紗吧, 這樣可以放過我了吧? 哈哈...

在RE的表達(dá)式里,主要分為兩種字符:literalmeta。 所謂literal就是在RE里不具有特殊功能的字符,如abc,123等; 而meta,在RE里具有特殊的功能。 要關(guān)閉之,需要在meta之前使用escape()轉(zhuǎn)義字符。

然而,在介紹meta之前,先讓我們來認(rèn)識一下字符組合(character set)會更好些。

一、所謂的char set就是將多個(gè)連續(xù)的字符作為一個(gè)集合。 例如:

char set 意義
abc 表示abc三個(gè)連續(xù)的字符,但彼此獨(dú)立而非集合。(可簡單視為三個(gè)char set)
(abc) 表示abc這三個(gè)連續(xù)字符的集合。(可簡單視為一個(gè)char set)
abc|xyz 表示abc或xyz這兩個(gè)char set之一
[abc] 表示單一字符,可為a或b或c;與wildcard的[abc]原理相同,稱之為字符類。
[^abc] 表示單一字符,不為a或b或c即可。(與wildcard [!abc]原理相同)
. 表示任意單個(gè)字符,(與wildcard的?原理相同)

note: abc|xyz 表示abc或xyz這兩個(gè)char set之一

在認(rèn)識了RE的char set這個(gè)概念之后,然后,在讓我們多認(rèn)識幾個(gè)RE中常見的meta字符:

二、 錨點(diǎn)(anchor): 用以標(biāo)識RE在句子中的位置所在。 常見的有:

錨點(diǎn) 說明
^ 表示句首。如,^abc表示以abc開頭的句子。
$ 表示句尾。如,abc$表示以abc結(jié)尾的句子。
\< 表示詞首。如,\<abc表示以abc開頭的詞。
\> 表示詞尾。如,abc\>表示以abc結(jié)尾的詞。

三、 修飾符(modifier):獨(dú)立表示時(shí)本身不具意義,專門用以修飾前一個(gè)char set出現(xiàn)的次數(shù)。 常見的有:

modifier 說明
* 表示前一個(gè)char set出現(xiàn)0次或多次,即任意次。如ab*c表示a與c之間可以有0個(gè)或多個(gè)b。
? 表示前一個(gè)char set出現(xiàn)0次或1次,即至多出現(xiàn)1次。如ab?c 表示a與c之間可以有0個(gè)或1個(gè)b。
+ 表示前一個(gè)char set出現(xiàn)1次或多次,即至少出現(xiàn)1次。如ab+c 表示a與c之間可以有1個(gè)或多個(gè)b。
{n} 表示前一個(gè)char set出現(xiàn)n次。如ab{n}c 表示a與c之間可以有n個(gè)b。
{n, } 表示前一個(gè)char set至少出現(xiàn)n次。如ab{n}c 表示a與c之間至少有n個(gè)b。
{n, m} 表示前一個(gè)char set至少出現(xiàn)n次,至多出現(xiàn)m次。如ab{n,m}c 表示a與c之間至少有n個(gè)b,至多有m個(gè)b。

然而,當(dāng)我們在識別modifier時(shí),卻很容易忽略"邊界(boundary)字符"的重要性。

ab{3,5}c為例,這里的a與c就是邊界字符了。 若沒有邊界字符的幫忙,我們很容易做出錯(cuò)誤的解讀。 比方說: 我們用ab{3,5}這個(gè)RE(少了c這個(gè)邊界字符) 可以抓到"abbbbbbbbbb"(a后面有10個(gè)b)的字符串嗎? 從剛才的modifier的說明,我們一般認(rèn)為,我們要的b是3到5個(gè), 若超出了此范圍,就不是我們所要表達(dá)的。 因此,我們或許會很輕率地認(rèn)為這個(gè)RE抓不到結(jié)果(上述"abbbbbbbbbb"字符串)。

然而,答案卻是可以的!為什么呢? 讓我們重新解讀ab{3,5}這個(gè)RE看看: 我們要表達(dá)的是a后接3到5個(gè)b即可,但3到5個(gè)b后面,我們卻沒有規(guī)定什么, 因此,在RE后面可以是任意的字符串,當(dāng)然包括b也可以啦!(明白了嗎?)

同樣,我們用b{3,5}c也同樣可以抓到"abbbbbbbbbbc" 這樣的字符串。

但當(dāng)我們用ab{3,5}c這樣的RE時(shí), 由于同時(shí)有a與c這連個(gè)邊界字符,就截然不同了!

有空在思考一下,為何我們用下面這些RE都抓到abc這樣的字符串呢?

x*
ax*, abx*, ax*b
abcx*, abx*c, ax*bc
bx*c, bcx*, x*bc

但, 若我們在這些RE前后分別加^$這樣的anchor,那又如何呢?

剛學(xué)RE時(shí),只要能掌握上面這些基本的meta的大概就可以入門了。 一如前述,RE是一種規(guī)范化的文字表達(dá)式, 主要用于某些文字處理工具之間,如: grep, perl, vi,awk,sed,等等, 常用于表示一段連續(xù)的字符串,查找和替換。

然而每種工具對RE表達(dá)式的具體解讀或有一些細(xì)微差別, 不過節(jié)本原理還是一致的。 只要掌握RE的基本原理,那就一理通百理了, 只是在實(shí)踐時(shí),稍加變通即可。

比方以grep來說, 在Linux上,你可以找到grep,egrep,fgrep這些程序, 其差異大致如下:

grep: 傳統(tǒng)的grep程序,在沒有任何選項(xiàng)(options)的情況下,只輸出符合RE字串的句子, 其常見的選項(xiàng)如下:

選項(xiàng) (option) 用途
-v 反模式, 只輸出“不含”RE的字符串的行。
-r  遞歸模式,可同時(shí)處理所有層級的子目錄里的文件
-q 靜默模式,不輸出任何結(jié)果(stderr 除外,常用于獲取return value,符合為true,否則,為false.
-i 忽略大小寫
-w 整詞匹配,類似 \<RE>
-n 同時(shí)輸出行號
-l 輸出匹配RE的文件名
-o 只輸出匹配RE的字符串。(gnu新版獨(dú)有,不見得所有版本支持)
-E 切換為egrep

egrep:為grep的擴(kuò)充版本,改良了許多傳統(tǒng)grep不能或者不便的操作,

  • grep下不支持?+這兩種meta,但egrep支持;
  • grep 不支持a|b或(abc|xyz)這類“或一”的匹配,但egrep支持;
  • grep 在處理{n,m}時(shí),需要\{ 與 \}處理,但egrep不需。

等諸如此類的。我個(gè)人建議能用egrep就不用grep啦...^_^

fgrep: 不作RE處理,表達(dá)式僅作一般的字符串處理,所有的meta均市區(qū)功能。

好了,關(guān)于RE的入門,我們暫時(shí)就介紹到這里。 雖然有點(diǎn)亂,且有些觀念也不恨精確, 不過,姑且算是對大家的一個(gè)交差吧...^_^ 若這兩天有時(shí)間的話,我在舉些范例來分析一下,以幫助大家更好的理解。 假如更有可能的話,也順道為大家介紹一下sed這個(gè)工具。

Part-III eval

講到command line的重組特性, 真的需要我們好好的加以解釋的。

如此便能抽絲剝繭的一層層的將整個(gè)command line分析的 一清二楚,而不至于含糊。

假如這個(gè)重組的特性理解了,那我們介紹一個(gè)好玩的命令:eval.

我們在變量替換的過程中,常會碰到所謂的復(fù)式變量的問題: 如:

a=1
A1=abc

我們都知道echo $A1就可以得到abc的結(jié)果。 然而,我們能否用$A$a來取代$A1,而同一樣替換為abc呢?

這個(gè)問題我們可用很輕松的用eval來解決:

eval echo \$A$a

說穿了,eval 只不過是在命令行完成替換重組后, 在來一次替換重組罷了... 就是這么簡單啦~~~ ^_^