鳥哥的 Linux 私房菜
<<

第 9 堂課:正規表示法與 shell script 初探

最近更新日期:2016/05/02

談完了指令列的 bash shell 的操作,再來得要思考一下,如果管理員有一堆指令要循序進行,且這些指令可能具有相依性 (例如判斷式), 或者是管理員需要寫一些讓使用者『互動』的指令碼時,應該如何處理?這時就得要透過 bash 的 shell script (程式化腳本) 來進行了。 此外,很多時候我們需要進行資料的擷取,這時好用的正規表示法就得要派上用場了!

9.1:正規表示法的應用

使用者在操作郵件時,常常會發現到許多郵件被丟到垃圾桶或者是被判定為病毒郵件,這些判定的方式,很多就是透過『正規表示法』來處理的! 正規表示法 (Regular Expression) 就是處理字串的方法,他是以行為單位來進行字串的處理行為,正規表示法透過一些特殊符號的輔助, 可以讓使用者輕易的達到『搜尋/刪除/取代』某特定字串的處理程序!

9.1.1:grep 指令的應用

由於正規表示法牽涉到資料的擷取,因此讀者們先了解一下最簡單的資料擷取指令: grep 的進階用法。例如,找出 /etc/passwd 當中, 含有 student 的那行,且列出行號:

[student@localhost ~]$ grep -n student /etc/passwd
43:student:x:1000:1000::/home/student:/bin/bash

讀者們會看到輸出的資訊中,最前面會多出一個行號的資訊。若需要使用 dmesg 擷取 eth0 這個網卡關鍵字,則使用:

[student@localhost ~]$ dmesg | grep -n -i eth0
671:[    4.334926] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready

承上,如果我們還需要知道該行之前的 4 行以及之後的 3 行,以了解這行前後文的話,則可以這樣處理:

[student@localhost ~]$ dmesg | grep -n -A 3 -B 4 -i eth0
667-[    4.071531] nf_conntrack version 0.5.0 (16384 buckets, 65536 max)
668-[    4.087847] ip6_tables: (C) 2000-2006 Netfilter Core Team
669-[    4.127769] Ebtables v2.0 registered
670-[    4.138456] Bridge firewalling registered
671:[    4.334926] IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready   <==以這行為基準
672-[   11.114627] tun: Universal TUN/TAP device driver, 1.6
673-[   11.114631] tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>
674-[   11.225880] device virbr0-nic entered promiscuous mode

例題:
  1. 輸入 df 後,將 tmpfs 相關的那幾行取消,讓螢幕僅輸出一般的檔案系統,方便查閱。 (註:man grep 找 invert "反向" 搜尋的關鍵字)

9.1.2:正規表示法的符號意義

正規表示法既然是透過一些字符來作為資料擷取的判斷,那麼有哪些慣用的符號呢?大概有底下這些基本的符號:

