|
最近更新日期:2006/10/05
本文已不再維護,更新文章請參考此處
正規表示法(或稱為常規表示法)是透過一些特殊字元的排列,用以 搜尋/取代/刪除 一列或多列文字字串,
簡單的說,正規表示法就是用在字串的處理上面的一項『表示式』。正規表示法並不是一個工具程式,
而是一個字串處理的標準依據,如果您想要以正規表示法的方式處理字串,就得要使用支援正規表示法的工具程式才行,
這類的工具程式很多,例如 vi, sed, awk 等等。
正規表示法對於系統管理員來說,實在是很重要。因為系統會產生很多的訊息,這些訊息有的重要,有的僅是告知,
此時,管理員可以透過正規表示法的功能來將重要訊息擷取出來,並產生便於查閱的報表,簡化管理流程。此外,
很多的套裝軟體也都支援正規表示法的分析,例如郵件伺服器的過濾機制(過濾垃圾信件)就是很重要的一個例子。
所以,您最好要瞭解正規表示法的相關技能,在未來管理主機時,才能夠更精簡處理您的日常事務!
註:本章節使用者需要多加練習,因為目前很多的套件都是使用正規表示法來達成其『過濾、分析』的目的,
為了未來主機管理的便利性,使用者至少要能看的懂正規表示法的意義!
|
前言
約略瞭解了 Linux 的基本指令 ( Shell ) 並且熟悉了
vi 之後,相信您對於敲擊鍵盤與指令比較不陌生了吧??
接下來,底下要開始介紹一個很重要的觀念,那就是所謂的『 正規表示法』囉!
什麼是正規表示法
任何一個有經驗的系統管理員,都會告訴您:『正規表示法真是挺重要的!』
為什麼很重要呢?因為日常生活就使用的到啊!舉個例子來說,
在您日常處理文書作業時,應該會常常使用到『搜尋/取代』等等的功能吧?
這些舉動要作的漂亮,就是正規表示法的工作了!
簡單的說,正規表示法就是處理字串的方法,他是以行為單位,
來進行字串的處理行為,他透過一些特殊符號的輔助,可以讓使用者輕易的達到 搜尋/取代
某特定字串的處理程序!
舉例來說,我要找到 VBird 或 Vbird 這個字樣,但是不要其他的字串,該如何辦理?
如果在沒有正規表示法的環境中(例如 MS word),您或許就得要使用忽略大小寫的辦法,
或者是分別以 VBird 及 Vbird 搜尋兩遍。但是,忽略大小寫可能會搜尋到 VBIRD/vbird/VbIrD
等等的不需要的字串,而造成使用者的困擾。
再舉個系統常見的例子好了,假設妳發現系統在開機的時候,老是會出現一個關於 mail 程式的錯誤,
而開機過程的相關程序都是在 /etc/rc.d/ 底下,也就是說,在該目錄底下的某個檔案內具有 mail
這個關鍵字,好了,此時,您怎麼找出來含有這個關鍵字的檔案??您當然可以一個檔案一個檔案的開啟,
然後去搜尋 mail 這個關鍵字,只是.....該目錄底下的檔案可能不止 100 個說∼
如果瞭解正規表示法的相關技巧,那麼只要一行指令就找出來啦! 『grep 'mail' /etc/rc.d/*』
那個 grep 就是支援正規表示法的工具程式之一!如何∼很簡單吧! ^_^y
談到這裡就得要進一步說明了,正規表示法基本上是一種『表示法』,
只要工具程式支援這種表示法,那麼該工具程式就可以用來作為正規表示法的字串處理之用。
也就是說,例如 vi, grep, awk ,sed 等等工具,因為她們有支援正規表示法,
所以,這些工具就可以使用正規表示法的特殊字元來進行字串的處理。
正規表示法對於系統管理員的用途
那麼為何我需要學習正規表示法呢?對於一般使用者來說,由於使用到正規表示法的機會可能不怎麼多,
因此感受不到他的魅力,不過,對於身為系統管理員的您來說,
正規表示法則是一個『不可不學的好東西!』
怎麼說呢?由於系統如果在繁忙的情況之下,每天產生的訊息資訊會多到你無法想像的地步,
而我們也都知道,系統的『 錯誤訊息登錄檔案』
的內容(這部份我們在第五篇會詳談)記載了系統產生的所有訊息,當然,
這包含你的系統是否被『入侵』的紀錄資料。
但是系統的資料量太大了,要身為系統管理員的你每天去看這麼多的訊息資料,
從千百行的資料裡面找出一行有問題的訊息,呵呵∼光是用肉眼去看,想不瘋掉都很難!
這個時候,我們就可以透過『正規表示法』的功能,將這些登錄的資訊進行處理,
僅取出『有問題』的資訊來進行分析,哈哈!如此一來,你的系統管理工作將會
『快樂得不得了』啊!當然,正規表示法的優點還不止於此,等您有一定程度的瞭解之後,您會愛上他喔!
正規表示法的廣泛用途
正規表示法除了可以讓系統管理員管理主機更為便利之外,事實上,
由於正規表示法強大的字串處理能力,目前一堆軟體都支援正規表示法呢!
最常見的就是『郵件伺服器』啦!
如果您留意網際網路上的消息,那麼應該不能發現,目前造成網路大塞車的主因之一就是『垃圾/廣告信件』了,
而如果我們可以在主機端,就將這些問題郵件剔除的話,用戶端就會減少很多不必要的頻寬耗損了。
那麼如何剔除廣告信件呢?由於廣告信件幾乎都有一定的標題或者是內容,因此,
只要每次有來信時,都先將來信的標題與內容進行特殊字串的比對,發現有不良信件就予以剔除!
嘿!這個工作怎麼達到啊?就使用正規表示法啊!目前兩大郵件伺服器軟體 sendmail 與 postfix
以及支援郵件伺服器的相關分析套件,都支援正規表示法的比對功能!
當然還不止於此啦,很多的伺服器軟體、以及套件都支援正規表示法呢!當然,
雖然各家軟體都支援他,不過,這些『字串』的比對還是需要系統管理員來加入比對規則的,
所以啦!身為系統管理員的你,為了自身的工作以及用戶端的需求,
正規表示法實在是很需要也很值得學習的一項工具呢!
正規表示法與 Shell 在 Linux 當中的角色定位
說實在的,我們在學數學的時候,一個很重要、但是粉難的東西是一定要『背』的,
那就是九九乘法表,背成功了之後,未來在數學應用的路途上,真是一帆風順啊!
這個九九乘法表我們在小學的時候幾乎背了一整年才背下來,並不是這麼好背的呢!
但他卻是基礎當中的基礎!您現在一定受惠相當的多呢 ^_^!
而我們談到的這個正規表示法,與前一章的 BASH shell
就有點像是數學的九九乘法表一樣,是 Linux 基礎當中的基礎,雖然也是最難的部分,
不過,如果學成了之後,一定是『大大的有幫助』的!這就好像是金庸小說裡面的學武難關,
任督二脈,打通任督二脈之後,武功立刻成倍成長!所以啦,
不論是對於系統的認識與系統的管理部分,他都有很棒的輔助啊!請好好的學習這個基礎吧! ^_^
延伸的正規表示法
正規表示法除了簡單的一組字串處理之外,還可以作群組的字串處理,
例如進行搜尋 VBird 或 netman 或 lman 的搜尋,注意,是『或(or)』而不是『和(and)』的處理,
此時就需要延伸正規表示法的幫助啦!藉由特殊的 ( 與 | 等字元的協助,
就能夠達到這樣的目的!好啦!清清腦門,咱們用功去囉!
Tips: 有一點要向大家報告的,那就是:『正規表示法與萬用字元是不一樣的東西!』
這很重要喔!因為萬用字元 (wildcard) 所代表的意義與正規表示法並不相同∼
要分的很清楚才行喔!所以,學習本章,請將前一章 bash 的萬用字元意義先忘掉吧!
| |
基礎正規表示法
既然正規表示法是處理字串的一個標準表示方式,他需要支援的工具程式來輔助,
所以,我們這裡就先介紹一個最簡單的字串擷取功能的工具程式,那就是 grep 囉!
在介紹完 grep 的基本功能之後,就進入正規表示法的特殊字符的處理能力了。
以 grep 擷取字串
既然要使用 grep 當然就得要先瞭解一下 grep 的語法囉∼
[root@test root]# grep [-acinv] '搜尋字串' filename
參數說明:
-a :將 binary 檔案以 text 檔案的方式搜尋資料
-c :計算找到 '搜尋字串' 的次數
-i :忽略大小寫的不同,所以大小寫視為相同
-n :順便輸出行號
-v :反向選擇,亦即顯示出沒有 '搜尋字串' 內容的那一行!
--color=auto 可將正確的那個擷取資料列出顏色
範例:
[root@test root]# grep 'root' /var/log/secure
將 /var/log/secure 這個檔案中有 root 的那一行秀出來
[root@test root]# grep -v 'root' /var/log/secure
若該行沒有 root 才將資料秀出來到螢幕上!
[root@test root]# last | grep root
若該行有 root 才將資料秀出來到螢幕上!
|
grep 是一個很常見也很常用的指令,他最重要的功能就是進行字串資料的比對,
然後將符合使用者需求的字串列印出來。
需要說明的是『 grep 在資料中查尋一個字串時,是以 "整行"
為單位來進行資料的擷取的!』也就是說,假如一個檔案內有 10
行,其中有兩行具有你所搜尋的字串,則將那兩行顯示在螢幕上,其他的就丟棄了!
而 grep 除了可以進行檔案的資料搜尋之外,也常常被應用在 input/output
的資料處理當中,例如常見的 管線命令 ( pipe ) 就可以常常見到他的蹤影!
以上面表格中的例子來看,我們可以發現前兩個例子是查尋檔案的內容,有沒有加上 -v
所顯示出來的結果是『相反的!』,而第三個例子則是以 pipe 的功能進行資料的處理的喔!
好了,我們就開始以 grep 來進行正規表示法的簡易說明吧!我們先以底下這個檔案來作為範例:
[root@test root]# vi regular_express.txt
"Open Source" is a good mechanism to develop programs.
apple is my favorite food.
Football game is not use feet only.
this dress doesn't fit me.
However, this dress is about $ 3183 dollars.
GNU is free air not free beer.
Her hair is very beauty.
I can’t finish the test.
Oh! The soup taste good.
motorcycle is cheap than car.
This window is clear.
the symbol '*' is represented as start.
Oh! My god!
The gd software is a library for drafting programs.
You are the best is mean you are the no. 1.
The world is the same with "glad".
I like dog.
google is the best tools for search keyword.
goooooogle yes!
go! go! Let's go.
# I am VBird
|
需要特別注意的是,上面這個檔案鳥哥是在 Windows 的環境下編輯的,
並且經過特殊處理過,因此,他雖然是純文字檔,但是內含一些 Windows
環境下的軟體常常自行加入的一些特殊字元,例如斷行字元(^M)就是一例!
所以,您可以直接將上面的文字以 vi 儲存成 regular_express.txt 這個檔案,
不過,比較建議直接點底下的連結下載:
此外,因為不同的語系編碼是不一樣的,所以,您必須要將語系改成英文語系,
才能夠進行底下的測試,否則,可能會有顯示的內容與底下的輸出不符的狀況喔!
修改語系的方法為:
[root@test root]# LANG=en
[root@test root]# export LANG
|
好了,現在開始我們一個案例一個案例的來介紹吧!
- 例題一、搜尋特定字串:
搜尋特定字串很簡單吧?假設我們要從剛剛的檔案當中取得 the 這個特定字串,
最簡單的方式就是這樣:
[root@test root]# grep -n 'the' regular_express.txt
8:I can't finish the test.
12:the symbol '*' is represented as start.
15:You are the best is mean you are the no. 1.
16:The world is the same with "glad".
18:google is the best tools for search keyword.
|
那如果想要『反向選擇』呢?也就是說,當該行沒有
'the' 這個字串時,才顯示在螢幕上,那就直接使用:
[root@test root]# grep -vn 'the' regular_express.txt
|
您會發現,螢幕上出現的行列為除了 8,12,15,16,18 五行之外的其他行列!
接下來,如果您想要取得不論大小寫的 the 這個字串,則:
[root@test root]# grep -in 'the' regular_express.txt
8:I can't finish the test.
9:Oh! The soup taste good.
12:the symbol '*' is represented as start.
14:The gd software is a library for drafting programs.
15:You are the best is mean you are the no. 1.
16:The world is the same with "glad".
18:google is the best tools for search keyword.
|
- 例題二、利用 [] 來搜尋集合字元
如果我想要搜尋 test 或 taste 這兩個單字時,可以發現到,其實她們有共通的 't?st'
存在∼這個時候,我可以這樣來搜尋:
[root@test root]# grep -n 't[ae]st' regular_express.txt
8:I can't finish the test.
9:Oh! The soup taste good.
|
瞭解了吧?其實 []
裡面不論有幾個字元,他都謹代表某『一個』字元,
所以,上面的例子說明了,我需要的字串是『tast』或『test』兩個字串而已!
而如果想要搜尋到有 oo 的字元時,則使用:
[root@test root]# grep -n 'oo' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
2:apple is my favorite food.
3:Football game is not use feet only.
9:Oh! The soup taste good.
18:google is the best tools for search keyword.
19:goooooogle yes!
|
但是,如果我不想要 oo 前面有 g 的話呢?此時,可以利用在集合字元的反向選擇 [^] 來達成
[root@test root]# grep -n '[^g]oo' regular_express.txt
2:apple is my favorite food.
3:Football game is not use feet only.
18:google is the best tools for search keyword.
19:goooooogle yes!
|
意思就是說,我需要的是 oo ,但是 oo 前面不能是 g 就是了!
仔細比較上面兩個表格,妳會發現,第 1,9 行不見了,因為 oo 前面出現了 g 所致!
第 2,3 行沒有疑問,因為 foo 與 Foo 均可被接受!但是第 18 行明明有 google 的 goo 啊∼
別忘記了,因為該行後面出現了 tool 的 too 啊!所以該行也被列出來∼
也就是說, 18 行裡面雖然出現了我們所不要的項目 (goo) 但是由於有需要的項目 (too) ,
因此,是符合字串搜尋的喔!
至於第 19 行,同樣的,因為 goooooogle 裡面的 oo 前面可能是 o ,例如:
go(ooo)oogle ,所以,這一行也是符合需求的!
再來,假設我 oo 前面不想要有小寫字元,所以,我可以這樣寫 [^abcd....z]oo ,
但是這樣似乎不怎麼方便,由於小寫字元的 ASCII 上編碼的順序是連續的,
因此,我們可以將之簡化為底下這樣:
[root@test root]# grep -n '[^a-z]oo' regular_express.txt
3:Football game is not use feet only.
|
也就是說,當我們在一組集合字元中,如果該字元組是連續的,例如大寫英文/小寫英文/數字等等,
就可以使用[a-z],[A-Z],[0-9]等方式來書寫,那麼如果我們的要求字串是數字與英文呢?
呵呵!就將他全部寫在一起,變成:[a-zA-Z0-9]
例如,我們要取得有數字的那一行,就這樣:
[root@test root]# grep -n '[0-9]' regular_express.txt
5:However, this dress is about $ 3183 dollars.
15:You are the best is mean you are the no. 1.
|
這樣對於 [] 以及 [^] 以及 [] 當中的 - 有瞭解了嗎?! ^_^y
- 例題三、行首與行尾字元 ^ $:
我們在例題一當中,可以查詢到一行字串裡面有 the 的,那如果我想要讓 the 只在行首列出呢?
這個時候就得要使用定位字元了!我們可以這樣做:
[root@test root]# grep -n '^the' regular_express.txt
12:the symbol '*' is represented as start.
|
此時,就只剩下第 12 行,因為只有第 12 行的行首是 the 開頭啊∼此外,
如果我想要開頭是小寫字元的那一行就列出呢?可以這樣:
[root@test root]# grep -n '^[a-z]' regular_express.txt
2:apple is my favorite food.
4:this dress doesn't fit me.
10:motorcycle is cheap than car.
12:the symbol '*' is represented as start.
18:google is the best tools for search keyword.
19:goooooogle yes!
|
如果我不想要開頭是英文字母,則可以是這樣:
[root@test root]# grep -n '^[^a-zA-Z]' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
20:# I am VBird
|
注意到了吧?那個 ^ 符號,在字元集合符號(括號[])之內與之外是不同的!
在 [] 內代表『反向選擇』,在 [] 之外則代表定位在行首的意義!要分清楚喔!
那如果我想要找出來,行尾結束為小數點 (.) 的那一行,該如何處理:
[root@test root]# grep -n '\.$' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
2:apple is my favorite food.
3:Football game is not use feet only.
4:this dress doesn't fit me.
10:motorcycle is cheap than car.
11:This window is clear.
12:the symbol '*' is represented as start.
15:You are the best is mean you are the no. 1.
16:The world is the same with "glad".
17:I like dog.
18:google is the best tools for search keyword.
|
特別注意到,因為小數點具有其他意義(底下會介紹),所以必須要使用跳脫字元(\)來加以解除其特殊意義!
不過,您或許會覺得奇怪,但是第 5~9 行最後面也是 . 啊∼怎麼無法列印出來??
這裡就牽涉到 Windows 平台的軟體對於斷行字元的判斷問題了!我們使用 cat -A 將第五行拿出來看,
您會發現:
[root@test root]# cat -A regular_express.txt
However, this dress is about $ 3183 dollars.^M$
|
注意到了沒?最後面的斷行字元應該是 $ 才對,但是,因為 Windows 的 nodepad 會主動加上 ^M
作為斷行的判斷,因此,那個 . 自然就不是緊接在 $ 之前喔!這樣可以瞭解 ^ 與 $ 的意義嗎?
好了,先不要看底下的解答,自己想一想,那麼如果我想要找出來,哪一行是『空白行』,
也就是說,該行並沒有輸入任何資料,該如何搜尋??
[root@test root]# grep -n '^$' regular_express.txt
21:
|
因為只有行首跟行尾( ^$ ),所以,這樣就可以找出空白行啦!再來,
假設您已經知道在一個批次腳本 (shell script) 或者是設定檔當中,
空白行與開頭為 # 的那一行是註解,因此如果您要將資料列出給別人參考時,
可以將這些資料省略掉,以節省保貴的紙張,那麼,您可以怎麼作呢?
我們以 /etc/syslog.conf 這個檔案來作範例,您可以自行參考一下輸出的結果:
[root@test root]# cat /etc/syslog.conf
[root@test root]# grep -v '^$' /etc/syslog.conf | grep -v '^#'
|
是否節省很多版面啊??
- 例題四、任意一個字元 . 與重複字元 *
在 bash 的章節當中,我們知道萬用字元 * 可以用來代表任意(0或多個)字元,
但是正規表示法並不是萬用字元,兩者之間是不相同的!
至於正規表示法當中的『 . 』則代表『絕對有一個任意字元』的意思!這樣講不好懂,
我們直接做個練習吧!假設我需要找出 g??d 的字串,亦即共有四個字元,
起頭是 g 而結束是 d ,我可以這樣做:
[root@test root]# grep -n 'g..d' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
9:Oh! The soup taste good.
16:The world is the same with "glad".
|
因為強調 g 與 d 之間一定要存在兩個字元,因此,第 13 行的 god 與第 14 行的 gd
就不會被列出來啦!再來,如果我想要列出有 oo, ooo, oooo 等等的資料,
也就是說,至少要有兩個 o 以上,該如何是好??是 o* 還是 oo* 還是 ooo* 呢?
雖然您可以試看看結果, 不過結果太佔版面了 @_@ ,所以,我這裡就直接說明。
因為 * 代表的是『重複 0 個或多個前面的 RE 字符』的意義,
因此,『o*』代表的是:『擁有空字元或一個 o 以上的字元』,
特別注意,因為允許空字元(就是有沒有字元都可以的意思),因此,
grep -n 'o*' regular_express.txt
將會把所有的資料都列印出來螢幕上!
那如果是『oo*』呢?則第一個 o 肯定必須要存在,第二個 o 則是可有可無的多個 o ,
所以,凡是含有 o, oo, ooo, oooo 等等,都可以被列出來∼
同理,當我們需要『至少兩個 o 以上的字串』時,就需要 ooo* ,亦即是:
[root@test root]# grep -n 'ooo*' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
2:apple is my favorite food.
3:Football game is not use feet only.
9:Oh! The soup taste good.
18:google is the best tools for search keyword.
19:goooooogle yes!
|
這樣理解 * 的意義了嗎?!好了,現在出個練習,如果我想要字串開頭與結尾都是 g,
但是兩個 g 之間僅能存在至少一個 o ,亦即是 gog, goog, gooog.... 等等,
那該如何?
[root@test root]# grep -n 'goo*g' regular_express.txt
18:google is the best tools for search keyword.
19:goooooogle yes!
|
如此瞭解了嗎?好,再來一題,如果我想要找出 g 開頭與 g 結尾的字串,
當中的字元可有可無,那該如何是好?是『g*g』嗎?
[root@test root]# grep -n 'g*g' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
3:Football game is not use feet only.
9:Oh! The soup taste good.
13:Oh! My god!
14:The gd software is a library for drafting programs.
16:The world is the same with "glad".
17:I like dog.
18:google is the best tools for search keyword.
19:goooooogle yes!
|
但測試的結果竟然出現這麼多行??太詭異了吧?
其實一點也不詭異,因為 g*g 裡面的 g* 代表『空字元或一個以上的 g』
在加上後面的 g ,因此,整個 RE 的內容就是 g, gg, ggg, gggg ,
因此,只要該行當中擁有一個以上的 g 就符合所需了!
那該如何得到我們的 g....g 的需求呢?呵呵!就利用任意一個字元『.』啊!
亦即是:『g.*g』的作法,因為 * 可以是 0 或多個重複前面的字符,而 . 是任意字元,所以:
『.* 就代表零個或多個任意字元』的意思啦!
[root@test root]# grep -n 'g.*g' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
14:The gd software is a library for drafting programs.
18:google is the best tools for search keyword.
19:goooooogle yes!
|
因為是代表 g 開頭與 g 結尾,中間任意字元均可接受,所以,第 1 與第 14 行是可接受的喔!
這個 .* 的 RE 表示任意字元是很常見的,希望大家能夠理解並且熟悉!
再出一題,如果我想要找出『任意數字』的行列呢?因為僅有數字,所以就成為:
[root@test root]# grep -n '[0-9][0-9]*' regular_express.txt
5:However, this dress is about $ 3183 dollars.
15:You are the best is mean you are the no. 1.
|
雖然使用 grep -n '[0-9]' regular_express.txt 也可以得到相同的結果,
但鳥哥希望大家能夠理解上面指令當中 RE 表示法的意義才好!
- 例題五、限定連續 RE 字符範圍 {}
在上個例題當中,我們可以利用 . 與 RE 字符及 * 來設定 0 個到無限多個重複字元,
那如果我想要限制一個範圍區間內的重複字元數呢?舉例來說,我想要找出兩個到五個 o
的連續字串,該如何作?這時候就得要使用到限定範圍的字符 {} 了。
但因為 { 與 } 的符號在 shell 是有特殊意義的,因此,
我們必須要使用跳脫字符 \ 來讓他失去特殊意義才行。
至於 {} 的語法是這樣的,假設我要找到兩個 o 的字串,可以是:
[root@test root]# grep -n 'o\{2\}' regular_express.txt
1:"Open Source" is a good mechanism to develop programs.
2:apple is my favorite food.
3:Football game is not use feet only.
9:Oh! The soup taste good.
18:google is the best tools for search keyword.
19:goooooogle yes!
|
這樣看似乎與 ooo* 的字符沒有什麼差異啊?因為第 19 行有多個 o 依舊也出現了!
好,那麼換個搜尋的字串,假設我們要找出 g 後面接 2 到 5 個 o ,然後再接一個 g 的字串,
他會是這樣:
[root@test root]# grep -n 'go\{2,5\}g' regular_express.txt
18:google is the best tools for search keyword.
|
嗯!很好!第 19 行終於沒有被取用了(因為 19 行有 6 個 o 啊!)。
那麼,如果我想要的是 2 個 o 以上的 goooo....g 呢?除了可以是 gooo*g ,也可以是:
[root@test root]# grep -n 'go\{2,\}g' regular_express.txt
18:google is the best tools for search keyword.
19:goooooogle yes!
|
呵呵!就可以找出來啦∼
重要特殊字元(characters)
經過了上面的幾個簡單的範例,我們可以將基礎的正規表示法特殊字符彙整如下:
RE 字符 | 意義與範例 |
^word | 待搜尋的字串(word)在行首! |
範例:grep -n '^#' regular_express.txt
搜尋行首為 # 開始的那一行! |
word$ | 待搜尋的字串(word)在行尾! |
範例:grep -n '!$' regular_express.txt
將行尾為 ! 的那一行列印出來! |
. | 代表『任意一個』字符,一定是一個任意字符! |
範例:grep -n 'e.e' regular_express.txt
搜尋的字串可以是 (eve) (eae) (eee) (e e), 但不能僅有 (ee) !亦即 e 與
e 中間『一定』僅有一個字元,而空白字元也是字元! |
\ | 跳脫字符,將特殊符號的特殊意義去除! |
範例:grep -n \' regular_express.txt
搜尋含有單引號 ' 的那一行! |
* | 重複零個或多個的前一個 RE 字符 |
範例:grep -n 'ess*' regular_express.txt
找出含有 (es) (ess) (esss) 等等的字串,注意,因為 * 可以是 0 個,所以 es
也是符合帶搜尋字串。另外,因為 * 為重複『前一個 RE 字符』的符號,
因此,在 * 之前必須要緊接著一個 RE 字符喔!例如任意字元則為 『.*』 ! |
\{n,m\} | 連續 n 到 m 個的『前一個 RE 字符』
若為 \{n\} 則是連續 n 個的前一個 RE 字符,
若是 \{n,\} 則是連續 n 個以上的前一個 RE 字符! |
範例:grep -n 'go\{2,3\}g' regular_express.txt
在 g 與 g 之間有 2 個到 3 個的 o 存在的字串,亦即 (goog)(gooog) |
[] | 字元集合的 RE 特殊字符的符號 |
[list]
範例:grep -n 'g[ld]' regular_express.txt
搜尋含有 (gl) 或 (gd) 的那一行∼
需要特別留意的是,在 [] 當中『謹代表一個待搜尋的字元』,
例如: a[afl]y 代表搜尋的字串可以是 aay, afy, aly
亦即 [afl] 代表 a 或 f 或 l 的意思!
[ch1-ch2]
範例:grep -n '[0-9]' regular_express.txt
搜尋含有任意數字的那一行!需特別留意,在字元集合 [] 中的減號 -
是有特殊意義的,他代表兩個字元之間的所有連續字元!但這個連續與否與 ASCII 編碼有關,
因此,您的編碼需要設定正確(在 bash 當中,需要確定 LANG 與 LANGUAGE 的變數是否正確!)
例如所有大寫字元則為 [A-Z]
[^]
範例:grep -n 'oo[^t]' regular_express.txt
搜尋的字串可以是 (oog) (ood) 但不能是 (oot) ,那個 ^ 在 [] 內時,
代表的意義是『反向選擇』的意思∼例如,我不要大寫字元,則為 [^A-Z] ∼
但是,需要特別注意的是,如果以 grep -n [^A-Z] regular_express.txt 來搜尋,
卻發現該檔案內的所有行都被列出,為什麼?因為這個 [^A-Z] 是『非大寫字元』的意思,
因為每一行均有非大寫字元,例如第一行的 "Open Source" 就有 p,e,n,o.... 等等的小寫字元,
以及雙引號 (") 等字元,所以當然符合 [^A-Z] 的搜尋!
|
請特別留意的是,『 正規表示法的特殊字元』
與一般在指令列輸入指令的『 萬用字元』並不相同,
例如,在萬用字元當中,* 代表的是 0 ~ 無限多個字元的意思,但是在正規表示法當中,
* 則是重複 0 到多個的前一個 RE 字符的意思∼使用的意義並不相同,不要搞混了!
(鳥哥我一開始摸正規表示法時就很容易搞混!因為這裡是新手最容易搞錯的地方,特別小心啊!)
舉例來說,不支援正規表示法的 ls 這個工具中,若我們使用 『ls -l * 』
代表的是任意檔名的檔案,而 『ls -l a* 』代表的是以 a 為開頭的任何檔名的檔案,
但在正規表示法中,我們要找到含有以 a 為開頭的檔案,則必須要這樣:(需搭配支援正規表示法的工具)
另外,例如萬用字元的反向選擇,為 [!range] ,至於正規表示法則是 [^range] 。
這樣是否瞭解正規表示法與萬用字元的差異啦??
延伸正規表示法
事實上,一般讀者只要瞭解基礎型的正規表示法大概就已經相當足夠了,不過,某些時刻,
為了要簡化整個指令操作,瞭解一下使用範圍更廣的延伸型正規表示法的表示式,會更方便呢!
舉個簡單的例子好了,在上節的例題三的最後一個例子中,我們要去除空白行與行首為 # 的行列,
使用的是
grep -v '^$' regular_express.txt | grep -v '^#'
需要使用到管線命令來搜尋兩次!
那麼如果使用延伸型的正規表示法,我們可以簡化為:
egrep -v '^$|^#' regular_express.txt
利用支援延伸型正規表示法的 egrep 與特殊字元 | 來區隔兩組字串,如此一來,是否方便很多呢?
這裡必須要特別強調, grep 支援的是基礎型的正規表示法,而 egrep 支援延伸正規表示法。
事實上, egrep 是 grep -E 的命令別名,為了方便使用,我們還是以 egrep 來跟 grep 區分吧!
熟悉了正規表示法之後,到這個延伸型的正規表示法,您應該也會想到,
不就是多幾個重要的特殊符號嗎? ^_^y 是的∼所以,我們就直接來說明一下,延伸型正規表示法有哪幾個特殊符號?
RE 字符 | 意義與範例 |
+ | 重複『一個或一個以上』的前一個 RE 字符 |
範例:egrep -n 'go+d' regular_express.txt
搜尋 (god) (good) (goood)... 等等的字串。
那個 o+ 代表『一個以上的 o 』所以,上面的執行成果會將第 1, 9, 13 行列出來。 |
? | 『零個或一個』的前一個 RE 字符 |
範例:egrep -n 'go?d' regular_express.txt
搜尋 (gd) (god) 這兩個字串。
那個 o? 代表『空的或 1 個 o 』所以,上面的執行成果會將第 13, 14 行列出來。
有沒有發現到,這兩個案例( 'go+d' 與 'go?d' )的結果集合與 'go*d' 相同?
想想看,這是為什麼喔! ^_^ |
| | 用或( or )的方式找出數個字串 |
範例:egrep -n 'gd|good' regular_express.txt
搜尋 gd 或 good 這兩個字串,注意,是『或』!
所以,第 1,9,14 這三行都可以被列印出來喔!那如果還想要找出 dog 呢?就這樣啊:
egrep -n 'gd|good|dog' regular_express.txt |
( ) | 找出『群組』字串 |
範例:egrep -n 'g(la|oo)d' regular_express.txt
搜尋 (glad) 或 (good) 這兩個字串,因為 g 與 d 是重複的,所以,
我就可以將 la 與 oo 列於 ( ) 當中,並以 | 來分隔開來,就可以啦!
此外,這個功能還可以用來作為『多個重複群組』的判別喔!舉例來說:
echo 'AxyzxyzxyzxyzC' | egrep 'A(xyz)+C'
上面的例子當中,意思是說,我要找開頭是 A 結尾是 C ,中間有一個以上的
"xyz" 字串的意思∼ | |
以上這些就是延伸型的正規表示法的特殊字元。另外,要特別強調的是,那個 ! 在正規表示法當中並不是特殊字元,
所以,如果您想要查出來檔案中含有 ! 與 > 的字行時,可以這樣:
grep -n '[!>]' regular_express.txt
這樣可以瞭解了嗎?!常常看到有陷阱的題目寫:『反向選擇這樣對否? '[!a-z]'?』,
呵呵!是錯的呦∼要 '[^a-z] 才是對的!
格式化列印: printf
在很多時候,我們可能需要將輸出的資料給他格式化輸出的∼
舉例來說,考試卷分數的輸出,姓名與科目及分數之間,總是可以稍微作個比較漂亮的版面配置吧?
例如我想要輸出底下的樣式:
Name Chinese English Math Average
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33
|
分成五個欄位,各個欄位分配到正確的位置去!但是因為每個欄位的原始資料其實並非是如此固定的,
而我就是想要如此表示出這些資料,此時,就得需要列印格式管理員 printf 的幫忙了!
printf 可以幫我們將資料輸出的結果格式化,而且而支援一些特殊的字符∼底下我們就來看看!
[root@linux ~]# printf '列印格式' 實際內容
參數:
關於格式方面的幾個特殊樣式:
\a 警告聲音輸出
\b 倒退鍵(backspace)
\f 清除螢幕 (form feed)
\n 輸出新的一行
\r 亦即 Enter 按鍵
\t 水平的 [tab] 按鍵
\v 垂直的 [tabl] 按鍵
\xNN NN 為兩位數的數字,可以轉換數字成為字元。
關於 C 程式語言內,常見的變數格式
%ns 那個 n 是數字, s 代表 string ,亦即多少個字元;
%ni 那個 n 是數字, i 代表 integer ,亦即多少整數位數;
%N.nf 那個 n 與 N 都是數字, f 代表 floating (浮點),如果有小數位數,
假設我共要十個位數,但小數點有兩位,即為 %10.2f 囉!
範例:
範例一:將剛剛上頭的資料變成檔案,僅列出姓名與成績:(用 [tab] 分隔
[root@linux ~]# printf '%s\t %s\t %s\t %s\t %s\t \n' `cat printf.txt`
Name Chinese English Math Average
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33
# 假設我將上面的檔案存成 printf.txt 檔案檔名,則可利用上面的案例,
# 將每個單字中間以 [tab] 按鍵隔開。由上面的輸出來看,雖然第二行以後是 OK 的,
# 但是第一行則因為某些單字長度較長,所以就無法對齊了!而 %s 表示以字串 (string)
# 的方式來展現該內容。而每個內容則以 \t 即 [tab] 來隔開啊!
範例二:將上述資料關於第二行以後,分別以字串、整數、小數點來顯示:
[root@linux ~]# printf '%10s %5i %5i %5i %8.2f \n' `cat printf.txt |\
> grep -v Name`
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33
# 這個時候的輸出可就有趣了!我將幾個內容分成不同的資料格式來輸出,
# 最有趣的應該是 %8.2f 這個項目了!我可以針對不同的小數位數來進行格式輸出,
# 例如變成底下的樣子時,您自己試看看,會是輸出什麼結果喔!
# printf '%10s %5i %5i %5i %8.1f \n' `cat printf.txt | grep -v Name`
範例三:列出數值 45 代表的字元為何?
[root@linux ~]# printf '\x45\n'
E
# 這東西也很好玩∼他可以將數值轉換成為字元,如果您會寫 script 的話,
# 可以自行測試一下,由 20~80 之間的數值代表的字元是啥喔! ^_^
|
printf 的使用相當的廣泛喔!包括等一下後面會提到的 awk 以及在 C 程式語言當中使用的螢幕輸出,
都是利用 printf 呢!鳥哥這裡也只是列出一些可能會用到的格式而已,
有興趣的話,可以自行多作一些測試與練習喔! ^_^
Tips: 列印格式化這個 printf 指令,乍看之下好像也沒有什麼很重要的∼
不過,如果您需要自行撰寫一些軟體,需要將一些資料在螢幕上頭漂漂亮亮的輸出的話,
那麼 printf 可也是一個很棒的工具喔!
| |
sed 工具簡介
在瞭解了一些正規表示法的基礎應用之後,再來呢?呵呵∼兩個東西可以玩一玩的,那就是 sed 跟 awk 了!
這兩個傢伙可是相當的有用的啊!舉例來說,鳥哥寫的 logfile.sh 分析登錄檔的小程式,
絕大部分分析關鍵字的取用、統計等等,就是用這兩個寶貝蛋來幫我完成的!
那麼你說,要不要玩一玩啊?! ^_^
我們先來談一談 sed 好了,基本上, sed 可以分析 Standard Input (STDIN) 的資料,
然後將資料經過處理後,再將他輸出到 standrad out (STDOUT) 的一個工具。
至於處理呢?可以進行取代、刪除、新增、擷取特定行等等的功能呢!很不錯吧∼
我們先來瞭解一下 sed 的用法,再來聊他的用途好了!
[root@linux ~]# sed [-nefr] [動作]
參數:
-n :使用安靜(silent)模式。在一般 sed 的用法中,所有來自 STDIN
的資料一般都會被列出到螢幕上。但如果加上 -n 參數後,則只有經過
sed 特殊處理的那一行(或者動作)才會被列出來。
-e :直接在指令列模式上進行 sed 的動作編輯;
-f :直接將 sed 的動作寫在一個檔案內, -f filename 則可以執行 filename 內的
sed 動作;
-r :sed 的動作支援的是延伸型正規表示法的語法。(預設是基礎正規表示法語法)
-i :直接修改讀取的檔案內容,而不是由螢幕輸出。
動作說明: [n1[,n2]]function
n1, n2 :不見得會存在,一般代表『選擇進行動作的行數』,舉例來說,如果我的動作
是需要在 10 到 20 行之間進行的,則『 10,20[動作行為] 』
function 有底下這些咚咚:
a :新增, a 的後面可以接字串,而這些字串會在新的一行出現(目前的下一行)∼
c :取代, c 的後面可以接字串,這些字串可以取代 n1,n2 之間的行!
d :刪除,因為是刪除啊,所以 d 後面通常不接任何咚咚;
i :插入, i 的後面可以接字串,而這些字串會在新的一行出現(目前的上一行);
p :列印,亦即將某個選擇的資料印出。通常 p 會與參數 sed -n 一起運作∼
s :取代,可以直接進行取代的工作哩!通常這個 s 的動作可以搭配
正規表示法!例如 1,20s/old/new/g 就是啦!
範例:
範例一:將 /etc/passwd 的內容列出,並且我需要列印行號,同時,請將第 2~5 行刪除!
[root@linux ~]# nl /etc/passwd | sed '2,5d'
1 root:x:0:0:root:/root:/bin/bash
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
.....(後面省略).....
# 看到了吧?因為 2-5 行給他刪除了,所以顯示的資料中,就沒有 2-5 行囉∼
# 另外,注意一下,原本應該是要下達 sed -e 才對,沒有 -e 也行啦!
# 同時也要注意的是, sed 後面接的動作,請務必以 '' 兩個單引號括住喔!
# 而,如果只要刪除第 2 行,可以使用 nl /etc/passwd | sed '2d' 來達成,
# 至於第 3 到最後一行,則是 nl /etc/passwd | sed '3,$d' 的啦!
範例二:承上題,在第二行後(亦即是加在第三行)加上『drink tea?』字樣!
[root@linux ~]# nl /etc/passwd | sed '2a drink tea'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
drink tea
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
# 嘿嘿!在 a 後面加上的字串就已將出現在第二行後面囉!那如果是要在第二行前呢?
# nl /etc/passwd | sed '2i drink tea' 就對啦!
範例三:在第二行後面加入兩行字,例如『Drink tea or .....』『drink beer?』
[root@linux ~]# nl /etc/passwd | sed '2a Drink tea or ......\
> drink beer ?'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
Drink tea or ......
drink beer ?
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
# 這個範例的重點是,我們可以新增不只一行喔!可以新增好幾行∼
# 但是每一行之間都必須要以反斜線 \ 來進行新行的增加喔!所以,上面的例子中,
# 我們可以發現在第一行的最後面就有 \ 存在啦!那是一定要的喔!
範例四:我想將第2-5行的內容取代成為『No 2-5 number』呢?
[root@linux ~]# nl /etc/passwd | sed '2,5c No 2-5 number'
1 root:x:0:0:root:/root:/bin/bash
No 2-5 number
6 sync:x:5:0:sync:/sbin:/bin/sync
# 沒有了 2-5 行,嘿嘿嘿嘿!我們要的資料就出現啦!
範例五:僅列出第 5-7 行
[root@linux ~]# nl /etc/passwd | sed -n '5,7p'
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
# 為什麼要加 -n 的參數呢?您可以自行下達 sed '5,7p' 就知道了!(5-7行會重複輸出)
# 有沒有加上 -n 的參數時,輸出的資料可是差很多的喔!
範例六:我們可以使用 ifconfig 來列出 IP ,若僅要 eth0 的 IP 時?
[root@linux ~]# ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:51:FD:52:9A:CA
inet addr:192.168.1.12 Bcast:192.168.1.255 Mask:255.255.255.0
inet6 addr: fe80::250:fcff:fe22:9acb/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
.....(以下省略).....
# 其實,我們要的只是那個 inet addr:..那一行而已,所以囉,利用 grep 與 sed 來捉
[root@linux ~]# ifconfig eth0 | grep 'inet ' | sed 's/^.*addr://g' | \
> sed 's/Bcast.*$//g'
# 您可以將每個管線 (|) 的過程都分開來執行,就會曉得原因囉!
# 去頭去尾之後,就會得到我們所需要的 IP 亦即是 192.168.1.12 囉∼
範例七:將 /etc/man.config 檔案的內容中,有 MAN 的設定就取出來,但不要說明內容。
[root@linux ~]# cat /etc/man.config | grep 'MAN'| sed 's/#.*$//g' | \
> sed '/^$/d'
# 每一行當中,若有 # 表示該行為註解,但是要注意的是,有時候,
# 註解並不是寫在第一個字元,亦即是寫在某個指令後方,如底下的模樣:
# 『shutdown -h now # 這個是關機的指令』,註解 # 就在指令的後方了。
# 因此,我們才會使用到將 #.*$ 這個正規表示法!
範例八:利用 sed 直接在 ~/.bashrc 最後一行加入『# This is a test』
[root@linux ~]# sed -i '$a # This is a test' ~/.bashrc
# 上頭的 -i 參數可以讓你的 sed 直接去修改後面接的檔案內容喔!而不是由螢幕輸出。
# 至於那個 $a 則代表最後一行才新增的意思。
|
總之,這個 sed 不錯用啦!而且很多的 shell script 都會使用到這個指令的功能∼
sed 可以幫助系統管理員管理好日常的工作喔!要仔細的學習呢!
awk 工具簡介
相較於 sed 常常作用於一整個行的處理, awk 則比較傾向於一行當中分成數個『欄位』來處理。
因此,awk 相當的適合處理小型的數據資料處理呢!awk 通常運作的模式是這樣的:
[root@linux ~]# awk '條件類型1{動作1} 條件類型2{動作2} ...' filename
|
awk 可以處理後續接的檔案,也可以讀取來自前個指令的 standard output 。
但如前面說的, awk 主要是處理『每一行的欄位內的資料』,而預設的『欄位的分隔符號為
"空白鍵" 或 "[tab]鍵" 』!舉例來說,我們用 last 可以將登入者的資料取出來,
結果如下所示:
[root@linux ~]# last
dmtsai pts/0 192.168.1.12 Mon Aug 22 09:40 still logged in
root tty1 Mon Aug 15 11:38 - 11:39 (00:01)
reboot system boot 2.6.11 Sun Aug 14 18:18 (7+15:41)
dmtsai pts/0 192.168.1.12 Fri Aug 12 12:07 - 12:08 (00:01)
|
若我想要取出帳號與登入者的 IP ,且帳號與 IP 之間以 [tab] 隔開,則會變成這樣:
[root@linux ~]# last | awk '{print $1 "\t" $3}'
dmtsai 192.168.1.12
root Mon
reboot boot
dmtsai 192.168.1.12
|
因為不論哪一行我都要處理,因此,就不需要有 "條件類型" 的限制!我所想要的是第一欄以及第三欄,
但是,第二行及第三行的內容怪怪的∼這是因為資料格式的問題啊!所以囉∼使用 awk
的時候,請先確認一下您的資料當中,如果是連續性的資料,請不要有空格或 [tab]
在內,否則,就會像這個例子這樣,會發生誤判喔!
另外,由上面這個例子您也會知道,在每一行的每個欄位都是有變數名稱的,那就是 $1, $2...
等變數名稱,以上面的例子來說, dmtsai 是 $1 ,因為他是第一欄嘛!至於 192.168.1.12 是第三欄,
所以他就是 $3 啦!後面以此類推∼呵呵!還有個變數喔!那就是 $0 ,$0 代表『一整列資料』的意思∼
以上面的例子來說,第一行的 $0 代表的就是『dmtsai pts/0.... 』那一行啊!
由此可知,剛剛上面四行當中,整個 awk 的處理流程是:
- 讀入第一行,並將第一行的資料填入 $0, $1, $2.... 等變數當中;
- 依據 "條件類型" 的限制,判斷是否需要進行後面的 "動作";
- 做完所有的動作與條件類型;
- 若還有後續的『行』的資料,則重複上面 1~3 的步驟,直到所有的資料都讀完為止。
經過這樣的步驟,您會曉得, awk 是『 以行為一次處理的單位』,
而『 以欄位為最小的處理單位』。好了,那麼 awk
怎麼知道我到底這個資料有幾行?有幾欄呢?這就需要 awk 的內建變數的幫忙啦∼
變數名稱 | 代表意義 |
NF | 每一行 ($0) 擁有的欄位總數 |
NR | 目前 awk 所處理的是『第幾行』資料 |
FS | 目前的分隔字元,預設是空白鍵 |
我們繼續以上面例子來做說明,如果我想要列出每一行的帳號,並且列出目前處理的行數,
並且說明,該行有多少欄位,則可以這樣 ( 注意, awk 後續的所有動作以 ' 括住,
所以,內容如果想要以 print 列印時,記得,非變數的文字部分,包含上一小節
printf 提到的格式中,都需要使用雙引號來定義出來喔!)
[root@linux ~]# last | awk '{print $1 "\t lines: " NR "\t columes: " NF}'
dmtsai lines: 1 columes: 10
root lines: 2 columes: 9
reboot lines: 3 columes: 9
dmtsai lines: 4 columes: 10
|
這樣可以瞭解 NR 與 NF 的差別了吧?好了,底下來談一談所謂的 "條件類型" 了吧!
awk 的邏輯運算字元
既然有需要用到 "條件" 的類別,自然就需要一些邏輯運算囉∼例如底下這些:
運算單元 | 代表意義 |
> | 大於 |
< | 小於 |
>= | 大於或等於 |
<= | 小於或等於 |
== | 等於 |
!= | 不等於 |
值得注意的是那個 == 的符號,因為在『邏輯運算』上面,
就是所謂的大於、小於、等於等等的判斷式上面,我們習慣上是以 ==
來表示,而如果是直接給予一個值,例如變數設定時,就直接使用 = 而已。
好了,我們實際來運用一下邏輯判斷吧!舉例來說,在 /etc/passwd 當中是以冒號 ":"
來作為欄位的分隔,那假設我要查閱,第三欄小於 10 以下的數據,並且僅列出帳號與第三欄,
那麼可以這樣做:
[root@linux ~]# cat /etc/passwd | \
> awk '{FS=":"} $3 < 10 {print $1 "\t " $3}'
root:x:0:0:root:/root:/bin/bash
bin 1
daemon 2
......(以下省略)......
|
有趣吧!不過,怎麼第一行沒有正確的顯示出來呢?這是因為我們讀入第一行的時候,
那些變數 $1, $2... 預設還是以空白鍵為分隔的,所以雖然我們定義了 FS=":" 了,
但是卻僅能在第二行後才開始生效。那麼怎麼辦呢?我們可以預先設定 awk 的變數啊!
利用 BEGIN 這個關鍵字喔!這樣做:
[root@linux ~]# cat /etc/passwd | \
> awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t " $3}'
root 0
bin 1
daemon 2
......(以下省略)......
|
很有趣吧!而除了 BEGIN 之外,我們還有 END 呢!另外,如果要用 awk
來進行『計算功能』呢?以底下的例子來看,
假設我有一個薪資資料表,內容是這樣的:
Name 1st 2nd 3th
VBird 23000 24000 25000
DMTsai 21000 20000 23000
Bird2 43000 42000 41000
|
如何幫我計算每個人的總額呢?而且我還想要格式化輸出喔!
你可以將上面的資料儲存成一個名稱為 pay.txt 的檔案,則:
[root@linux ~]# cat pay.txt | \
> awk 'NR==1{printf "%10s %10s %10s %10s %10s\n",$1,$2,$3,$4,"Total" }
NR>=2{total = $2 + $3 + $4
printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}'
Name 1st 2nd 3th Total
VBird 23000 24000 25000 72000.00
DMTsai 21000 20000 23000 64000.00
Bird2 43000 42000 41000 126000.00
|
上面的例子有幾個重要事項應該要先說明的:
- 所有的動作,亦即在 {} 內的動作,如果有需要多個指令輔助時,可利用分號『;』間隔,
或者直接以 [Enter] 按鍵來隔開每個指令,例如上面的 NR>=2 後面接的動作,
利用 total = ... 那個指令來指定加總,而後續則以 printf 來格式化輸出!
- 邏輯運算當中,如果是『等於』的情況,則務必使用兩個等號『==』!
- 格式化輸出時,在 printf 的格式設定當中,務必加上 \n ,才能進行分行!
- 與 bash shell 的變數不同,在 awk 當中,變數可以直接使用,不需加上 $ 符號。
利用 awk 這個玩意兒,就可以幫我們處理很多日常工作了呢!真是好用的很∼
此外, awk 的輸出格式當中,常常會以 printf 來輔助,所以,
最好您對 printf 也稍微熟悉一下比較好啦!另外, awk 的動作內 {} 也是支援 if (條件) 的喔!
舉例來說,上面的指令可以修訂成為這樣:
[root@linux ~]# cat pay.txt | \
> awk '{if(NR==1) printf "%10s %10s %10s %10s %10s\n",$1,$2,$3,$4,"Total"}
NR>=2{total = $2 + $3 + $4
printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}'
|
你可以仔細的比對一下上面兩個輸入有啥不同∼從中去瞭解兩種語法吧!
我個人是比較傾向於使用第一種語法,因為會比較有統一性啊! ^_^
除此之外, awk 還可以幫我們進行迴圈計算喔!真是相當的好用!
不過,那屬於比較進階的單獨課程了,我們這裡就不再多加介紹。如果您有興趣的話,
可以到中研院的網站查詢喔: http://phi.sinica.edu.tw/aspac/reports/94/94011/,鳥哥這裡也有一份
pdf 檔的備份: http://linux.vbird.org/linux_basic/0330regularex/awk.pdf。
您可以自行參閱一下該文章的內容,裡頭可以好好的查閱一下關於陣列與迴圈方面的介紹,
我認為該文章寫的很棒喔!該介紹的都介紹了!很好∼我喜歡∼ ^_^
文件資料比對與列印的相關功能
正規表示法是相當有用的工具,當然,那個 sed 還有 awk 也是很棒的工具程式,
不過,除此之外,我們其實還有很多可以使用的工作來處理文件資料喔!
舉例來說,假如我有兩個檔案,一個檔案是原始檔,一個則是經過一些時間累積處理後的檔案,
我想要知道這兩個檔案之間的差別,該如何運用正規表示法?呼呼∼可能要透過所謂的迴圈來一行一行比對檢查呢∼
但是,我們可以透過 Linux 提供的 diff 及 cmp 指令來進行比對即可喔!很棒的啊!
檔案比對
什麼時候會用到檔案的比對啊?通常是『同一個套裝軟體的不同版本之間,比較設定檔與原始檔的差異』,
所以囉,很多時候所謂的檔案比對,通常是用在 ASCII 純文字檔的比對上的!
那麼比對檔案的指令有哪些?最常見的就是 diff 囉!
diff
diff 就是用在比對兩個檔案之間的差異的,一般是用在 ASCII 純文字檔的比對上。
我們先預處理一下一個檔案好了。假設我要將 /etc/passwd 的內容,將第四行刪除,
第六行則取代成為『no six line』,新的檔案放置到 /tmp/test 裡面,
那麼應該怎麼做?
[root@linux ~]# mkdir -p /tmp/test
[root@linux ~]# cat /etc/passwd | \
> sed -e '4d' -e '6c no six line' > /tmp/test/passwd
# 注意一下, sed 後面如果要接超過兩個以上的動作時,每個動作前面得加 -e 才行!
|
接下來討論一下關於 diff 的用法吧!
[root@linux ~]# diff [-bBi] from-file to-file
參數:
from-file :一個檔名,作為原始比對檔案的檔名;
to-file :一個檔名,作為目的比對檔案的檔名;
注意,from-file 或 to-file 可以 - 取代,那個 - 代表『Standard input』之意。
-b :忽略一行當中,僅有多個空白的差異(例如 "about me" 與 "about me" 視為相同
-B :忽略空白行的差異。
-i :忽略大小寫的不同。
範例:
範例一:比對 /tmp/test/passwd 與 /etc/passwd 的差異:
[root@linux ~]# diff /etc/passwd /tmp/test/passwd
4d3 <==這裡是說,左邊檔案(/etc/passwd)第四行被刪除 (d)
< adm:x:3:4:adm:/var/adm:/sbin/nologin
6c5 <==這裡是說,左邊檔案的第六行被取代成右邊檔案(/tmp/test/passwd)的第五行
< sync:x:5:0:sync:/sbin:/bin/sync
---
> no six line
# 很聰明吧!用 diff 就把我們剛剛的處理給比對完畢了!
|
用 diff 比對檔案真的是很簡單喔!另外, diff 也可以比對整個目錄下的差異喔!
舉例來說,我們將兩個目錄比對一下:
[root@linux ~]# diff /etc /tmp/test
......(前面省略).....
Only in /etc: paper.config
diff /etc/passwd /tmp/test/passwd
4d3
< adm:x:3:4:adm:/var/adm:/sbin/nologin
6c5
< sync:x:5:0:sync:/sbin:/bin/sync
---
> no six line
Only in /etc: passwd-
......(後面省略).....
|
我們的 diff 很聰明吧!還可以比對不同目錄下的相同檔名的內容,
這樣真的很方便喔∼
cmp
相對於 diff 的廣泛用途, cmp 似乎就用的沒有這麼多了∼
cmp 主要也是在比對兩個檔案,他主要利用『位元』單位去比對,因此,
當然也可以比對 binary file 囉∼(還是要再提醒喔, diff 主要是以『行』為單位比對,
cmp 則是以『位元』為單位去比對,這並不相同!)
[root@linux ~]# cmp [-s] file1 file2
參數:
-s :將所有的不同點的位元處都列出來。因為 cmp 預設僅會輸出第一個發現的不同點。
範例:
範例一:用 cmp 比較一下 /etc/passwd 與 /tmp/test/passwd
[root@linux ~]# cmp /etc/passwd /tmp/test/passwd
/etc/passwd /tmp/test/passwd differ: byte 106, line 4
|
看到了嗎?第一個發現的不同點在第四行,而且位元數是在第 106 個位元處!
這個 cmp 也可以用來比對 binary 啦! ^_^
patch
patch 這個指令與 diff 可是有密不可分的關係啊!我們前面提到,
diff 可以用來分辨兩個版本之間的差異,舉例來說,剛剛我們所建立的 /tmp/test/passwd
與 /etc/passwd 就是兩個不同版本之間的檔案。那麼,如果要『升級』呢?
就是『將舊的檔案升級成為新的檔案』時,應該要怎麼做呢?
舉例來說,我們可以這樣做測試:
[root@linux ~]# mkdir /tmp/old; cp /etc/passwd /tmp/old
[root@linux ~]# mkdir /tmp/new; cp /tmp/test/passwd /tmp/new
[root@linux ~]# cd /tmp ; diff -Naur old/ new/ > test.patch
|
此時,在 /tmp/test.patch 檔案之中,就記錄了新舊的檔案之間的差異,
對了!您必須要瞭解的是,用 diff 製作這個檔案時,舊的檔案必須是在前面,亦即是
diff oldfile newfile 才行喔!此外,新舊檔案的『相對目錄位置』最好也是一樣比較好喔!
OK!那麼如何將舊的內容 (/tmp/old/passwd) 更新到新版 (/tmp/new/passwd) 的內容呢?
簡單的說,可以用這樣:
[root@linux ~]# patch -pN < patch_file
參數:
-p :後面可以接『取消幾層目錄』的意思。
範例:
範例一:將剛剛製作出來的 patch file 用來更新舊版資料
[root@linux ~]# cd /tmp/old
[root@linux ~]# patch -p1 < /tmp/test.patch
patching file passwd
# 為什麼這裡會使用 -p1 呢?因為我們在比對新舊版的資料時,是在 /tmp 底下,
# 而實際的資料是在 /tmp/old 裡面,因此,當我們進入到 /tmp/old 時,
# 再查閱 /tmp/test.patch 的第一行如下:
# diff -Naur old/passwd new/passwd (用 head -n 1 /tmp/test.patch)
# 發現到,我們所在的目錄其實是 old 裡面,所以,就必須要減去一層目錄。
|
更詳細的 patch 用法我們會在後續的第五章跟大家介紹,
這裡僅是介紹給您,呵呵!我們可以利用 diff 來比對兩個檔案之間的差異,
更可進一步利用這個功能來製作修補檔案 (patch file) ,讓大家更容易進行比對與升級呢!
很不賴吧! ^_^
檔案列印準備: pr
如果您曾經使用過一些圖形介面的文書處理軟體的話,那麼很容易發現,
當我們在列印的時候,可以同時選擇與設定每一頁列印時的標頭吧!
也可以設定頁碼呢!那麼,如果我是在 Linux 底下列印純文字檔呢
可不可以具有標題啊?可不可以加入頁碼啊?呵呵!當然可以啊!
使用 pr 就能夠達到這個功能了。不過, pr 的參數實在太多了,
我也說不完,一般來說,我都僅使用最簡單的方式來處理而已。
舉例來說,如果想要列印 /etc/man.config 呢?
[root@linux ~]# pr /etc/man.config
2003-02-10 23:20 /etc/man.config Page 1
#
# Generated automatically from man.conf.in by the
# configure script.
.....以下省略......
|
上面特殊字體那一行呢,其實就是使用 pr 處理後所造成的標題啦∼
標題中會有『檔案時間』、『檔案檔名』及『頁碼』三大項目。
更多的 pr 使用,請參考 pr 的說明啊! ^_^
重點回顧
- 使用 grep 或其他工具進行正規表示法的字串比對時,因為編碼的問題會有不同的狀態,因此,
您最好將 LANG 及 LANGUAGE 等變數設定為 C 或者是 en 等英文語系!
-
正規表示法 ( Regular Expression ) 的用途主要是用來做為『搜尋』字串之用,還可以用來過濾特殊訊息等用途;
-
由於嚴謹度的不同,正規表示法之上還有更嚴謹的延伸正規表示法;
-
正規表示法的處理方式,經常是以『整行』或稱為『整段』來進行處理的;
-
grep 與 egrep 在正規表示法裡面是很常見的兩支程式,其中, egrep 支援更嚴謹的正規表示法的語法;
參考資源
本章習題練習
( 要看答案請將滑鼠移動到『答:』底下的空白處,按下左鍵圈選空白處即可察看 )
- 我想要知道某個檔案裡面含有 boot 的字眼,而這個檔案在 /etc/ 底下,我要如何找出這個檔案?
- 我想要知道,在 /etc 底下,只要含有 XYZ 三個字元的任何一個字元的那一行就列出來,要怎樣進行?
『只要』含有 X 或 Y 或 Z 就將該行列出來,因此,我們的範圍很很廣啦!這個時候就必需要使用到
[] 這個咚咚!還記得中括號的用途嗎?那就是『在中括號裡面謹代表一個字元而已!』
而這個中括號是一個『代表』,可以是一串字也可以是幾個不連續的字!這裡我們僅需要
XYZ 其中任何一個,所以可以這樣寫:
則只要在每一行當中,只要發現 X 或 Y 或 Z 任何一個,就會將他印出來!這個與
grep XYZ /etc/* 是『完全不一樣』的!請仔細的思考一下ㄟ!
- 我想要找出在 /etc 底下,檔案內容含有 * 的檔案名稱?
由於 * 是特殊字元,在變數的訂定法則裡面曾經提過要將特殊字元移除,需要使用跳脫字元,亦即是
\ 符號,所以我可以這樣下達指令:
2002/07/29:第一次完成;
2003/02/10:重新編排與加入 FAQ ;
2005/01/28:重新彙整基礎正規表示法的內容!重點在 regular_express.txt 的處理與練習上!
2005/03/30:修訂了 grep -n 'goo*g' regular_express.txt 這一段
2005/05/23:修訂了 grep -n '^[a-z]' regular_express.txt 所要擷取的是小寫,之前寫成大寫,錯了!
2005/08/22:加入了 awk, sed 等工具的介紹,還有 diff 與 cmp 等指令的說明!
2005/09/05:加入 printf 內,關於 \xNN 的說明!
2006/03/10:將原本的 sed 內的動作(action)中, s 由『搜尋』改成『取代』了!
2006/10/05:在 sed 當中多了一個 -i 的參數說明,也多了一個範例八可以參考。感謝討論區的thyme兄!
2008/10/08:加入 grep 內的 --color=auto 說明!
|
|