##shell十三問之15: [^ ] 跟[! ]差在哪? (RE: Regular Expression)
------------------------------------------------------------
### Part-II Regular Expression (正則表達式)
------------------------------------------
接下來的Regular Expression(RE) 可是個大題目,要講的很多。
我這里當然不可能講得很全。
只希望能帶給大家一個基本的入門概念,就很足夠了...
先來考一下英文好了:What is expression?
簡單來說,就是"表達",也就是人們在溝通的時候所要陳述的內容。
然而,生活中,表達方要清楚的將意思描述清楚,
而讓接收方完整無誤地領會,可不是件容易的事情。
因而才會出現那么多的"誤會", 真可嘆句"表達不易"啊......
同樣的情形也發生在計算機的數據處理過程中,
尤其是當我們在描述一段"文字內容"的時候....
那么,我們不禁要問:
有何方法可以讓大家的誤會降至最低程度,
而讓表達的精確度達到最高程度呢?
答案就是"標準化"了,
也就是我們這里要談的`Regular Expression`啦...^_^
然而,在進入`RE`介紹之前,不妨先讓我們溫習一下shell十三問之第4問,
那就是關于quoting的部分。
**關鍵是要能夠區分 shell command line上的meta與literal的這兩種不同的字符類型**。
然后,我這里也跟你講:
**RE 表達式里字符也分meta與literal這兩種**。
呵,不知親愛的讀者是否被我搞混亂了呢?... ^_^
這也難怪啦,因為這的確是最容易混淆的地方,
剛學`RE`的朋友很多時候,都死在這里!
因此,請特別小心理解哦...
簡單而言,除非你將`RE`寫在特定程序使用的腳本里,
否則,我們的`RE`也是通過 command line輸入的。
然而,
**不少RE所使用的meta字符,跟shell 的meta字符是沖突的**。
比方說,
**`*`這個字符,在RE里是一個modifier(修飾符);而在command line上,確是wildcard(通配符)**。
那么,我們該如何解決這樣的沖突呢?
關鍵就是看你對shell十三問的第4問中所提的quoting是否足夠理解了!
若你明白到
**shell quoting 就是用來在command line上關閉shell meta這一基本原理**,
那你就能很輕松的解決 RE meta與shell meta的沖突問題了:
**用shell quoting 關閉掉shell meta就是了**。
就這么簡單... ^_^
再以剛提到`*`字符為例,
若在command line的path中沒有quoting處理的話,
如abc\* 就會被作為wildcard expression來擴充及重組了。
若將其置于quoting中,即"abc\*",則可以避免wildcard expand的處理。
好了,說了大半天,還沒有進入正式的RE介紹呢....
大家別急,因為我的教學風格就是要先建立基礎,循序漸進的... ^_^
因此, 我這里還要再啰嗦一個觀念,才會到RE的說明啦...(哈...別打我...)
當我們在談到RE時,千萬別跟wildcard搞混在一起!
尤其是
```
在command line的位置里,wildcard只作用于argument的path上;
而RE卻只用于"字符串處理" 的程序中,這與路徑名一點關系也沒有。
```
> **Tips:**
> RE 所處理的字符串,通常是指純文本或通過stdin讀進的內容。
okay,夠了夠了,我已看到一堆人開始出現不耐煩的樣子了... ^_^
現在,就讓我們登堂入室,揭開RE的神秘面紗吧,
這樣可以放過我了吧? 哈哈...
在RE的表達式里,主要分為兩種字符:`literal`與`meta`。
所謂`literal`就是在RE里不具有特殊功能的字符,如abc,123等;
而`meta`,在RE里具有特殊的功能。
要關閉之,需要在`meta`之前使用escape(\)轉義字符。
然而,在介紹`meta`之前,先讓我們來認識一下字符組合(character set)會更好些。
一、所謂的char set就是將多個連續的字符作為一個集合。
例如:
| char set | 意義
|-------------|----------------------------
| abc | 表示abc三個連續的字符,但彼此獨立而非集合。(可簡單視為三個char set)|
| (abc) | 表示abc這三個連續字符的集合。(可簡單視為一個char set)|
| abc\|xyz | 表示abc或xyz這兩個char set之一|
|[abc] | 表示單一字符,可為a或b或c;與wildcard的[abc]原理相同,稱之為字符類。|
|[^abc] |表示單一字符,不為a或b或c即可。(與wildcard [!abc]原理相同)|
| . | 表示任意單個字符,(與wildcard的?原理相同)|
note: abc|xyz 表示abc或xyz這兩個char set之一
在認識了RE的char set這個概念之后,然后,在讓我們多認識幾個RE中常見的meta字符:
二、 錨點(anchor): 用以標識RE在句子中的位置所在。
常見的有:
|錨點 | 說明|
|-------|-------|
| ^ | 表示句首。如,^abc表示以abc開頭的句子。|
| $ | 表示句尾。如,abc$表示以abc結尾的句子。|
| \\< | 表示詞首。如,\\<abc表示以abc開頭的詞。|
| \\> | 表示詞尾。如,abc\\>表示以abc結尾的詞。|
三、 修飾符(modifier):獨立表示時本身不具意義,專門用以修飾前一個char set出現的次數。
常見的有:
| modifier | 說明|
|------------|-----------------------------------
| * | 表示前一個char set出現0次或多次,即任意次。如ab*c表示a與c之間可以有0個或多個b。|
| ? | 表示前一個char set出現0次或1次,即至多出現1次。如ab?c 表示a與c之間可以有0個或1個b。|
| + | 表示前一個char set出現1次或多次,即至少出現1次。如ab+c 表示a與c之間可以有1個或多個b。|
| {n} | 表示前一個char set出現n次。如ab{n}c 表示a與c之間可以有n個b。|
| {n, } | 表示前一個char set至少出現n次。如ab{n}c 表示a與c之間至少有n個b。|
| {n, m} | 表示前一個char set至少出現n次,至多出現m次。如ab{n,m}c 表示a與c之間至少有n個b,至多有m個b。|
然而,當我們在識別modifier時,卻很容易忽略"邊界(boundary)字符"的重要性。
以`ab{3,5}c`為例,這里的a與c就是邊界字符了。
若沒有邊界字符的幫忙,我們很容易做出錯誤的解讀。
比方說: 我們用`ab{3,5}`這個RE(少了c這個邊界字符)
可以抓到"abbbbbbbbbb"(a后面有10個b)的字符串嗎?
從剛才的modifier的說明,我們一般認為,我們要的b是3到5個,
若超出了此范圍,就不是我們所要表達的。
因此,我們或許會很輕率地認為這個RE抓不到結果(上述"abbbbbbbbbb"字符串)。
然而,答案卻是可以的!為什么呢?
讓我們重新解讀`ab{3,5}`這個RE看看:
我們要表達的是a后接3到5個b即可,但3到5個b后面,我們卻沒有規定什么,
因此,在RE后面可以是任意的字符串,當然包括b也可以啦!(明白了嗎?)
同樣,我們用`b{3,5}c`也同樣可以抓到"abbbbbbbbbbc"
這樣的字符串。
但當我們用`ab{3,5}c`這樣的RE時,
由于同時有a與c這連個邊界字符,就截然不同了!
有空在思考一下,為何我們用下面這些RE都抓到abc這樣的字符串呢?
```
x*
ax*, abx*, ax*b
abcx*, abx*c, ax*bc
bx*c, bcx*, x*bc
```
但, 若我們在這些RE前后分別加`^`與`$`這樣的anchor,那又如何呢?
剛學RE時,只要能掌握上面這些基本的meta的大概就可以入門了。
一如前述,RE是一種規范化的文字表達式,
主要用于某些文字處理工具之間,如:
grep, perl, vi,awk,sed,等等,
常用于表示一段連續的字符串,查找和替換。
然而每種工具對RE表達式的具體解讀或有一些細微差別,
不過節本原理還是一致的。
只要掌握RE的基本原理,那就一理通百理了,
只是在實踐時,稍加變通即可。
比方以grep來說,
在Linux上,你可以找到grep,egrep,fgrep這些程序,
其差異大致如下:
grep: 傳統的grep程序,在沒有任何選項(options)的情況下,只輸出符合RE字串的句子,
其常見的選項如下:
| 選項 (option)| 用途|
| -----|----------------------------
| -v | 反模式, 只輸出“不含”RE的字符串的行。|
| -r | 遞歸模式,可同時處理所有層級的子目錄里的文件|
| -q | 靜默模式,不輸出任何結果(stderr 除外,常用于獲取return value,符合為true,否則,為false.|
| -i | 忽略大小寫|
| -w | 整詞匹配,類似 \<RE\>|
| -n | 同時輸出行號|
| -l | 輸出匹配RE的文件名|
| -o | 只輸出匹配RE的字符串。(gnu新版獨有,不見得所有版本支持)|
| -E | 切換為egrep|
egrep:為grep的擴充版本,改良了許多傳統grep不能或者不便的操作,
- grep下不支持`?`與`+`這兩種meta,但egrep支持;
- grep 不支持`a|b`或(`abc|xyz`)這類“或一”的匹配,但egrep支持;
- grep 在處理`{n,m}`時,需要\\{ 與 \\}處理,但egrep不需。
等諸如此類的。我個人建議能用egrep就不用grep啦...^_^
fgrep: 不作RE處理,表達式僅作一般的字符串處理,所有的meta均市區功能。
好了,關于RE的入門,我們暫時就介紹到這里。
雖然有點亂,且有些觀念也不恨精確,
不過,姑且算是對大家的一個交差吧...^_^
若這兩天有時間的話,我在舉些范例來分析一下,以幫助大家更好的理解。
假如更有可能的話,也順道為大家介紹一下sed這個工具。
---------------------------------------
### Part-III eval
---------------------------------------
講到command line的重組特性,
真的需要我們好好的加以解釋的。
如此便能抽絲剝繭的一層層的將整個command line分析的
一清二楚,而不至于含糊。
假如這個重組的特性理解了,那我們介紹一個好玩的命令:`eval`.
我們在變量替換的過程中,常會碰到所謂的復式變量的問題:
如:
```shell
a=1
A1=abc
```
我們都知道`echo $A1`就可以得到abc的結果。
然而,我們能否用$A$a來取代$A1,而同一樣替換為abc呢?
這個問題我們可用很輕松的用`eval`來解決:
```shell
eval echo \$A$a
```
說穿了,`eval` 只不過是在命令行完成替換重組后,
在來一次替換重組罷了...
就是這么簡單啦~~~ ^_^