RE 字符意義與範例
^word意義:待搜尋的字串(word)在行首!
範例:搜尋行首為 # 開始的那一行,並列出行號
grep -n '^#' regular_express.txt
word$意義:待搜尋的字串(word)在行尾!
範例:將行尾為 ! 的那一行列印出來,並列出行號
grep -n '!$' regular_express.txt
.意義:代表『一定有一個任意字元』的字符!
範例:搜尋的字串可以是 (eve) (eae) (eee) (e e), 但不能僅有 (ee) !亦即 e 與 e 中間『一定』僅有一個字元,而空白字元也是字元!
grep -n 'e.e' regular_express.txt
\意義:跳脫字符,將特殊符號的特殊意義去除!
範例:搜尋含有單引號 ' 的那一行!
grep -n \' regular_express.txt
*意義:重複零個到無窮多個的前一個 RE 字符
範例:找出含有 (es) (ess) (esss) 等等的字串,注意,因為 * 可以是 0 個,所以 es 也是符合帶搜尋字串。另外,因為 * 為重複『前一個 RE 字符』的符號, 因此,在 * 之前必須要緊接著一個 RE 字符喔!例如任意字元則為 『.*』 !
grep -n 'ess*' regular_express.txt
[list]意義:字元集合的 RE 字符,裡面列出想要擷取的字元!
範例:搜尋含有 (gl) 或 (gd) 的那一行,需要特別留意的是,在 [] 當中『謹代表一個待搜尋的字元』, 例如『 a[afl]y 』代表搜尋的字串可以是 aay, afy, aly 即 [afl] 代表 a 或 f 或 l 的意思!
grep -n 'g[ld]' regular_express.txt
[n1-n2]意義:字元集合的 RE 字符,裡面列出想要擷取的字元範圍!
範例:搜尋含有任意數字的那一行!需特別留意,在字元集合 [] 中的減號 - 是有特殊意義的,他代表兩個字元之間的所有連續字元!但這個連續與否與 ASCII 編碼有關,因此,你的編碼需要設定正確(在 bash 當中,需要確定 LANG 與 LANGUAGE 的變數是否正確!) 例如所有大寫字元則為 [A-Z]
grep -n '[A-Z]' regular_express.txt
[^list]意義:字元集合的 RE 字符,裡面列出不要的字串或範圍!
範例:搜尋的字串可以是 (oog) (ood) 但不能是 (oot) ,那個 ^ 在 [] 內時,代表的意義是『反向選擇』的意思。 例如,我不要大寫字元,則為 [^A-Z]。但是,需要特別注意的是,如果以 grep -n [^A-Z] regular_express.txt 來搜尋,卻發現該檔案內的所有行都被列出,為什麼?因為這個 [^A-Z] 是『非大寫字元』的意思, 因為每一行均有非大寫字元,例如第一行的 "Open Source" 就有 p,e,n,o.... 等等的小寫字
grep -n 'oo[^t]' regular_express.txt
\{n,m\}意義:連續 n 到 m 個的『前一個 RE 字符』
意義:若為 \{n\} 則是連續 n 個的前一個 RE 字符,
意義:若是 \{n,\} 則是連續 n 個以上的前一個 RE 字符!
範例:在 g 與 g 之間有 2 個到 3 個的 o 存在的字串,亦即 (goog)(gooog)
grep -n 'go\{2,3\}g' regular_express.txt

另外,由於字元擷取通常會有大小寫、數字、特殊字元等等的差異,因此我們也能夠使用如下的符號來代表某些特殊字元:

特殊符號代表意義
[:alnum:]代表英文大小寫字元及數字,亦即 0-9, A-Z, a-z
[:alpha:]代表任何英文大小寫字元,亦即 A-Z, a-z
[:blank:]代表空白鍵與 [Tab] 按鍵兩者
[:cntrl:]代表鍵盤上面的控制按鍵,亦即包括 CR, LF, Tab, Del.. 等等
[:digit:]代表數字而已,亦即 0-9
[:graph:]除了空白字元 (空白鍵與 [Tab] 按鍵) 外的其他所有按鍵
[:lower:]代表小寫字元,亦即 a-z
[:print:]代表任何可以被列印出來的字元
[:punct:]代表標點符號 (punctuation symbol),亦即:" ' ? ! ; : # $...
[:upper:]代表大寫字元,亦即 A-Z
[:space:]任何會產生空白的字元,包括空白鍵, [Tab], CR 等等
[:xdigit:]代表 16 進位的數字類型,因此包括: 0-9, A-F, a-f 的數字與字元

請操作者進行如下的例題來處理相關任務:

例題:
  1. 找出 /etc/services 內含 http 關鍵字的那幾行
  2. 承上,若僅須『開頭含有 http 』字樣的那幾行?
  3. 承上,若僅須『開頭含有 http 或 https 』字樣的那幾行?
  4. 承上,若僅須『開頭含有 http 或 https 』字樣之外,且後面僅能接空白字元或 [tab] 字元的那幾行?
  5. 承上,若僅須『開頭含有 http 且後續接有 80 』字樣的那幾行
  6. 找出 /etc/services 內含有星號 (*) 的那幾行
  7. 找出 /etc/services 含有一個數字緊鄰一個大寫字元的那幾行
  8. 找出 /etc/services 開頭是一個數字緊鄰一個大寫字元的那幾行
  9. 使用 find /etc 找出檔名,並找出結尾含有『 .conf 』的檔名資料
  10. 承上,且含有大寫字元檔名在內的那幾個
  11. 承上,且含有數字在內的那幾個

9.1.3:sed 工具的使用

