介紹正規表示法的應用與相關技巧
本文資料主要針對 Fedora Core 4 的系統進行說明, Fedora Core 1 主要是由 Red Hat Linux 9 改版而來, 這個 Red Hat Linux 9 並不是當前大家聽到的 RHEL 喔!那是在 RHEL 出現之前的產品,基本上是在 2003 年以前的作品了!Fedora Core 4 則是在 2005 年 6 月份釋出,使用的核心是 2.6.11 版,當時是很紅的一個作品!只是生命週期太短,所以用這個 Fedora 系列來介紹 Server, 當時的決定確實有點莫名其妙了...
建議您前往本站查詢最新版本的 Linux distribution 文章來閱讀,比較不會浪費時間。那為何還需要編輯 Fedora Core 4 的資料呢? 鳥哥只想要做個自己曾經撰寫過的文件內容保存而已囉! ^_^!最新文章請前往鳥站首頁查閱囉!
正規表示法(或稱為常規表示法)是透過一些特殊字元的排列,用以 搜尋/取代/刪除 一列或多列文字字串, 簡單的說,正規表示法就是用在字串的處理上面的一項『表示式』。正規表示法並不是一個工具程式, 而是一個字串處理的標準依據,如果您想要以正規表示法的方式處理字串,就得要使用支援正規表示法的工具程式才行, 這類的工具程式很多,例如 vi, sed, awk 等等。
正規表示法對於系統管理員來說,實在是很重要。因為系統會產生很多的訊息,這些訊息有的重要,有的僅是告知, 此時,管理員可以透過正規表示法的功能來將重要訊息擷取出來,並產生便於查閱的報表,簡化管理流程。此外, 很多的套裝軟體也都支援正規表示法的分析,例如郵件伺服器的過濾機制(過濾垃圾信件)就是很重要的一個例子。 所以,您最好要瞭解正規表示法的相關技能,在未來管理主機時,才能夠更精簡處理您的日常事務!
註:本章節使用者需要多加練習,因為目前很多的套件都是使用正規表示法來達成其『過濾、分析』的目的, 為了未來主機管理的便利性,使用者至少要能看的懂正規表示法的意義!
[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 行,其中有兩行具有你所搜尋的字串,則將那兩行顯示在螢幕上,其他的就丟棄了!
[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 I like dog. google is the best tools for search keyword. goooooogle yes! go! go! Let's go. # I am VBird |
[root@test root]# LANG=en [root@test root]# export LANG好了,現在開始我們一個案例一個案例的來介紹吧!
[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那如果想要『反向選擇』呢?也就是說,當該行沒有 'the' 這個字串時,才顯示在螢幕上,那就直接使用:is the same with "glad". 18:google is the best tools for search keyword.
[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 worldis the same with "glad". 18:google is the best tools for search keyword.
[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) , 因此,是符合字串搜尋的喔!
[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
[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特別注意到,因為小數點具有其他意義(底下會介紹),所以必須要使用跳脫字元(\)來加以解除其特殊意義! 不過,您或許會覺得奇怪,但是第 5~9 行最後面也是 . 啊~怎麼無法列印出來?? 這裡就牽涉到 Windows 平台的軟體對於斷行字元的判斷問題了!我們使用 cat -A 將第五行拿出來看, 您會發現:is the same with "glad". 17:I like dog. 18:google is the best tools for search keyword.
[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 '^#'是否節省很多版面啊??
[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因為強調 g 與 d 之間一定要存在兩個字元,因此,第 13 行的 god 與第 14 行的 gd 就不會被列出來啦!再來,如果我想要列出有 oo, ooo, oooo 等等的資料, 也就是說,至少要有兩個 o 以上,該如何是好??是 o* 還是 oo* 還是 ooo* 呢? 雖然您可以試看看結果, 不過結果太佔版面了 @_@ ,所以,我這裡就直接說明。is the same with "glad".
[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但測試的結果竟然出現這麼多行??太詭異了吧? 其實一點也不詭異,因為 g*g 裡面的 g* 代表『空字元或一個以上的 g』 在加上後面的 g ,因此,整個 RE 的內容就是 g, gg, ggg, gggg , 因此,只要該行當中擁有一個以上的 g 就符合所需了!is the same with "glad". 17:I like dog. 18:google is the best tools for search keyword. 19:goooooogle yes!
[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 表示法的意義才好!
[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!呵呵!就可以找出來啦~
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] 的搜尋! |
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" 字串的意思~ |
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 呢!鳥哥這裡也只是列出一些可能會用到的格式而已, 有興趣的話,可以自行多作一些測試與練習喔! ^_^
[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 可以幫助系統管理員管理好日常的工作喔!要仔細的學習呢!
[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]
在內,否則,就會像這個例子這樣,會發生誤判喔!變數名稱 | 代表意義 | NF | 每一行 ($0) 擁有的欄位總數 | NR | 目前 awk 所處理的是『第幾行』資料 | FS | 目前的分隔字元,預設是空白鍵 |
[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 的差別了吧?好了,底下來談一談所謂的 "條件類型" 了吧!運算單元 | 代表意義 |
> | 大於 |
< | 小於 |
>= | 大於或等於 |
<= | 小於或等於 |
== | 等於 |
!= | 不等於 |
[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上面的例子有幾個重要事項應該要先說明的:
[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}'你可以仔細的比對一下上面兩個輸入有啥不同~從中去瞭解兩種語法吧! 我個人是比較傾向於使用第一種語法,因為會比較有統一性啊! ^_^
[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 很聰明吧!還可以比對不同目錄下的相同檔名的內容, 這樣真的很方便喔~
[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 啦! ^_^
[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) ,讓大家更容易進行比對與升級呢! 很不賴吧! ^_^
[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 的說明啊! ^_^