sed 也是支援正規表示法的一項工具軟體,具有很多很好用的功能在內!過去我們曾經使用過 ifconfig 與 awk 來找到 IP , 現在讓我們使用 sed 來處理 IP 的設定。最基礎的 sed 功能為取代,如下所示:

[student@localhost ~]$ sed 's/舊字串/新字串/g' 檔案內容

使用者只要替換『新舊字串』內容,即可處理相關的字串修訂。現在讓我們來處理 IP 的擷取。使用 ifconfig eth0 來輸出網路資料, 之後以 grep 取出 inet 那一行:

[student@localhost ~]$ ifconfig eth0 | grep 'inet[[:space:]]'
        inet 172.16.0.83  netmask 255.255.0.0  broadcast 172.16.255.255

使用 sed 取代開頭到 inet 空白的項目:

[student@localhost ~]$ ifconfig eth0 | grep 'inet[[:space:]]' | \
> sed 's/^.*inet[[:space:]]*//g'
172.16.0.83  netmask 255.255.0.0  broadcast 172.16.255.255

再接著取消空白netmask 之後的訊息

[student@localhost ~]$ ifconfig eth0 | grep 'inet[[:space:]]' | \
> sed 's/^.*inet[[:space:]]*//g' | sed 's/[[:space:]]*netmask.*$//g'
172.16.0.83  netmask 255.255.0.0  broadcast 172.16.255.255

除了替換資料之外,sed 還可以擷取出特定的關鍵行數,例如只想要取出 10-15 行的 /etc/passwd 內容時,可以這樣做:

[student@localhost ~]$ cat -n /etc/passwd | sed -n '10,15p' 

這個動作在處理一些腳本化程式時,相當有幫助!而如果想要直接修改檔案內容時,例如想要將 .bashrc 內的 function 改成大寫時, 也可以這樣做:

[student@localhost ~]$ sed 's/function/FUNCTION/g' .bashrc
.......
# User specific aliases and FUNCTIONs >==這裡會變大寫

[student@localhost ~]$ sed -i 's/function/FUNCTION/g' .bashrc

加上 -i 選項後,該改變直接寫入檔案,且不會在螢幕上輸出了!因此使用上需要特別注意!

例題:
  1. 找出 /etc/passwd 裡面,結尾是 bash 的那幾行
  2. 承上,透過 sed 將 /bin/bash 改成 /sbin/nologin 顯示到螢幕上
  3. 承上,透過 tr 這個指令,將全部的英文都變成大寫字元

9.2:學習 shell script

shell script 對管理員來說,是一項非常好用的工具!請讀者們一定要自己手動設計過一次相關的腳本程式, 而且能夠針對自己管理的伺服器進行一些例行工作的優化,才會更有感覺。

9.2.1:基礎 shell script 的撰寫與執行

shell script 的撰寫其實沒有很難,基本上需要注意到:

  • 指令的執行是從上而下、從左而右的分析與執行;
  • 指令的下達中,指令、選項與參數間的多個空白都會被忽略掉;
  • 空白行也將被忽略掉,並且 [tab] 按鍵所推開的空白同樣視為空白鍵;
  • 如果讀取到一個 Enter 符號 (CR) ,就嘗試開始執行該行 (或該串) 命令;
  • 至於如果一行的內容太多,則可以使用『 \[Enter] 』來延伸至下一行;
  • 『 # 』可做為註解!任何加在 # 後面的資料將全部被視為註解文字而被忽略!

至於 shell script 的執行,例如有名為 /home/student/shell.sh 的 script 時,可以用底下的方法:

  • 直接指令下達: shell.sh 檔案必須要具備可讀與可執行 (rx) 的權限,然後:
    • 絕對路徑:使用 /home/student/shell.sh 來下達指令;
    • 相對路徑:假設工作目錄在 /home/student/ ,則使用 ./shell.sh 來執行
    • 變數『PATH』功能:將 shell.sh 放在 PATH 指定的目錄內,例如: ~/bin/
  • 以 bash 程式來執行:透過『 bash shell.sh 』或『 sh shell.sh 』來執行

底下我們將使用 student 的身份,並在 ~/bin 底下建立多個 shell script 來作為練習。首先,如果操作者執行 myid.sh 時, 系統會輸出這個帳號的 id 指令輸出訊息,並且輸出使用者的家目錄 (${HOME})、以及歷史命令紀錄筆數 (${HISTSIZE}), 最後列出所有的命令別名 (alias) 時,我們可以這樣做:

  1. 請先宣告使用的 script 為 bash ;
  2. 請先說明程式的功能 (需要用 # 註解)
  3. 請先說明程式的撰寫者 (需要用 # 註解)
  4. 秀出『 This script will show your account messages 』
  5. 秀出『 The 'id' command output is: 』
  6. 秀出 id 這個指令的結果
  7. 秀出『 your user's home is: ${HOME} 』的結果
  8. 秀出『 your history record: ${HISTSIZE} 』的結果
  9. 秀出『 your command aliases: 』
  10. 顯示出 alias 的結果

上述的結果一項一項撰寫成為 myid.sh 的內容如下:

[student@localhost ~]$ mkdir bin
[student@localhost bin]$ vim myid.sh
#!/bin/bash
# This script will use id, echo to show account's messages
# write by VBird 2016/04/27
echo "This script will show your accout messages."
echo "The 'id' command output is: "
id
echo "your user's home is: ${HOME}"
echo "your history record: ${HISTSIZE}"
echo "your command aliases: "
alias

例題:
  1. 如何直接以 bash 或 sh 去執行這隻腳本 (指的是直接用 bash 指令去執行,而不是執行 myid.sh)?
  2. 承上,執行過程中,如果還需要輸出程式碼之後才執行,可以加上哪個選項?(此功能相當有用!可用以檢測程式碼的錯誤, debug)
  3. 若需要直接輸入 myid.sh 就能執行時,需要有什麼設定 (包括權限、路徑等等的資料) ?
  4. 如何使用絕對路徑來執行?
  5. 若你剛好在工作目錄下看到這個腳本,但你不確定工作目錄有沒有在 PATH 環境中,該如何下達指令執行該腳本?
  6. 為何 alias 的結果沒有輸出?若執行該腳本時還要輸出目前的 alias ,該如何執行?為什麼?

9.2.2:shell script 的執行環境

本章之前曾經談過程序的觀察,且程序之間是有相依性的,因此可以使用 pstree 來觀察程序的相依行為。 那麼使用 shell script 時,他與當前的 shell 有無關係呢?底下舉例來瞧瞧。如果你有一個如下的腳本, 該如何進入到該目錄去?

[student@localhost ~]$ cd ~/bin
[student@localhost bin]$ vim gototmp.sh
#!/bin/bash
# this shell script will take you togo /tmp directory.
# VBird 2016/05/02
cd /tmp
[student@localhost bin]$ chmod a+x gototmp.sh

請問,當你執行 gototmp.sh 這個腳本之後,以及『執行期間』,你的工作目錄會在哪裡?為什麼?

例題:你在操作 Linux 系統的過程中,可能會切換到許多不同的 shell (例如 bash 轉到 csh 等等)。不過這些操作環境中,均需要使用到底下的變數, 且這些變數是在有需要時才載入的 (不是寫入到 .bashrc),因此需要額外撰寫成 myenv.sh。內容需要:
  1. 設計 MYIP 的變數為目前系統的 IP (假設網路卡為 eth0 時)
  2. 設計 mywork 的變數為指定到 /usr/local/libexec 目錄中
  3. 設計 megacli 的變數為 /opt/mega/cli/command 這個指令(此指令並不存在,僅作為範例用)
  4. 設計完畢之後,若要使用這個檔案內的資料,該如何執行?

9.3:課後練習操作

請使用 student 的身份進行如下的實做,並將腳本通通放置於 ~student/bin/ 目錄中。

  1. 寫一隻名為 mydate.sh 的腳本,執行後可以輸出如下的資料:
    • 以 西元年/月/日 顯示出目前的日期
    • 以 小時:分鐘:秒鐘 顯示出目前的時間
    • 輸出從 1970/01/01 到目前累計的秒數
    • 列出這個月的月曆,且依據台灣的習慣,輸出時,以星期一為一週的開始。
2016/05/02:這一章是因為期中考過後,要留點時間來檢討期中考的結果,因此幾乎少了 30~60 分鐘的時間!份量較少的原一!
2016/05/02以來統計人數
計數器
>>
HOME
PrePage
NextPage
   http://linux.vbird.org is designed by VBird during 2001-2015.