Free mind

          Be fresh and eager every morning, and tired and satisfied every night.
          posts - 39, comments - 2, trackbacks - 0, articles - 0
             :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          Shell 和 Shell Script

          Posted on 2007-05-27 19:58 morphis 閱讀(1256) 評(píng)論(0)  編輯  收藏 所屬分類: 6. Linux
          http://www.study-area.net/linux/system/linux_shell.htm

          Shell 和 Shell Script


          認(rèn)識(shí) SHELL

          或許﹐許多人都已經(jīng)聽(tīng)過(guò) shell 或 bash 這些名字﹐但不知道您是否知道它們究竟是什麼東東呢﹖

          先回到電腦基礎(chǔ)常識(shí)上吧﹕所有的電腦都是由硬體和軟體構(gòu)成的﹐硬體就是大家能摸得著看得見(jiàn)的部份﹐例如﹕鍵盤﹑熒幕﹑CPU﹑記憶體﹑硬碟﹑等等。離開(kāi)了硬體﹐所謂的電腦是不存在的﹐因?yàn)檎麄€(gè)系統(tǒng)的輸入和輸出以及運(yùn)算都離不開(kāi)硬體。請(qǐng)問(wèn)﹕如果沒(méi)有鍵盤和熒幕您是怎樣使用電腦的﹖但是﹐您透過(guò)鍵盤進(jìn)行的輸入﹐以及從熒幕看到的輸出﹐真正發(fā)揮功能的﹐是軟體的功勞。而直接負(fù)責(zé)和這些硬體進(jìn)行溝通的軟體﹐就是所謂的核心(kernel)﹐kernel 必須能夠接管鍵盤的輸入﹐然後交由 CPU 進(jìn)行處理﹐最後將執(zhí)行結(jié)果輸出到熒幕上。當(dāng)然﹐除了鍵盤和熒幕外﹐所有的硬體都必須獲得 kernel 的支援才能使用。

          那麼﹐kernel 又如何知道我們鍵盤輸入的東西是什麼呢﹖那就是我們這裡介紹的 shell 所負(fù)責(zé)的事情了。因?yàn)殡娔X本身所處理的數(shù)據(jù)﹐都是二進(jìn)位的機(jī)器碼﹐和我們?nèi)祟惲?xí)慣使用的語(yǔ)言很不一樣。比方說(shuō)﹐輸入 pwd 命令﹐我們知道這是 print working directory 的意思(非常簡(jiǎn)單的人類語(yǔ)音)﹐但作為 kernel 來(lái)說(shuō)﹐它並不知道 pwd 是什麼﹐kernel 只會(huì)看機(jī)器碼﹐這時(shí)候﹐shell 就會(huì)幫我們將 pwd 翻譯為 kernel 能理解的程式碼。所以﹐我們?cè)谑褂秒娔X的時(shí)候﹐基本上就是和 shell 打交道﹐而不是直接和 kernel 溝通﹐更不是直接控制硬體。

          簡(jiǎn)單來(lái)看﹐我們就這樣來(lái)看待它們的關(guān)係﹕光從字面來(lái)解析的話﹐shell 就是“殼”﹐kernel 就是“核”。好比一個(gè)果實(shí)一樣﹐您第一眼看到的就是殼﹐把殼扒開(kāi)才看的到裡面的核。shell 就是使用者和 kernel 之間的界面﹐將使用者下的命令翻譯給 kernel 處理﹐關(guān)係如下圖﹕

           

          我們?cè)?shell 輸入一個(gè)命令﹐shell 會(huì)嘗試搜索整個(gè)命令行﹐並對(duì)其中的一些特殊字符做出處理﹐如果遇到 CR 字符( Enter ) 的時(shí)候﹐就嘗試重組整行命令﹐並解釋給 kernel 執(zhí)行。而一般的命令格式(syntax)大致如下﹕
          # command parameter1 patrameter2 ...

          各命令都有自己的選項(xiàng)(options, 通常用“ - ”符號(hào)帶領(lǐng))﹐可輸入也可以不輸入﹐如果沒(méi)有額外指定﹐命令通常都有自己的預(yù)設(shè)選項(xiàng)﹔而參數(shù)(argument)則視各程式要求而定﹐有些很嚴(yán)格﹐有些也有預(yù)設(shè)的參數(shù)。例如 "ls -l" 這個(gè)命令﹐選項(xiàng)是 -l (long list)﹐而預(yù)設(shè)的參數(shù)則是當(dāng)前目錄。在命令行中,選項(xiàng)和參數(shù)都被稱為參項(xiàng)(parameter)。

          我們經(jīng)常談到的 Linux﹐其實(shí)是指 kernel 這部份﹐而在 kernel 之外﹐則是各種各樣的程式和工具﹐整合起來(lái)才成為一個(gè)完整的 Linux 發(fā)行套件。無(wú)論如何﹐Linux 的 kernel 只有一個(gè)(儘管有許多不同的版本﹐都由 Linus Tovalds 負(fù)責(zé)維護(hù))﹐但 kernel 之外的 shell 卻有許多種﹐例如 bourne Shell﹑C Shell﹑Korn Shell﹑Zsh Shell﹑等等﹐但我們最常接觸到的名叫 BASH (Bourne Again SHell)﹐為 GNU 所加強(qiáng)的一個(gè) burne shell 版本﹐ 也是大多數(shù) Linux 套件的預(yù)設(shè) shell 。不同的 shell 都各自有其不同的優(yōu)缺點(diǎn)﹐有興趣您可以自行找這方面的資料來(lái)看﹐我這裡就不一一介紹了。

          BASH 這個(gè)優(yōu)秀的 shell﹐之所以會(huì)被各大 Linux 套件採(cǎi)用為預(yù)設(shè)的 shell﹐除了它本身是 open source 程式之外﹐它的強(qiáng)大功能應(yīng)該是吸引大家目光的重要因素之一。BASH 的功能很多﹐實(shí)在很難全部介紹﹐下面只列舉其中一少部份而已﹕

          命令補(bǔ)全功能﹕
          當(dāng)您輸入命令的時(shí)候﹐您可以輸入目錄或檔案的開(kāi)首字面﹐然後按‘tab’鍵將您的命令路徑補(bǔ)全。比方說(shuō)﹐您要 ls 一下 /etc/sysconfig 這個(gè)目錄的內(nèi)容(假設(shè)您已經(jīng)在 /etc 目錄下了)﹐您可以只輸入 ls sy 然後接連按兩下 tab 鍵﹐然後就會(huì)將 /etc/ 目錄下所有以 sy 開(kāi)頭的檔案和目錄顯示出來(lái)﹐您或許可以看到 sysconfig﹑sysctl.conf ﹑syslog.conf 這三個(gè)結(jié)果﹔如果您只輸入 ls sys 再按兩下 tab 的話﹐結(jié)果是是一樣的﹐因?yàn)樵?/etc/ 目錄下面﹐所有以 sy 開(kāi)頭的檔案﹐第 3 個(gè)字面都是 s 而沒(méi)有其它字面了﹔如果您輸入 ls sysc 再重複這個(gè)動(dòng)作﹐那麼顯示結(jié)果就剩下 sysconfig 和 sysctl.conf 而已﹐因?yàn)橐?sysc 開(kāi)頭的只有這兩個(gè)檔﹐如果您再按 ls sysco 接一個(gè) tab﹐那就會(huì)幫您將 sysconfig 這個(gè)唯一以 sysco 開(kāi)頭的檔案補(bǔ)全。

           

          如果您所輸入的路徑﹐是唯一的﹐那麼只要按一下 tab 就能補(bǔ)全﹐否則﹐會(huì)聽(tīng)到一下 beat 聲﹐這時(shí)您再補(bǔ)一下 tab ﹐就會(huì)將所有以此路徑開(kāi)頭的檔案列出來(lái)﹔假如符號(hào)條件的檔案太多﹐那系統(tǒng)會(huì)先將符號(hào)條件的檔案數(shù)目告訴您﹐例如 242 possibilities﹐然後您按 y 才顯示﹐如果按 n 則讓您增加命令的輸入﹐然後您可以重複這些動(dòng)作﹐直到您所輸入的路徑只剩唯一的對(duì)應(yīng)﹐才可以用一個(gè) tab 補(bǔ)全。

          同樣的﹐這個(gè)功能也可以用在輸入命令的時(shí)候﹐比方說(shuō)﹐您要輸入 Xconfigurator 命令﹐那您只需輸入 Xc 然後按一下 tab 就可以了﹗是否很方便呢﹖ ^_^

          Tip﹕用 tab 來(lái)補(bǔ)全命令﹐不但方便迅速﹐而且也比較保險(xiǎn)。因?yàn)椹o如果您前面的路徑輸入不正確﹐用 tab 是不能完成補(bǔ)全的﹐這可以避免您輸入錯(cuò)誤的路徑而執(zhí)行錯(cuò)誤的程式。我強(qiáng)烈建議您執(zhí)行每一個(gè)命令都常試用 tab 補(bǔ)全功能﹐以確保其正確性。(多敲這個(gè) tab 鍵沒(méi)什麼壞處啦)

           

           

          命令記錄表﹕
          每次您輸入一個(gè)命令﹐並按 Enter 執(zhí)行之後﹐那您這個(gè)命令就被存放在命令記錄表(command history)中﹐而每個(gè)命令都有一個(gè)記錄號(hào)碼﹐您可以用 history命令來(lái)看看當(dāng)前的命令歷史表。這樣﹐您只要用向上方向鍵﹐就可以依次呼叫出您最近所輸入的命令﹐按下方向鍵則退回最新的命令﹐找到您想要重新輸入的命令﹐然後再按 Enter 即可。

           

          不過(guò)﹐也有一下更便利的辦法﹕您可以輸入 !nnn (其中的 nnn 是 history 命令找到的命令記錄號(hào)碼)﹐就能執(zhí)行指定的舊命令了﹔如果您輸入 !! 再 Enter 的話﹐那就是重複上一個(gè)命令(和按向上方向鍵再 Enter 一樣)﹔如果您輸入 !ls 的話﹐則是最後一次的 ls 開(kāi)頭的命令﹐如果是 !cd 那就是上一個(gè) cd 開(kāi)頭的命令﹐如此類推﹔如果您按著 Ctrl 和 R 兩個(gè)鍵之後﹐然後輸入您以前曾經(jīng)輸入過(guò)的命令﹐那它會(huì)和上面介紹的補(bǔ)全功能一樣﹐將您以前輸入過(guò)的命令補(bǔ)全起來(lái)。呵~~ 太厲害啦﹗

          Bash 會(huì)將您登錄之後的所有命記錄在記 cache 裡面﹐然後﹐只要您成功退出這個(gè) shell 之後﹐那這些記錄就會(huì)存放到家目錄的 ~/.bash_history 這個(gè)檔裡面(小心看﹐它是以 . 開(kāi)頭的檔案哦﹐也就是隱藏檔是也﹐您要用 ls -a 才看得到。) 不過(guò)﹐這個(gè)檔只保持一定數(shù)量的命令記錄而已﹐您可以透過(guò) $HISTFILESIZE 這個(gè)變數(shù)(我們馬上會(huì)介紹變數(shù))﹐來(lái)獲得或改變檔案的記錄數(shù)量。

           

          alias 功能﹕
          在 Linux 裡面﹐您可以透過(guò) alias (別名) 的功能﹐來(lái)定義出一個(gè)命令的預(yù)設(shè)參數(shù)﹐甚至用另外一個(gè)名稱來(lái)簡(jiǎn)化一個(gè)命令(及參數(shù))。如果您輸入 alias 這個(gè)命令﹐您就會(huì)看到目前的 alias 有哪些。您或許會(huì)看到其中有一個(gè)﹕ alias rm='rm -i' 這行﹐它的意思是﹕如果您執(zhí)行 rm 這個(gè)命令﹐那麼系統(tǒng)實(shí)際執(zhí)行的命令會(huì)帶上 -i 的參數(shù)﹐也就是以 interactive 模式進(jìn)行﹐結(jié)果是在您進(jìn)行刪除檔案的時(shí)候﹐會(huì)經(jīng)過(guò)您的確認(rèn)才真正刪除。在某些沒(méi)有這個(gè) alias 的系統(tǒng)中﹐那您執(zhí)行 rm 而不另行指定 -i 的話﹐那就無(wú)聲無(wú)息的將您能砍的檔案給砍掉。小心哦﹐在 Linux 上面﹐檔案一旦刪除就沒(méi)辦法救回了﹗所以﹐用心的系統(tǒng)﹐會(huì)幫您做這個(gè) alias。

           

          在另外一種情形之下﹐當(dāng)您發(fā)現(xiàn)某些長(zhǎng)命令會(huì)經(jīng)常使用到﹐但打字起來(lái)挺麻煩的﹐那您就可以用 alias 來(lái)解決。比方說(shuō)﹐您每次關(guān)機(jī)要輸入的命令是 shutdown -h now 這麼一串﹐那您先輸入 which shd (目的是確定現(xiàn)有的命令名稱)﹐如果您並沒(méi)有發(fā)現(xiàn)這個(gè)命令出現(xiàn)在您的命令路徑之中的話﹐那您可以輸入 alias shd='shutdown -h now'﹐然後再輸入 shd 就可以關(guān)機(jī)了﹗不過(guò)﹐現(xiàn)在不要執(zhí)行它﹗﹗因?yàn)槟@樣真的會(huì)把機(jī)器關(guān)掉哦~~ 請(qǐng)您用 alias 替換其它的長(zhǎng)命令看看﹖

          如果您要取消一個(gè) alias﹐可以使用 unalias 命令﹐如﹕unalias shd 。

          一旦您滿意您的新 alias ﹐那您可以修改您的 ~/.bashrc 這個(gè)檔﹐將它加在其它 alias 命令之後﹔假如您想系統(tǒng)上所有使用者都能使獲得這個(gè) alias ﹐那就將它放到 /etc/bashrc 裡面吧。(如果您目前還不會(huì)編輯檔案﹐那就回到上一章補(bǔ)習(xí) vi 吧:-)

           

          強(qiáng)大的 script 能力
          玩過(guò) DOS 的朋友﹐一定會(huì)知道 batch 檔案的功能﹐在 BASH 本身可以幫您執(zhí)行一系列根據(jù)條件判斷的命令﹐其功能比 DOS 的 batch 強(qiáng)大多了。在本章的後面部份﹐會(huì)詳細(xì)討論 shell script 的基本技巧。
          事實(shí)上﹐bash 還有許多厲害的功能﹐恐怕很難全部介紹了﹐還是留給您自己去找尋了。

           

          環(huán)境變數(shù)

          還記得上一章裡面﹐我曾經(jīng)提到過(guò)﹕當(dāng)我們登入系統(tǒng)的時(shí)候﹐首先就獲得一 shell﹐而且它也佔(zhàn)據(jù)一個(gè)行程﹐然後再輸入的命令都屬於這個(gè) shell 的子程式。如果您學(xué)習(xí)夠細(xì)心﹐不難發(fā)現(xiàn)我們的 shell 都在 /etc/passwd 這個(gè)檔裡面設(shè)定的﹐也就是帳號(hào)設(shè)定的最後一欄﹐預(yù)設(shè)是 /bin/bash 。

          事實(shí)上﹐當(dāng)我們獲得一個(gè) shell 之後﹐我們才真正能和系統(tǒng)溝通﹐例如輸入您的命令﹑執(zhí)行您的程式﹑等等。您也可以在獲得一個(gè) shell 之後﹐再進(jìn)入另外一個(gè) shell (也就是啟動(dòng)一個(gè)子程式)﹐然後還可以再進(jìn)入更深一層的 shell (再進(jìn)入子程式的子程式)﹐直到您輸入 exit 才退回到上一個(gè) shell 裡面(退回上一級(jí)的父程式)。假如您已經(jīng)閱讀過(guò)上一章所說(shuō)過(guò)的子程式概念﹐應(yīng)該不難理解。不過(guò)﹐您的行為也不是無(wú)限制的﹐而且﹐有許多設(shè)定都必須事先得到定義。所以﹐當(dāng)您獲得 shell 的時(shí)候﹐同時(shí)也獲得一些環(huán)境設(shè)定﹐或稱為“環(huán)境變數(shù)( Environment variables)”。

          所謂的 變數(shù)( variable )﹐就是用特定的名稱(或標(biāo)籤)保存一定的設(shè)定值﹐然後供程式將來(lái)使用。例如﹐姓=chen ﹔名=kenny ﹐那麼‘姓’和‘名’就是變數(shù)名稱﹐而 chen 和 kenny 就是變數(shù)所保存的值。由 shell 所定義和管理的變數(shù)﹐我們稱為環(huán)境變數(shù)﹐因?yàn)檫@些變數(shù)可以供 shell 所產(chǎn)生的所有子程式使用。環(huán)境變數(shù)名稱一般都用大寫字母表示﹐例如﹐我們常用的環(huán)境變數(shù)有這些﹕

          變數(shù)名稱 代表意思
          HISTCMD 當(dāng)前命令的記錄號(hào)碼。
          HISTFILE 命令記錄表之存放檔案。
          HISTSIZE 命令記錄表體積。
          HOME 預(yù)設(shè)登錄家目錄。
          IFS 預(yù)設(shè)分隔符號(hào)。
          LINENO 當(dāng)前命令在 shell script 中的行數(shù)。
          MAIL 郵件信箱的路徑。
          MAILCHECK 檢查郵件的秒數(shù)。
          OLDPWD 上次進(jìn)入的目錄路徑。
          OSTYPE 作業(yè)系統(tǒng)類型。
          PATH 預(yù)設(shè)命令搜索路徑。
          PPID 父程式之 PID。
          PWD 當(dāng)前工作目錄路徑。
          SECONDS 當(dāng)前 shell 之持續(xù)啟動(dòng)時(shí)間。
          SHELL 當(dāng)前 shell 之執(zhí)行路徑。
          TMOUT 自動(dòng)登出之最高閑置時(shí)間。
          UID 使用者之 UID。
          $ 當(dāng)前 shell 之 PID。
          最後一個(gè)命令之返回狀態(tài)。

          假如您想看看這些變數(shù)值是什麼﹐只要在變數(shù)名稱前面加上一個(gè)“$”符號(hào)﹐然後用 echo 命令來(lái)查看就可以了﹕
          # echo $PWD
          /root
          # echo $$
          1206
          # echo $?
          0

          第一個(gè)命令就是將當(dāng)前目錄的路徑顯示出來(lái)﹐和您執(zhí)行 pwd 命令的結(jié)果是一樣的﹔第二個(gè)命令將當(dāng)前這個(gè) shell 的 PID 顯示出來(lái)﹐也就是 1206。如果您這時(shí)候輸入 kill -9 1206 的話﹐會(huì)將當(dāng)前的 shell 砍掉﹐那您就要重新登錄才能獲得另外一個(gè) shell﹐而它的 PID 也是新的﹔第三行命令是上一個(gè)命令的返回狀態(tài)﹕如果命令順利執(zhí)行﹐並沒(méi)有錯(cuò)誤﹐那通常是 0﹔如果命令遇到錯(cuò)誤﹐那返回狀態(tài)則是非 0 ﹐其值視程式設(shè)計(jì)者而定(我們?cè)卺崦娴?shell script 的時(shí)候會(huì)介紹)。關(guān)於最後一個(gè)命令﹐不妨比較一下如下結(jié)果﹕
          # ls mbox
          mbox
          # echo $?
          0
          # ls no_mbox
          ls: no_mbox: No such file or directory
          # echo $?
          1

          您會(huì)發(fā)現(xiàn)﹕第一命令成功執(zhí)行﹐所以其返回狀態(tài)是 0 ﹔而第二個(gè)命令執(zhí)行失敗﹐其返回狀態(tài)是 1 。假如程式設(shè)計(jì)者為不同的錯(cuò)誤設(shè)定不同的返回狀態(tài)等級(jí)﹐那您可以根據(jù)返回值推算出問(wèn)題是哪種錯(cuò)誤引起的。

          Tips﹕如果您日後寫程式或 script﹐要養(yǎng)成一個(gè)習(xí)慣﹐為每一種命令結(jié)果設(shè)定返回狀態(tài)。這非常重要﹐尤其在進(jìn)行 debug 的時(shí)候。這個(gè)我們?cè)卺崦鎸W(xué)習(xí) script 的時(shí)候再談。

           

          我們隨時(shí)都可以用一個(gè) = (等號(hào)) 來(lái)定義一個(gè)新的變數(shù)或改變一個(gè)原有變數(shù)。例如﹕
          # MYNAME=kenny
          # echo $MYNAME
          kenny

          假如您要取消一個(gè)定義好的變數(shù)﹐那麼﹐您可以使用 unset 命令﹕
          # unset MYNAME

          不過(guò)﹐環(huán)境變數(shù)的特性之一﹐是單向輸出的。也就是說(shuō)﹕一個(gè) shell 的特定變數(shù)﹐只能在這個(gè) shell 裡面使用。如果您要分享給同一個(gè) shell 裡面的其它程式﹑script﹑命令使用﹐或它們的子程式使用﹐那您必須用 export 命令將這個(gè)變數(shù)進(jìn)行輸出。但無(wú)論如何﹐如果您在一個(gè)子程式中定義了一個(gè)變數(shù)﹐那麼這個(gè)變數(shù)的值﹐只影響這個(gè)子程式本身以及它自己的子程式﹐而永遠(yuǎn)不會(huì)影像到父程式或父程式產(chǎn)生的其它子程式。

          比方說(shuō)﹐您在一個(gè)程式中定義一個(gè)新的變數(shù)﹐或改變一個(gè)原有變數(shù)值﹐在程式結(jié)束的時(shí)候﹐那它所設(shè)定的變數(shù)均被取消﹔如果您想將變數(shù)值分享給該程式所產(chǎn)生的子程式﹐您必須用 export 命令才能保留這個(gè)變數(shù)值﹐除非子程式另外重新定義。但無(wú)論如何﹐當(dāng)前程式所定義的變數(shù)值﹐是無(wú)法傳回父程式那邊的。不妨做做如下的實(shí)驗(yàn)﹕
          # MYNAME=kenny
          # echo $MYNAME
          kenny
          # export MYNAME
          # 設(shè)定一個(gè)變數(shù)。
          #
          # 當(dāng)前的設(shè)定值。
          # 用 export 輸出變數(shù)值。
          # /bin/bash # 再開(kāi)一個(gè) shell﹐也就是進(jìn)入子程式中。
          # echo $MYNAME
          kenny

          #

          # 保留原有設(shè)定值。

          # export MYNAME=netman
          # echo $MYNAME
          netman

          # 重新定義設(shè)定值﹐同時(shí)也用 export 輸出。

          #
          # 變數(shù)值被新值取代。

          # exit

          # 退出子程式﹐返回父程式。

          # echo $MYNAME
          kenny

          #

          # 父程式的變數(shù)值並沒(méi)有改變。

          關(guān)於變數(shù)的另一個(gè)特性﹐是的變數(shù)值是可以繼承的。也就是說(shuō)﹐您可以將一個(gè)變數(shù)值來(lái)設(shè)定另外一個(gè)變數(shù)名稱。比方說(shuō)﹕

          # FIRST_NAME="Kenny"
          # MYNAME=$FIRST_NAME

          # echo $MYNAME
          Kenny

          # 定義一個(gè)變數(shù)。

          # 再定義另一個(gè)變數(shù)﹐但它的值是第一個(gè)變數(shù)。

          #
          # 第二個(gè)變數(shù)繼承了第一個(gè)變數(shù)的值。

          另外﹐在定義變數(shù)的時(shí)候您還要注意一些規(guī)則﹕

          • 定義變數(shù)時(shí)﹐“=”號(hào)兩邊沒(méi)有空白鍵﹔
          • 作為變數(shù)的名稱﹐只能是字母和數(shù)字﹐但不能以數(shù)字開(kāi)頭﹔如果名稱太長(zhǎng)﹐可以用“_”分隔﹔
          • 預(yù)先定義的變數(shù)均為大寫﹐自定義變數(shù)可以混合大小寫﹐以更好區(qū)別﹔
          • 只有 Shell 本身定義的變數(shù)才能稱為環(huán)境變數(shù);
          • 如果變數(shù)中帶有特殊字符﹐必須先行用“\”符號(hào)跳脫﹔
          • 如果變數(shù)中帶有空白﹐必須使用引號(hào)﹐或進(jìn)行跳脫。

           

          關(guān)於後兩項(xiàng)﹐或許我們?cè)僬倚├觼?lái)體會(huì)一下﹕

          # TOPIC='Q & A'

          # 用單引號(hào)保留特殊符號(hào)和空白

           

          # Q1=What\'s\ your\ \"topic\"\?

          # echo $Q1
          What's your "topic"?

           

          # 用 \ 將特殊符號(hào)(含引號(hào))和空白跳脫出來(lái)

          #

          # 跳脫後﹐特殊符號(hào)和空白都保留下來(lái)。

           

          # ANS="It is $TOPIC."

          # echo $ANS
          It is Q & A.

           

          # 用雙引號(hào)保留變數(shù)值($)

          #

          # 用雙引號(hào)﹐顯示出變數(shù)值。

           

          # WRONG_ANS='It is "$TOPIC".'

          # echo $WRONG_ANS
          It is "$TOPIC".

           

           

          # 用單引號(hào)保留特殊符號(hào)和空白(同第一行)

          #
          # 用單引號(hào)﹐全部保留﹔同時(shí)﹕

          # $ 也當(dāng)成一般符號(hào)保留﹐而非變數(shù)值。

           

          # ALT_ANS='the $TOPIC'\ is\ "'$TOPIC'"\.

          # echo $ALT_ANS
          The $TOPIC is 'Q & A'.

           

          # 同時(shí)混合單引號(hào)﹑雙引號(hào)﹑和跳脫字符 \

          #

          # 單引號(hào)保留全部﹔雙引號(hào)保留變數(shù)值﹔
          # \ 將特殊符號(hào)跳脫出來(lái)。

          我這裡解釋一下最後面的例子好了﹕'the $TOPIC is '"$TOPIC"\.。首先用單引號(hào)將 'the $TOPIC is ' 這段文字括好﹐其中用 3 個(gè)空白鍵和一個(gè) $ 符號(hào)﹔然後用雙引號(hào)保留 $TOPIC 的變數(shù)值﹔最後用 \ 跳脫小數(shù)點(diǎn)。

          在引用 " " 和 ' ' 符號(hào)的時(shí)候﹐基本上﹐ ' ' 所包括的內(nèi)容﹐會(huì)變成單一的字串﹐任何特殊字符都失去其特殊的功能﹐而變成一般字符而已﹐但其中不能再使用 ' 符號(hào)﹐而在 " " 中間﹐則沒(méi)有 ' ' 那麼嚴(yán)格﹐某些特殊字符﹐例如 $ 號(hào)﹐仍然保留著它特殊的功能。您不妨實(shí)作一下﹐比較看看 echo ' "$HOME" ' 和 echo " '$HOME' " 的差別。

          Tips﹕在 shell 命令行的跳脫字符“ \ ”其實(shí)我們會(huì)經(jīng)常用到的。例如﹐您的一個(gè)命令太長(zhǎng)﹐一直打下去可能超過(guò)一行﹐或是想要整潔的輸入命令行﹐您或許想按 Enter 鍵敲到下一行繼續(xù)輸入。但是﹐當(dāng)您敲 Enter 鍵的時(shí)候﹐事實(shí)上是輸入一個(gè) CR (Carriage-Return) 字符﹐一但 shell 讀到 CR 字符﹐就會(huì)嘗試執(zhí)行這個(gè)命令。這時(shí)﹐您就可以在輸入 Enter 之前先輸入 \ 符號(hào)﹐就能將 CR 字符也跳脫出來(lái)﹐這樣 shell 就不會(huì)馬上執(zhí)行命令了。這樣的命令行﹐我們?cè)?script 中經(jīng)常看到﹐但您必須知道那代表什麼意思。

           

          如果﹐您想對(duì)一些變數(shù)值進(jìn)行過(guò)濾﹐例如﹕MY_FILE=' ~/tmp/test.sh' ﹐而您想將變數(shù)值換成 test.sh (也就是將前面的路徑去掉)﹐那您可以將 $MY_FILE 換成 ${MY_FILE##*/}。這是一個(gè)變數(shù)值字串過(guò)濾﹕## 是用來(lái)比對(duì)變數(shù)前端部份﹐然後 */ 是比對(duì)的色樣 (也就是任何字母到 / 之間)﹐然後將最長(zhǎng)的部份刪除掉。您可以參考如下範(fàn)例﹕

          當(dāng) FNAME="/home/kenny/tmp/test.1.sh" 的時(shí)候﹕
          變數(shù)名稱 代表意思 結(jié)果
          ${FNAME} 顯示變數(shù)值的全部。

          /home/kenny/tmp/test.1.sh
          ${FNAME##/*/} 比對(duì)變數(shù)值開(kāi)端﹐如果以 /*/ 開(kāi)頭的話﹐砍掉最長(zhǎng)的部份。

          test.1.sh
          ${FNAME#/*/} 比對(duì)變數(shù)值開(kāi)端﹐如果以 /*/ 開(kāi)頭的話﹐砍掉最短的部份。

          kenny/tmp/test.1.sh
          ${FNAME%.*} 比對(duì)變數(shù)值末端﹐如果以 .* 結(jié)尾的話﹐砍掉最短的部份。

          /home/kenny/tmp/test.1
          ${FNAME%%.*} 比對(duì)變數(shù)值末端﹐如果以 .* 結(jié)尾的話﹐砍掉最長(zhǎng)的部份。

          /home/kenny/tmp/test
          ${FNAME/sh/bash} 如果在變數(shù)值中找到 sh 的話﹐將第一個(gè) sh 換成 bash。

          /home/kenny/tmp/test.1.bash
          ${FNAME//sh/bash} 如果在變數(shù)值中找到 sh 的話﹐將全部 sh 換成 bash。

          /home/kenny/tmp/test.1.bash

          您除了能夠?qū)ψ償?shù)進(jìn)行過(guò)濾之外﹐您也能對(duì)變數(shù)做出限制﹑和改變其變數(shù)值﹕

            字串沒(méi)設(shè)定 空字串 非空字串
          使用預(yù)設(shè)值
          var=${str-expr} var=expr var= var=$str
          var=${str:-expr} var=expr var=expr var=$str
          使用其它值
          var=${str+expr} var=expr var=expr var=expr
          var=${str:+expr} var=expr var= var=expr
          設(shè)定預(yù)設(shè)值
          var=${str=expr} str=expr

          var=expr

          str 不變

          var=

          str 不變

          var=$str

          var=${str:=expr} str=expr

          var=expr

          str=expr

          var=expr

          str 不變

          var=$str

          輸出錯(cuò)誤
          var=${str?expr} expr 輸出至 stderr  var= var=str
          var=${str:?expr} expr 輸出至 stderr  expr 輸出至 stderr var=str

          一開(kāi)始或許比較難理解上面的兩個(gè)表格說(shuō)明的意思﹐真的很混亂~~ 但只要多做一些練習(xí)﹐那您就知道怎麼使用了。比方說(shuō)﹕
          # expr=EXPR
          # unset str
          # var=${str=expr}; echo var=$var str=$str expr=$expr
          var=expr str=expr expr=EXPR
          # var=${str:=expr}; echo var=$var str=$str expr=$expr
          var=expr str=expr expr=EXPR
          # str=
          # var=${str=expr}; echo var=$var str=$str expr=$expr
          var= str= expr=EXPR
          # var=${str:=expr}; echo var=$var str=$str expr=$expr
          var=expr str=expr expr=EXPR
          # str=STR
          # var=${str=expr}; echo var=$var str=$str expr=$expr
          var=STR str=STR expr=EXPR
          # var=${str:=expr}; echo var=$var str=$str expr=$expr
          var=STR str=STR expr=EXPR

          # MYSTRING=test
          # echo ${MYSTRING?string not set\!}
          test
          # MYSTRING=
          # echo ${MYSTRING?string not set\!}
           
          # unset MYSTRING
          # echo ${MYSTRING?string not set\!}
          bash: MYSTRING: string not set!

          請(qǐng)記住這些變數(shù)的習(xí)性﹐日後您要寫 shell script 的時(shí)候就不會(huì)將變數(shù)搞混亂了。假如您想看看當(dāng)前 shell 的環(huán)境變數(shù)有哪些﹐您可以輸入 set 命令﹔如果只想檢查 export 出來(lái)的變數(shù)﹐可以輸入 exportenv (前者是 shell 預(yù)設(shè)的輸出變數(shù))。

          Bash 設(shè)定

          到這裡﹐您或許會(huì)問(wèn)﹕shell 的環(huán)境變數(shù)在哪裡定義呢﹖可以調(diào)整嗎﹖

          嗯﹐第一個(gè)問(wèn)題我不大了解﹐我猜那是 shell 設(shè)計(jì)者預(yù)設(shè)定義好的﹐我們一登錄獲得 shell 之後就有了。不過(guò)﹐第二個(gè)問(wèn)題﹐我卻可以肯定答復(fù)您﹕您可以隨時(shí)調(diào)整您的環(huán)境變數(shù)。您可以在進(jìn)入 shell 之後用在命令行裡面重新定義﹐也可以透過(guò)一些 shell 設(shè)定檔來(lái)設(shè)定。

          先讓我們看看﹐當(dāng)您在進(jìn)行登錄的時(shí)候﹐系統(tǒng)會(huì)檢查哪些檔案吧﹕

          1. /etc/profile﹕首先﹐系統(tǒng)會(huì)檢查這個(gè)檔﹐以定義如下這些變數(shù)﹕PATH﹑USER﹑LOGNAME﹑MAIL﹑HOSTNAME﹑HISTSIZE﹑INPUTRC。如果您會(huì) shell script (我們後面再討論)﹐那您應(yīng)該看得出這些變數(shù)是如何定義的。另外﹐還指定了 umask 和 ulimit 的設(shè)定﹕umask 大家應(yīng)該知道了﹐而 ulmimit 呢﹖它是用來(lái)限制一個(gè) shell 做能建立的行程數(shù)目﹐以避免系統(tǒng)資源被無(wú)限制的消耗。最後﹐它還會(huì)檢查並執(zhí)行 /etc/profile.d/*.sh 那些 script﹐有興趣您可以追蹤看看。

             

             

          2. ~/.bash_profile﹕這裡會(huì)定義好 USERNAME﹑BASH_ENV﹑PATH。其中的 PATH 除了現(xiàn)有的 $PATH 之外﹐還會(huì)再加入使用者相關(guān)的路徑﹐您會(huì)發(fā)現(xiàn) root 和普通帳號(hào)的路徑是不一樣的﹔而 BASH_ENV 呢﹐仔細(xì)點(diǎn)看﹐是下一個(gè)要檢查的檔案﹕

             

             

          3. ~/.bashrc﹕在這個(gè)檔裡面﹐您可以發(fā)現(xiàn)一些 alias 設(shè)定(哦~~ 原來(lái)在這裡﹗)。然後﹐您會(huì)發(fā)現(xiàn)有一行﹕. /etc/bashrc 。在 shell script 中﹐用一個(gè)小數(shù)點(diǎn)然後然後一個(gè)空白鍵再指向另外一個(gè) script﹐意思是同時(shí)執(zhí)行那個(gè) script 並採(cǎi)用那裡的變數(shù)設(shè)定。

             

             

          4. /etc/bashrc﹕基本上﹐這裡的設(shè)定﹐是所有使用者在獲得 shell 的時(shí)候都會(huì)採(cǎi)用的。這裡指定了一些 terminal 設(shè)定﹐以及 shell 提示字符等等。

             

             

          5. ~/.bash_login﹕如果 ~/.bash_profile 不存在﹐則使用這個(gè)檔。

             

             

          6. ~/.profile﹕如果 ~/.bash_profile 和 ~/.bash_login 都不存在﹐則使用這個(gè)檔。

             

             

          7. ~/.bash_logout﹕這個(gè)檔通常只有一個(gè)命令﹕clear﹐也就是把熒幕顯示的內(nèi)容清掉。如果您想要在登出 shell 的時(shí)候﹐會(huì)執(zhí)行一些動(dòng)作﹐例如﹕清空臨時(shí)檔(假如您有使用到臨時(shí)檔)﹑還原某些設(shè)定﹑或是執(zhí)行某些備份之類的。

           

          您可以透過(guò)修改上面提到的檔案﹐來(lái)調(diào)整您進(jìn)入 shell 之後的變數(shù)值。一般使用者可以修改其家目錄( ~/ )中的檔案﹐以進(jìn)行個(gè)人化的設(shè)定﹔而作為 root﹐您可以修改 /etc/下面的檔案﹐設(shè)定大家共用的變數(shù)值。至於 bash 的變數(shù)值如何設(shè)定﹖有哪些變數(shù)﹖各變數(shù)的功能如何﹖您打可以執(zhí)行 man bash 參考手冊(cè)資料。

          Tips﹕一旦您修改了 /etc/profile 或 ~/.bash_profile 檔案﹐其新設(shè)定要在下次登錄的時(shí)候才生效。如果您不想退出﹐又想使用新設(shè)定﹐那可以用 source 命令來(lái)抓取﹕
          source ~/.bash_profile

           

          命令重導(dǎo)向

          好了﹐相信您已經(jīng)對(duì)您的 shell 有一定的了解了。然後﹐讓我們看看 shell 上面的一些命令功能吧﹐這些技巧都是作為一個(gè)系統(tǒng)管理員基本要素。其中之一就是﹕命令重導(dǎo)向 (command redirection) 和 命令管線 (command pipe) 。

          在深入講解這兩個(gè)技巧之前﹐先讓我們了解一下 shell 命令的基本概念﹕

          名稱 代號(hào) 代表意思 設(shè)備
          STDIN 0 標(biāo)準(zhǔn)輸入 鍵盤
          STDOUT 1 標(biāo)準(zhǔn)輸出 熒幕
          STDERR 2 標(biāo)準(zhǔn)錯(cuò)誤 熒幕

          表格中分別是我們?cè)?shell 中一個(gè)命令的標(biāo)準(zhǔn) I/O (輸出與輸入)。當(dāng)我們執(zhí)行一個(gè)命令的時(shí)候﹐先讀入輸入 (STDIN)﹐然後進(jìn)行處理﹐最後將結(jié)果進(jìn)行輸出 (STDOUT)﹔如果處理過(guò)程中遇到錯(cuò)誤﹐那麼命令也會(huì)顯示錯(cuò)誤 (STDERR)。我們可以很容易發(fā)現(xiàn)﹕一般的標(biāo)準(zhǔn)輸入﹐都是從我們的鍵盤讀取﹔而標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤﹐都從我們的銀幕顯示。

          同時(shí)﹐在系統(tǒng)上﹐我們通常用號(hào)碼來(lái)代表各不同的 I/O﹕STDIN 是 0﹑STDOUT 是 1﹑STDERR 是 2。

          當(dāng)您了解各個(gè) I/O 的意思和所代表號(hào)碼之後﹐讓我們看比較如下命令的結(jié)果﹕
          # ls mbox
          mbox
          # ls mbox 1> file.stdout

          請(qǐng)小心看第二個(gè)命令﹕在命令的後面多了一個(gè) 1 ﹐而緊接著(沒(méi)有空白﹗)是一個(gè)大於符號(hào) (>)﹐然後是另外一個(gè)檔案名稱。但是﹐熒幕上卻沒(méi)有顯示命令的執(zhí)行結(jié)果﹐也就是說(shuō)﹕ STDOUT 不見(jiàn)了﹗那到底發(fā)生什麼事情了呢﹖

          呵﹐相信您不會(huì)這麼快忘記了 STDOUT 的代號(hào)是 1 吧﹗沒(méi)錯(cuò)了﹐因?yàn)槲覀冞@裡將 1 用一個(gè) > 符號(hào)重導(dǎo)到一個(gè)檔案中了。結(jié)果過(guò)是﹕我們將標(biāo)準(zhǔn)輸出從熒幕改變到檔案中﹐所以我們?cè)阢y幕就看不到 STDOUT﹐而原先的 STDOUT 結(jié)果則保存在大於符號(hào)右邊的檔中了。不信﹐您看看這個(gè)檔案的內(nèi)容就知道了﹕
          # cat file.stdout
          mbox

          當(dāng)我們用一個(gè) > 將命令的 STDOUT 導(dǎo)向到一個(gè)檔案的時(shí)候﹐如果檔案不存在﹐則會(huì)建立一個(gè)新檔﹔如果檔案已經(jīng)存在﹐那麼﹐這個(gè)檔案的內(nèi)容就換成 STDOUT 的結(jié)果。有時(shí)候﹐您或許想保留原有檔案的內(nèi)容﹐而將結(jié)果增加在檔案末端而已。那您可以多加一個(gè) >﹐也就是使用 >> 就是了。您可以自己玩玩看哦~~﹐通常﹐我們要將一些命令或錯(cuò)誤記錄下來(lái)﹐都用這個(gè)方法。

          Tips﹕如果您不希望 > 意外的蓋掉一個(gè)原有檔﹐那您可以執(zhí)行這個(gè)命令﹕
          set -o noclobber

           

          不過(guò)﹐仍可以用 >| 來(lái)強(qiáng)迫寫入。

           

          上前面的例子中﹐我們指定了 I/O 1 (STDOUT) 進(jìn)行重導(dǎo)向﹐這也是預(yù)設(shè)值﹐如果您沒(méi)有指定代號(hào)﹐那麼就是進(jìn)行 STDOUT 的重導(dǎo)向﹐所以 1> 和 > 是一樣的﹔1>> 和 >> 也是一樣的。但如果您使用了數(shù)字﹐那麼數(shù)字和 > 之間一定不能有空白存在。

          好了﹐下面再比較兩個(gè)命令﹕
          # ls no_mbox
          ls: no_mbox: No such file or directory
          # ls no_mbox 2>> file.stderr
           

          嗯﹐相信不用我多解釋了吧﹖(如果檔案不存在﹐>> 和 > 都會(huì)建立新的。)

          事實(shí)上﹐在我們的日常管理中﹐重導(dǎo)向的應(yīng)用是非常普遍的。我只舉下面這個(gè)例子就好了﹕

          當(dāng)我們進(jìn)行核心編譯的時(shí)候(我們下一章再介紹)﹐熒幕會(huì)飛快的顯示出成千上萬(wàn)行信息﹔其中有大部份是 STDOUT﹐但也有些是 STDERR。除非您的眼睛真的那麼厲害﹐否則您很難分辯出哪些是正常信息﹐哪些是錯(cuò)誤信息。當(dāng)您要編譯失敗﹐嘗試找錯(cuò)誤的時(shí)候﹐如果已經(jīng)將 STDERR 重導(dǎo)出來(lái)﹐就非常方便了﹕
          # make dep clean bzImage modules 1>/dev/null 2>/tmp/kernel.err &

          這裡﹐我一共有三個(gè)打算﹕(1) 將標(biāo)準(zhǔn)輸出送到一個(gè)叫 null 的設(shè)備上﹐如果您記性夠好﹐我在前面的文章中曾比喻它為黑洞﹕所有東西進(jìn)去之後都會(huì)消失掉。憑我個(gè)人的習(xí)慣﹐我會(huì)覺(jué)得編譯核心時(shí)跑出來(lái)的信息﹐如果您不感興趣的話﹐那都是垃圾﹐所以我將 STDOUT 給重導(dǎo)到 null 去﹐眼不見(jiàn)為乾淨(jìng)﹔ (2) 然後﹐我將 STDERR 重導(dǎo)到 /tmp/kernel.err 這個(gè)檔去﹐等命令結(jié)束後﹐我就可以到那裡看看究竟有部份有問(wèn)題。有些問(wèn)題可能不是很重要﹐有些則可能需要重新再編核心﹐看您經(jīng)驗(yàn)啦。(3) 最後﹐我將命令送到 background 中執(zhí)行 (呵~~ 相信您還沒(méi)忘記吧﹗)。因?yàn)椹o編譯核心都比較花時(shí)間﹐所以我將之送到背景去﹐這樣我可以繼續(xù)做其它事情。

          Tips﹕這時(shí)﹐因?yàn)橄到y(tǒng)太忙了﹐可能反應(yīng)速度上會(huì)比較慢些﹐如果您真的很在意﹐不妨考慮把 make 的 nice level 提高。(忘記怎麼做了﹖那翻看前一章吧)

           

          前面的例子﹐我們是分開(kāi)將 STDOUT 和 STDERR 重導(dǎo)到不同的檔案去﹐那麼﹐我們能否把兩者都重導(dǎo)到同一個(gè)檔呢﹖當(dāng)然是可以的﹐請(qǐng)比較下面三行﹕
          # make dep clean bzImage modules >/tmp/kernel.result 2>/tmp/kernel.result
          # make dep clean bzImage modules >/tmp/kernel.result 2>&1
          # make dep clean bzImage modules &>/tmp/kernel.resultt

          我這裡告訴您﹕第一行的命令不怎麼正確﹐因?yàn)檫@樣會(huì)造成這兩個(gè)輸出同時(shí)在‘搶’一個(gè)檔案﹐寫入的順序很難控制。而第 2 行和第 3 行的結(jié)果都是一樣的﹐看您喜歡用哪個(gè)格式了。不過(guò)﹐要小心的是﹕& 符號(hào)後面不能有空白鍵﹐否則會(huì)當(dāng)成將命令送到背景執(zhí)行﹐而不是將 STDOUT 和 STDERR 整合。

          好了﹐前面我們都在談 STDOUT 和 STDERR 的重導(dǎo)向﹐那麼﹐我們是否能重導(dǎo) STDIN 呢﹖

          當(dāng)然可以啦~~~

          有些命令﹐當(dāng)我們執(zhí)行之後﹐它會(huì)停在那裡等待鍵盤的 STDIN 輸入﹐直到遇到 EOF (Ctrl+D) 標(biāo)籤才會(huì)真正結(jié)束命令。比方說(shuō)﹐在同一個(gè)系統(tǒng)上﹐如果有多位使用者同時(shí)登入的話﹐您可以用 write 命令向特的使用者送出短訊。而短訊的內(nèi)容就是鍵盤敲入的文字﹐這時(shí)候命令會(huì)進(jìn)入輸入模式﹐您每輸入一行並按 Enter 之後﹐那麼訊息就會(huì)在另外一端﹐直到您按 Ctrl+D 鍵才離開(kāi)並結(jié)束命令。
          # write user1
          Hello!
          It is me... ^_^
          How r u!
          (Ctrl+D)

          這樣通常都需要花一些時(shí)間輸入﹐假如對(duì)方在寫什麼東西和查看某些資料的時(shí)候﹐就很混亂。這時(shí)候﹐您或許可以先將短訊的內(nèi)容寫在一個(gè)檔案裡面﹐例如 greeting.msg﹐然後這樣輸入就可以了﹕
          write user1 < greeting.msg

          就這樣﹐這裡我們用小於符號(hào) (<) 來(lái)重導(dǎo) STDIN 。簡(jiǎn)單吧﹖^_^

          不過(guò)﹐我們用 cat 命令建立簡(jiǎn)單的檔案的時(shí)候﹐卻是使用 > 符號(hào)的﹕
          cat > file.tmp

           

          等您按 Ctrl+D 之後﹐從鍵盤輸入的 STDIN﹐就保存在 file.tmp 中了。請(qǐng)想想看為什麼會(huì)如此﹖(我在 LPI 的考試中碰到過(guò)這道題目哦~~~)

          pipe

          查字典﹐pipe 這個(gè)英文是水管﹑管道﹑管線的意思。那麼﹐它和命令又有什麼牽連呢﹖簡(jiǎn)單的說(shuō)﹐一個(gè)命令管線﹐就是將一個(gè)命令的 STDOUT 作為另一個(gè)命令的 STDIN 。

          其實(shí)﹐這樣的例子我們前面已經(jīng)碰到多次了﹐例如上一章介紹 tr 命令的時(shí)候﹕
          # cat /path/to/old_file | tr -d '\r' > /path/to/new_file

          上面這個(gè)命令行﹐事實(shí)上有兩個(gè)命令﹕cat 和 tr ﹐在這兩個(gè)命令之間﹐我們用一個(gè) “ | ”符號(hào)作為這兩個(gè)命令的管線﹐也就是將 cat 命令的 STDOUT 作為 tr 命令的 STDIN ﹔然後﹐tr 命令的 STDOUT 用 > 重導(dǎo)到另外一個(gè)檔案去。

          上面只是一個(gè)非常簡(jiǎn)單的例子而已﹐事實(shí)上﹐我們可以用多個(gè)管線連接多個(gè)程式﹐最終獲得我們確切想要的結(jié)果。比方說(shuō)﹕我想知道目前有多少人登錄在系統(tǒng)上面﹕
          # w | tail +3 | wc -l

          我們不妨解讀一下這個(gè)命令行﹕(1) w 命令會(huì)顯示出當(dāng)前登錄者的資源使用情況﹐並且每一個(gè)登錄者佔(zhàn)一行﹔(2) 再用 tail 命令抓取第 3 行開(kāi)始的字行﹔(3) 然後用 wc -l 計(jì)算出行數(shù)。這樣﹐就可以知道當(dāng)前的登錄人數(shù)了。

          許多朋友目前都採(cǎi)用撥接 ADSL 上網(wǎng)﹐每次連線的 IP 都未必一樣﹐只要透過(guò)簡(jiǎn)單的命令管線﹐您就可以將當(dāng)前的 IP 抓出來(lái)了﹕

          1. 我們不妨觀察 ifconfig ppp0 這個(gè)命令的輸出結(jié)果﹕
            # ifconfig ppp0
                                                                                ppp0      Link encap:Point-to-Point Protocol
                                                                                inet addr:211.74.48.254  P-t-P:211.74.48.1  Mask:255.255.255.255
                                                                                UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1492  Metric:1
                                                                                RX packets:5 errors:0 dropped:0 overruns:0 frame:0
                                                                                TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
                                                                                collisions:0 txqueuelen:3
                                                                                

             

             

          2. 不難發(fā)現(xiàn) IP 位址所在的句子中有著其它句子所沒(méi)有的字眼﹕inet addr 。然後﹐我們就可用 grep 把這行抓出來(lái)﹕
            # ifconfig ppp0 | grep "inet addr"
                                                                                inet addr:211.74.48.254  P-t-P:211.74.48.1  Mask:255.255.255.255
                                                                                

             

             

          3. 再來(lái)﹐我們先用相同的分隔符號(hào)將句子分成數(shù)列﹐然後抓出 IP 位址所在的那列。

             

            嗯﹐這裡﹐我們可以用“ : ”來(lái)分出 4 列﹔也可以用空白鍵來(lái)分出 5 列(空因?yàn)榫渥娱_(kāi)首就是一個(gè)空白鍵)。如果用空白鍵來(lái)分的話﹐由於有些間隔有多個(gè)空白鍵的原因﹐那麼﹐我們可以用 tr 命令﹐將多個(gè)空白鍵集合成一個(gè)空白鍵﹕
            # ifconfig ppp0 | grep "inet addr" | tr -s ' ' ' '
                                                                                inet addr:211.74.48.254 P-t-P:211.74.48.1 Mask:255.255.255.255
                                                                                
            (注意﹕在 ' ' 之間是一個(gè)空白鍵﹗)

             

          4. 然後用 cut 命令抓出 IP 所在的列﹐細(xì)心數(shù)一數(shù)﹐應(yīng)該是第 3 列﹕
            # ifconfig ppp0 | grep "inet addr" | tr -s ' ' ' ' | cut -d ' ' -f3
                                                                                addr:211.74.48.254
                                                                                

             

             

          5. 然後我們用“ : ”再分兩列﹐抓第 2 列就是 IP 了﹕
            # ifconfig ppp0 | grep "inet addr" | tr -s ' ' ' ' \
                                                                                | cut -d ' ' -f3 | cut -d ':' -f2
                                                                                211.74.48.254
                                                                                

           

          這裡﹐我們一共用 5 個(gè) pipe 將 4 個(gè)命令連接起來(lái)﹐就抓出機(jī)器當(dāng)前的 IP 位址了。是否很好用呢﹖

          在同一個(gè)命令行裡面出現(xiàn)多個(gè)命令的情形﹐除了 “ | ”之外﹐或許您會(huì)看到 " ` ` " 符號(hào)﹐也就是和 ~ 鍵同一個(gè)鍵的符號(hào)(不用按 Shift )。它必須是一對(duì)使用的﹐其中可以包括單一命令﹐或命令管線。那它的效果和命令管線又有什麼分別呢﹖

          我們使用 pipe 將一個(gè)命令的 STDOUT 傳給下一個(gè)命令的 STDIN﹐但使用 `` 的時(shí)候﹐它所產(chǎn)生的 STDOUT 或 STDERR 僅作為命令行中的一個(gè)參數(shù)而已。嗯﹐不如看看下面命令好了﹕
          # TODAY=`date +%D`
          # echo Today is $TODAY.
          Today is 08/17/01.

          從結(jié)果我們可以看出﹐我們用 `` 將 date 這個(gè)命令括起來(lái)(可含參數(shù))﹐那麼它的執(zhí)行結(jié)果可以作為 TODAY 的變數(shù)值。我們甚至還可以將一串命令管線直接用在命令行上面﹕
          # echo My IP is `ifconfig ppp0 | grep "inet addr" \
              | tr -s ' ' ' ' | cut -d ' ' -f3 | cut -d ':' -f2`

          My IP is 211.74.48.254.

          註意﹕第一行的 CR 被 \ 跳脫了﹐所以這個(gè)命令行‘看起來(lái)’有兩行。我之所以弄這麼複雜﹐是告訴您這對(duì) `` 符號(hào)可以適用的範(fàn)圍。

          Tips﹕在變數(shù)中使用 `` 可以將命令的執(zhí)行結(jié)果當(dāng)成變數(shù)值的部份。事實(shí)上﹐除了用 `` 之外﹐您也可以用這樣的格式﹕VAR_NAME=$(command) ﹐那是和 VAR_NAME=`command` 的結(jié)果是一樣的。

           

          除了這對(duì) `` 和 | 之外﹐還有另外一個(gè)符號(hào) “ ; ”來(lái)分隔命令的。不過(guò)﹐這個(gè)比較簡(jiǎn)單﹕就是當(dāng)?shù)谝幻罱Y(jié)束之後﹐再執(zhí)行第二個(gè)命令﹐如此類推﹕
          # ./configure; make; make install

          呵~~ 如果您對(duì)您的安裝程式有絕對(duì)信心﹐用上面一行命令就夠了﹗

          Shell Script

          當(dāng)我們對(duì) shell 變數(shù)和命令行有一定認(rèn)識(shí)之後﹐那麼﹐我們就可以嘗試寫自己的 shell script 囉~~ 這可是非常好玩而又有成就感的事情呢﹗^_^

          在 linux 裡面的 shell script 可真是無(wú)處不在﹕我們開(kāi)機(jī)執(zhí)行的 run level 基本上都是一些 script ﹔登錄之後的環(huán)境設(shè)定﹐也是些 script ﹔甚至工作排程和記錄維護(hù)也都是 script 。您不妨隨便到 /etc/rc.d/init.d 裡面抓兩個(gè)程式回來(lái)看看﹐不難發(fā)現(xiàn)它們都有一個(gè)共同的之處﹕第一行一定是如下這樣的﹕
          #!/bin/sh
                                                                          或﹕
                                                                          #!/bin/bash
                                                                          

          其實(shí)﹐這裡的 #! 後面要定義的就是命令的解釋器(command interpreter)﹐如果是 /bin/bash 的話﹐那下面的句子就都用 bash 來(lái)解釋﹔如果是 /usr/bin/perl 的話﹐那就用 perl 來(lái)解釋。不同的解釋器所使用的句子語(yǔ)法都不一樣﹐非常嚴(yán)格﹐就算同是用 shell 來(lái)解釋﹐不同的 shell 之間的格式也不僅相同。所以﹐如果您看到 script 的解釋器是 /bin/sh 的話﹐那就要小心了﹕如果您仔細(xì)看這個(gè)檔案﹐事實(shí)上它僅是一個(gè) link 而已﹐有些系統(tǒng)或許會(huì)將它 link 到其它 shell 去。假如您的 script 句子使用的語(yǔ)法是 bash 的話﹐而這個(gè) sh 卻 link 到 csh ﹐那執(zhí)行起來(lái)可能會(huì)有問(wèn)題。所以﹐最好還是直接指定 shell 的路徑比較安全一些﹕在這裡的範(fàn)例都使用 /bin/bash 來(lái)作為 script 的解釋器。

          在真正開(kāi)始寫 script 之前﹐先讓我們認(rèn)識(shí) script 的一些基本概念﹕

          簡(jiǎn)單來(lái)說(shuō)﹐shell script 裡面就是一連串命令行而已﹐再加上條件判斷﹑流程控制﹑迴圈﹑等技巧﹐聰明地執(zhí)行正確的命令和使用正確的參數(shù)選項(xiàng)。和我們?cè)?shell 裡面輸入命令一樣﹐shell script 也有這樣的特性﹕

          • 當(dāng)讀到一個(gè) CR 字符的時(shí)候﹐就嘗試執(zhí)行該行命令﹔
          • 它會(huì)忽略空白行﹔句子前面的空白和 tab 也不理會(huì)﹔
          • CR 字符也同樣可以用“ \ ”符號(hào)跳脫﹔
          • 另外﹐“ # ”符號(hào)是註解符號(hào)﹐從這個(gè)符號(hào)至句子末端的內(nèi)容全被忽略﹐程式本身不會(huì)讀入這部份﹐但我們經(jīng)常用來(lái)給使用者閱讀﹐因而名為註解﹔
          • 等等。

           

          一個(gè)良好的 script 作者﹐在程式開(kāi)頭的時(shí)候﹐都會(huì)用註解說(shuō)明 script 的名稱﹑用途﹑作者﹑日期﹑版本﹑等信息。如果您有這個(gè)機(jī)會(huì)寫自己的 script﹐也應(yīng)該有這個(gè)良好習(xí)慣。

          shell script 檔的命名沒(méi)一定規(guī)則﹐可以使用任何檔案名稱(參考檔案系統(tǒng))﹐但如果您喜歡的話﹐可以用 .sh 來(lái)做它的副檔名﹐不過(guò)這不是硬性規(guī)定的。不過(guò)﹐要執(zhí)行一個(gè) shell script﹐使用者必須對(duì)它有執(zhí)行權(quán)限( x )﹐用文件編輯器新建立的檔案都是沒(méi)有 x permission 的﹐請(qǐng)用 chmod 命令加上。執(zhí)行的時(shí)候﹐除非該 script 已經(jīng)至於 PATH 環(huán)境變數(shù)之內(nèi)的路徑內(nèi)﹐否則您必須指定路徑。例如﹐您寫了一個(gè)叫 test.sh 的 shell script﹐放在家目錄內(nèi)﹐假設(shè)這也是您的當(dāng)前工作目錄﹐您必須加上路徑才能執(zhí)行﹕./test.sh 或 ~/test.sh 。所以﹐建議您在 script 測(cè)試無(wú)誤之後﹐放在 ~/bin 目錄裡面﹐那就可以在任何地方執(zhí)行自己的 script 了﹐當(dāng)然﹐您要確定 ~/bin 已經(jīng)出現(xiàn)在您的 PATH 變數(shù)裡面。

          script 之所以聰明﹐在於它能夠?qū)σ恍l件進(jìn)行測(cè)試( test )。您可以直接用 test 命令﹐也可以用 if 敘述﹐例如﹕test -f ~/test.sh 。它的意思是測(cè)試一下 ~/test.sh 這個(gè)檔案是否存在﹐這個(gè) -f 通常用在檔案上面的測(cè)試﹐除了它﹐還有很多﹕

          標(biāo)籤 代表意思
          -G 存在﹐並且由 GID 所執(zhí)行的行程所擁有。
          -L 存在﹐並且是 symbolic link 。
          -O 存在﹐並且由 UID 所執(zhí)行的行程所擁有。
          -S 存在﹐並且是一個(gè) socke 。
          -b 存在﹐並且是 block 檔案﹐例如磁碟等。
          -c 存在﹐並且是 character 檔案﹐例如終端或磁帶機(jī)。
          -d 存在﹐並且是一個(gè)目錄。
          -e 存在。
          -f 存在﹐並且是一個(gè)檔案。
          -g 存在﹐並且有 SGID 屬性。
          -k 存在﹐並且有 sticky bit 屬性。
          -p 存在﹐並且是用於行程間傳送資訊的 name pipe 或是 FIFO。
          -r 存在﹐並且是可讀的。
          -s 存在﹐並且體積大於 0 (非空檔)。
          -u 存在﹐並且有 SUID 屬性。
          -w 存在﹐並且可寫入。
          -x 存在﹐並且可執(zhí)行。

          事實(shí)上﹐關(guān)於這些測(cè)試項(xiàng)目還有很多很多﹐您可以 man bash 然後參考 CONDITIONAL EXPRESSIONS 那部份。另外﹐我們還可以同時(shí)對(duì)兩個(gè)檔案進(jìn)行測(cè)試﹐例如﹕test file1 -nt file2 就是測(cè)試 file1 是否比 file2 要新。這種測(cè)試使用的標(biāo)籤是﹕

          標(biāo)籤 代表意思
          -nt Newer Than﹕第一個(gè)檔案比第二個(gè)檔案要新。
          -ot Older Than﹕第一個(gè)檔案比第二個(gè)檔案要舊。
          -ef Equal File﹕第一個(gè)檔案和第二個(gè)檔案其實(shí)都是同一個(gè)檔案 (如 link)。

          我們這裡所說(shuō)的這些測(cè)試﹐不單只用來(lái)測(cè)試檔案﹐而且還常會(huì)用來(lái)比對(duì)‘字串 (string)’或數(shù)字(整數(shù))。那什麼是字串呢﹖字面來(lái)介紹就是一串文字嘛。在一個(gè)測(cè)試中﹐~/test.sh 本身是一個(gè)檔案﹔但 '~/test.sh' ﹐則是在引號(hào)裡面(單引號(hào)或雙引號(hào))﹐那就是字串了。

          在數(shù)字和字串上面的比對(duì)(或測(cè)試)﹐所使用的標(biāo)籤大約有﹕

          標(biāo)籤 代表意思
          = 等於
          != 不等於
          < 小於
          > 大於
          -eq 等於
          -ne 不等於
          -lt 小於
          -gt 大於
          -le 小於或等於
          -ge 大於或等於
          -a 雙方都成立
          -o 單方成立
          -z 空字串
          -n 非空字串

          在上面提到的比對(duì)中﹐雖然有些意思一樣﹐但使用場(chǎng)合卻不盡相同。例如 = 和 -eq 都是‘等於’的意思﹐但 = 只能比對(duì)字串﹐而 -eq 則可以用來(lái)比對(duì)字串﹐也能用來(lái)比對(duì)表示色樣(我們?cè)?regular expression 會(huì)碰到)。

          我們之所以要進(jìn)行測(cè)試或比對(duì)﹐主要是用來(lái)做判斷的﹕假如測(cè)試或比對(duì)成立﹐那就返回一個(gè)‘真實(shí) (true)’否則返回‘虛假 (false)’。也就是說(shuō)﹕如果條件成立那麼就會(huì)如何如何﹔如果條件不成立又會(huì)如何如何﹐從而讓 script 有所‘智慧’。基本上﹐我們的程式之所以那麼聰明﹐都是從這些簡(jiǎn)單到複雜的判斷開(kāi)始的。

          比方說(shuō)﹐上面的 -a (AND) 和 -o (OR) 是用來(lái)測(cè)試兩個(gè)條件﹕A 和 B 。如果使用 test A -a B ﹐那麼 A 和 B 都必須成立那條件才成立﹔如果使用 test A -o B ﹐那麼只要 A 或 B 成立那條件就成立。至於其它的比對(duì)和測(cè)試﹐應(yīng)該更好理解吧﹖

          另外﹐還有一個(gè)特殊符號(hào)﹕“ !”您可不能不會(huì)運(yùn)用。它是‘否’的意思﹐例如﹕"! -f"是非檔案﹔ "-ne" 和 "! -eq" 都是‘不等於’的意思。

          我們?cè)诿钚猩厦嬉呀?jīng)知道如何定義和改變一個(gè)變數(shù)﹐那在 shell script 裡面就更是司空見(jiàn)慣了。而且﹐越會(huì)利用變數(shù)﹐您的 script 能力就越高。在 shell script 中所定義的變數(shù)有更嚴(yán)格的定義﹕

          標(biāo)籤 代表意思
          -a 定義為陣列 (array) 變數(shù)
          -f 僅定義功 (function) 能名稱。
          -i 定義為整數(shù)。
          -r 定義為唯獨(dú)變數(shù)。
          -x 透過(guò)環(huán)境輸出變數(shù)。

          我們除了用 “ = ”來(lái)定義變數(shù)之外﹐還可以用 declare 命令來(lái)明確定義變數(shù)。例如﹕
          # A=3 B="-2"
          # RESULT=$A*$B
          # echo $RESULT
          3*-2
          # declare -i A=3 B="-2"
          # declare -i RESULT=$A*$B
          # echo $RESULT
          -6

          您這裡會(huì)發(fā)現(xiàn)﹕如果沒(méi)有使用 declare 命令將變數(shù)定義為整數(shù)的話﹐那麼 A 和 B 的變數(shù)值都只是字串而已。

          您現(xiàn)在已經(jīng)知道什麼是變數(shù)﹑如何定義變數(shù)﹑什麼是字串﹑如何比對(duì)和測(cè)試字串和檔案﹐這些都是 script 的基本技巧。寫一些簡(jiǎn)單的 script 應(yīng)該不成問(wèn)題了﹐例如在家目錄寫一個(gè) test.sh ﹐其內(nèi)容如下﹕
                1 #!/bin/bash
                                                                          2 # Purpose: a simple test shell script.
                                                                          3 # Author: netman <netman@study-area.net>
                                                                          4 # Date: 2001/08/17
                                                                          5 # Version: 0.01
                                                                          6
                                                                          7 CHK_FILE=~/tmp/test.sh
                                                                          8
                                                                          9 if [ ! -e $CHK_FILE ]
                                                                          10 then
                                                                          11         echo "$0: Error: '$CHK_FILE' is not found." ; exit 1
                                                                          12
                                                                          13 elif [ -d $CHK_FILE ];then
                                                                          14         echo -n "$CHK_FILE is a directory, and you can "
                                                                          15         test -x $CHK_FILE || echo -n "NOT "
                                                                          16         echo "search it."
                                                                          17         exit 2
                                                                          18
                                                                          19 elif [ -f $CHK_FILE ]; then
                                                                          20         echo "$CHK_FILE is a regular file."
                                                                          21         test -r $CHK_FILE && echo "You can read it."
                                                                          22         test -x $CHK_FILE && echo "You can execute it."
                                                                          23         test -w $CHK_FILE && echo "You can write to it."
                                                                          24         test -s $CHK_FILE || echo "However, it is empty."
                                                                          25         exit 0
                                                                          26
                                                                          27 else
                                                                          28         echo "$CHK_FILE is a special file."
                                                                          29         exit 3
                                                                          30
                                                                          31 fi
                                                                          
          (注意﹕我目前用 vi 編輯﹐並用 :set nu 將行數(shù)顯示出來(lái)﹐實(shí)際的命令行是沒(méi)有行數(shù)的。)

          先讓我們看第一行﹕#!/bin/bash﹐就是定義出 bash 是這個(gè) script 的 command interpreter 。

          然後是一些註解﹐說(shuō)明了這個(gè) script 的用途﹑作者﹑日期﹑版本﹐等資訊。

          在註解之後﹐第 7 行才是 script 的真正開(kāi)始﹕首先定義出一個(gè)變數(shù) CHK_FILE ﹐目前內(nèi)容是家目錄中 tmp 子目錄的 test.sh 檔案。

          Tips﹕事實(shí)上﹐這個(gè)定義比較有局限﹐如果您想改良這個(gè)設(shè)計(jì)﹐可以將這行(第 7 行)換成下面數(shù)行﹕
          if [ -z $1 ]
                                                                          then echo "Syntax Erro! Usage: $0 <file_path>" ; exit 5
                                                                          else CHK_FILE=$1
                                                                          fi
                                                                          

           

          第一行是開(kāi)始一個(gè) if 的判斷命令﹐它一定要用一個(gè) fi 命令來(lái)結(jié)束(您可以在最後一行找到它)﹔然後在 if 和 fi 之間必須有一個(gè) then 命令。這是典型的 if-then-fi 邏輯判斷﹕如果某某條件成立﹐然後如何如何﹔還有 if-then-else-fi 判斷﹕如果某某條件成立﹐然後如何如何﹐否則如何如何﹔另外﹐也有 if-then-elif-then-else-fi 判斷﹕如果某某成立﹐然後如何如何﹔否則﹐再如果某某成立﹐然後如何如何﹔如果還是不成立﹐那就如何如何。

          上面那幾行﹐主要目的是將 CHK_FILE 這個(gè)變數(shù)值定義為 $1。嗯﹖您或許會(huì)問(wèn) $1 是什麼啊﹖那是當(dāng)您執(zhí)行這個(gè) script 的時(shí)候所輸入的第一個(gè)參數(shù)﹔而 $0 則是 script 命令行本身。所以﹐這裡是先判斷一下 $1 是否為空的 ( -z )﹐然則(then)﹐告訴您語(yǔ)法錯(cuò)誤﹐並告訴您正確的格式﹐同時(shí)退出﹐並返回一個(gè)狀態(tài)值(後面再談)﹔否則(else)﹐就將 CHK_FILE 定義為 $1。

           

          接下來(lái)第 9 行﹐您可以將 "if [ ! -e $CHK_FILE ]" 換成 "if test ! -e $CHK_FILE " ﹐意思都是一個(gè)測(cè)試。但如果用 [ ] 的話有一個(gè)地方要注意﹕"[ " 的右邊必須保留一個(gè)空白﹔" ]" 的左邊必須保留一個(gè)空白。

          在目前這個(gè) script 中﹐判斷邏輯如下﹕

          1. 先檢查 $CHK_FILE (也就是 ~/tmp/test.sh 這個(gè)檔) 是否存在( ! -e )﹐如果( if )條件成立﹐那就參考 then 裡面的命令﹔否則參考下面 elif 或 else。

             

             

          2. 如果上一步驟成立﹐也就是 ~/tmp/test.sh 不存在﹐然則用 echo 命令告訴您不能讀取這個(gè)檔﹐並同時(shí)返回父程式一個(gè)返回狀態(tài)(還記得我們?cè)谇懊嫣岬竭^(guò)的 $? 變數(shù)嗎﹖)﹐這裡為 1。在 script 中﹐任何時(shí)候執(zhí)行 exit 的話﹐就會(huì)離開(kāi) script﹐不管後面是否還有其它命令行或判斷。因?yàn)槲覍⑦@裡 echo 和 exit 寫在同一行﹐所以用一個(gè) " ; " 符號(hào)分隔開(kāi)來(lái)﹐否則﹐您可以將 exit 寫在下一行。

             

             

          3. 接下來(lái)( 13 行)是一個(gè) elif ﹐就是 else if 的意思﹐也就是說(shuō)﹕如果上一個(gè) if 不成立﹐然後在這裡再做另外一個(gè) if 測(cè)試。這裡是繼續(xù)檢查這個(gè)檔是否為一個(gè)目錄( -d )﹐然則﹐告您它是一個(gè)目錄﹐並同時(shí)嘗試告訴您是否能對(duì)這個(gè)目錄進(jìn)行搜索。

             

            然後看看下一行( 15 行)動(dòng)內(nèi)容﹐請(qǐng)留意上一個(gè) echo 和這個(gè) echo﹐都帶有一個(gè) -n 的參數(shù)﹐意思是在顯示信息的時(shí)候不進(jìn)行斷行( newline )的動(dòng)作﹐所以﹐和下面那行合在一起(共 3 行 script )才是真實(shí)顯示的內(nèi)容。這裡再進(jìn)行一個(gè)測(cè)試﹕看看您對(duì)這個(gè)目錄是否具有 -x 權(quán)限﹐否則會(huì)在 "and you can" 和 "search it." 之間加上一個(gè) "NOT"﹐如果有權(quán)限就不出現(xiàn)這個(gè) NOT 。

            這裡﹐我們沒(méi)有用 if-then 來(lái)判斷﹐而是直接用 “ || ” ( OR ) 來(lái)做判斷﹕非此即彼。這在一些簡(jiǎn)單的判斷中非常好用﹐尤其對(duì)懶人來(lái)說(shuō)﹐因?yàn)椴挥么蛱嗟淖蜘r但功能就比較有限﹕判斷之後只能執(zhí)行一個(gè)命令而已。除了 || 之外﹐您也可以用 “ && ”( AND ) 做判斷﹐套句 Jack 的名言﹕You jump I jump。所以﹐這句也可以換成﹕
            test ! -x $CHK_FILE && echo -n "NOT " (粗體字是修改部份)。

            最後﹐根據(jù)目前這個(gè) elif 條件所進(jìn)行的所有命令都執(zhí)行完畢﹐並退出這個(gè) script﹐同時(shí)設(shè)定返回狀態(tài)為 2 。

             

          4. 再下來(lái)( 19 行)是另一個(gè) elif ﹐也就是說(shuō)﹕如果連上一個(gè) elif 也不成立的話﹐那這裡繼續(xù)檢查這個(gè)檔是否是一個(gè)常規(guī)檔案( -f )﹐然則﹐告訴您是一個(gè)常規(guī)檔案﹐然後﹐接連進(jìn)行三個(gè)測(cè)試﹐分別測(cè)試您是否具有 -r﹑-x﹑-w 的權(quán)限﹐有的話﹐分別告訴您相關(guān)的可行性。最後還檢查這個(gè)檔案的長(zhǎng)度是否超過(guò) 0 ( -s )﹐否則告訴您它是一個(gè)空檔。完成這些判斷之後﹐就退出 script﹐並返回一個(gè)為 0 的狀態(tài)。

             

             

          5. 然後( 27 行)是一個(gè) else﹐意思是如果上面的所有 if 和 elif 都不成立﹐那就看這裡的。也就是說(shuō)﹕這個(gè)檔案是存在的﹐但不是目錄﹐也不是常規(guī)檔案﹐那它就是一個(gè)特殊檔。然後退出 script﹐並設(shè)定返回狀態(tài)為 3。

             

            在這個(gè)範(fàn)例中﹐script 一共有 0﹑1﹑2﹑3 這四個(gè)返回狀態(tài)﹐根據(jù)這個(gè)返回值( $? )﹐我們就可以得知檢查的檔案究竟是一個(gè)常規(guī)檔﹑還是不存在﹑還是目錄﹑還是特殊檔。

             

          6. 最後﹐再?zèng)]有其它動(dòng)作了﹐就結(jié)束這個(gè) if 判斷。

           

          目前這個(gè) script 僅提供一些 script 概念給您而已﹐例如﹕定義和使用變數(shù)﹑if-then-else-fi 判斷式﹑條件測(cè)試﹑邏輯關(guān)係﹑退出狀態(tài)﹑等等。同時(shí)﹐這個(gè)範(fàn)例也提供了一些基本的 script 書寫慣例﹕用不同的縮排來(lái)書寫不同的判斷或迴圈。例如這裡一共有兩個(gè) if-then-else-fi 判斷﹐第一個(gè) if﹑then﹑else﹑fi 都沒(méi)有縮排﹐然後﹐緊接這些命令後面的敘述就進(jìn)行縮排﹔當(dāng)碰到第二層的 if-then-else-fi 的時(shí)候﹐也如此類推。事實(shí)上﹐並非一定如此寫法﹐但日後如果您的程式越寫越長(zhǎng)﹐您自然會(huì)這樣安排的啦~~

          剛纔我們認(rèn)識(shí)了一個(gè) if-then-else-fi 的判斷﹐事實(shí)上﹐在 script 的應(yīng)用上﹐還有其它的許多判斷技巧﹐在我們開(kāi)發(fā)更強(qiáng)大和複雜的 script 之前﹐不妨先認(rèn)識(shí)一下﹕

          case
          格式﹕
          case string in
                                                              pattern)
                                                              commands
                                                              ;;
                                                              esac
                                                              

           

          它能根據(jù)不同的字串來(lái)做相應(yīng)的動(dòng)作﹐不如用例子來(lái)說(shuō)好了﹕
                1 #!/bin/bash
                                                                          2 # Purpose: a simple test shell script.
                                                                          3 # Author: netman <netman@study-area.net>
                                                                          4 # Date: 2001/08/20
                                                                          5 # Version: 0.02
                                                                          6
                                                                          7
                                                                          8 echo Please pick a number:
                                                                          9 echo "	"a, To show the local time.
                                                                          10 echo "	"b, To list current directory.
                                                                          11 echo "	"c, To see who is on the machine.
                                                                          12 echo -n "Your choice: "
                                                                          13
                                                                          14 read choice
                                                                          15
                                                                          16 case $choice in
                                                                          17                 a | A) echo -n "The local time is "
                                                                          18                    date ;;
                                                                          19                 b | B) echo "The current directory is $PWD ";;
                                                                          20                 c | C) echo "There are following users on the machine:"
                                                                          21                    who ;;
                                                                          22                 *) echo "Your choice is an invalid option." ;;
                                                                          23 esac
                                                                          

          這個(gè) script 是先請(qǐng)您選擇 a﹑b﹑c 字母﹐再用 read 命令從鍵盤讀入 choice 的變數(shù)值﹐然後將這個(gè)變數(shù)應(yīng)用在 case 判斷中﹕

          • 如果是 a 或 A﹕執(zhí)行 date 命令﹔
          • 如果是 b 或 B﹕用 $PWD 這個(gè)環(huán)境變數(shù)顯示當(dāng)前目錄﹔
          • 如果是 c 或 C﹕則執(zhí)行 who 命令﹔
          • 如果是其它 ( * ) ﹕則告訴您 invalid 。

           

          不知道您是否有注意到﹕每一個(gè) case 的選項(xiàng)﹐都用一個(gè) " ) " 作指引﹐然後﹐在這個(gè) case 最後一個(gè)命令完成以後﹐一定要用 " ;; " 來(lái)結(jié)束。最後﹐還必須用 case 的倒寫 esac 來(lái)關(guān)閉這個(gè)判斷。

           

          for
          格式﹕
          for item in list
                                                              do
                                                              commands
                                                              done
                                                              

           

          當(dāng)您需要重複處理一些事物的時(shí)候﹐for 迴圈就非常好用了。它通常用來(lái)重複處理一些列表( list ) 中的事物﹐比方說(shuō)您有一個(gè)變數(shù)﹐裡面包含著一串列表﹐那麼迴圈會(huì)一個(gè)接一個(gè)的進(jìn)行處理﹐直到最後一個(gè)處理完畢之後才退出。不如又用一個(gè)範(fàn)例來(lái)說(shuō)明好了﹕
                1 #!/bin/bash
                                                                          2 # Purpose: a simple test shell script.
                                                                          3 # Author: netman <netman@study-area.net>
                                                                          4 # Date: 2001/08/21
                                                                          5 # Version: 0.03
                                                                          6
                                                                          7
                                                                          8 if [ -z "$1" ] || [ -z "$2" ] ; then
                                                                          9         echo "Syntax Error: Usage ${0##*/} <word to search> <target dir>"
                                                                          10         exit 1
                                                                          11 fi
                                                                          12 if [ ! -d $2 ]; then
                                                                          13         echo "${0##*/} : Error: $2 is not a directory."
                                                                          14         exit 2
                                                                          15 fi
                                                                          16 TWORD="$1"
                                                                          17 TDIR="$2"
                                                                          18 TFILE=`grep -r "$TWORD" "$TDIR" | cut -d ':' -f1 | uniq`
                                                                          19
                                                                          20 if [ ! -z "$TFILE" ]; then
                                                                          21         echo "You can find $TWORD in following file(s):"
                                                                          22         for i in $TFILE ;do
                                                                          23                 echo $i
                                                                          24         done
                                                                          25         exit 0
                                                                          26 else
                                                                          27         echo "Could not find $TWORD in any file under $TDIR."
                                                                          28         exit 3
                                                                          29 fi
                                                                          

          這個(gè) script 是在一個(gè)目錄下面搜索檔案﹐如果檔案裡面發(fā)現(xiàn)有指定的文字﹐就將檔案的名稱列出來(lái)。它必須要抓兩個(gè)變數(shù)﹕TWORD 和 TDIR ﹐這兩個(gè)變數(shù)分別為 script 的第 1 個(gè)和第 2 個(gè)參數(shù)。

          一開(kāi)始要檢查命令行是否有兩個(gè)變數(shù)﹐用 -z $1 和 -z $1 來(lái)測(cè)試﹐如果它們其一沒(méi)有指定﹐就告訴您語(yǔ)法錯(cuò)誤﹐同時(shí)退出(返回值為 1 ) 。然後再檢查 $2 是否為目錄﹐如果不是目錄﹐就也提出警告﹐並退出(返回值為 2 )。如果通過(guò)上面兩道檢查﹐然後用命令 grep﹑cut﹑uniq﹐將檔案抓出來(lái)。注意﹕這就是 for 迴圈需要檢查的列表。

          然後會(huì)檢查列表是否有內(nèi)容﹐如果有的話﹐那就用 for 迴圈來(lái)重複顯示列表裡面的所有項(xiàng)目﹔一次一個(gè)﹐直到列表最後一個(gè)項(xiàng)目也處理完畢。這就是一個(gè) for 迴圈的基本運(yùn)作方式了。如果列表沒(méi)有被建立起來(lái)﹐那就告訴您找不到您指定的文字﹐並退出(返回值為 3 )。

           

          while
          格式﹕
          while condition
                                                              do
                                                              commands
                                                              done
                                                              

           

          這個(gè)迴圈應(yīng)該蠻容易理解的﹕當(dāng)條件成立的時(shí)候﹐就一直重複﹐直到條件消失為止。我們不妨改良前面的 case 那個(gè) script 看看﹕
                1 #!/bin/bash
                                                                          2 # Purpose: a simple test shell script.
                                                                          3 # Author: netman <netman@study-area.net>
                                                                          4 # Date: 2001/08/21
                                                                          5 # Version: 0.02.1
                                                                          6
                                                                          7
                                                                          8 while [ "$choice" != "x" ]; do
                                                                          9         echo
                                                                          10         echo Please pick a number:
                                                                          11         echo "	"a, To show the local time.
                                                                          12         echo "	"b, To list current directory.
                                                                          13         echo "	"c, To see who is on the machine.
                                                                          14         echo "	"x, To exit.
                                                                          15         echo -n "Your choice: "
                                                                          16
                                                                          17         read choice
                                                                          18         echo
                                                                          19
                                                                          20         case $choice in
                                                                          21                 a) echo -n "The local time is "
                                                                          22                    date ;;
                                                                          23                 b) echo "The current directory is $PWD ";;
                                                                          24                 c) echo "There are following users on the machine:"
                                                                          25                    who ;;
                                                                          26                 x) echo "Bye bye..."; exit 0 ;;
                                                                          27                 *) echo "Your choice is an invalid option." ;;
                                                                          28         esac
                                                                          29 done
                                                                          

          首先﹐我們用 while 進(jìn)行應(yīng)該條件判斷﹕如果 $choice 的變數(shù)值不等於 x 的話﹐那就重複迴圈﹐直到遇到 x (條件消失)為止。那麼這個(gè) script 會(huì)一直提示您鍵入選項(xiàng)﹐然後進(jìn)行處理﹐直到您按 x 鍵才會(huì)結(jié)束。

           

          until
          格式﹕
          until condition
                                                              do
                                                              commands
                                                              done
                                                              

           

          這個(gè) until 剛好和 while 相反﹕如果條件不成立就一直重複迴圈﹐直到條件成立為止。如果繼續(xù)引用上例﹐只需將 while 的條件設(shè)為相反就可以了﹕
          修改前﹕
                                                                          8 while [ "$choice" != "x" ]; do
                                                                          修改後﹕
                                                                          8 until [ "$choice" = "x" ]; do
                                                                          

          沒(méi)錯(cuò)﹕就是這麼簡(jiǎn)單﹗

           

          sub function
          格式﹕
          function function_name
                                                              {
                                                              commands
                                                              }
                                                              或﹕
                                                              function_name ()
                                                              {
                                                              commands
                                                              }
                                                              

           

          當(dāng)您在一個(gè) script 中﹐寫好了段可以用來(lái)處理特定條件的程式之後﹐或許後面會(huì)重複用到。當(dāng)然﹐您可以重複寫這些句子﹐但更便利的辦法是﹕將這些重複性的句子做成 sub function。如果您有模組的概念﹐那就是將一些能夠共享的程式做成模組﹐然後提供給需要用到此功能的其它程式使用。說(shuō)實(shí)在﹐看一個(gè)程式撰寫人的模組化程度﹐也就能看得出這個(gè)人的程式功力。

          我們不妨寫一個(gè) script 來(lái)顯示機(jī)器目前所使用的網(wǎng)路卡界面資訊﹐看看裡面的 sub function 是怎麼運(yùn)用的 ﹕
                1 #!/bin/bash
                                                                          2 # Purpose: a simple test shell script.
                                                                          3 # Author: netman <netman@study-area.net>
                                                                          4 # Date: 2001/08/21
                                                                          5 # Version: 0.04
                                                                          6
                                                                          7
                                                                          8 # function 1: to get interface.
                                                                          9 getif () {
                                                                          10     until [ "$CHKIFOK" = "1" ] || [ "$GETNONE" = "1" ]; do
                                                                          11         echo -n "The interface (ethX) for $CHKNET network [Enter for none]: "
                                                                          12         read CHKIF
                                                                          13         if [ -z "$CHKIF" ]; then
                                                                          14             echo
                                                                          15             echo "There is no interface for $CHKNET network."
                                                                          16             echo
                                                                          17             GETNONE=1
                                                                          18         else
                                                                          19             chkif 	# invoke the second function
                                                                          20         fi
                                                                          21     done
                                                                          22 }
                                                                          23
                                                                          24 # function 2: to check interface.
                                                                          25 chkif () {
                                                                          26     TESTIF=`/sbin/ifconfig $CHKIF | grep "inet add"`
                                                                          27     if [ -z "$TESTIF" ]; then
                                                                          28         echo ""
                                                                          29         echo "ERROR: Could not find interface '$CHKIF' on your machine!"
                                                                          30         echo "       Please make sure $CHKIF has been set up properly."
                                                                          31         echo ""
                                                                          32         return 1
                                                                          33     else
                                                                          34         CHKIFOK=1
                                                                          35         getip	# invoke the third function
                                                                          36         return 0
                                                                          37     fi
                                                                          38 }
                                                                          39
                                                                          40 # function 3: to get ip.
                                                                          41 getip () {
                                                                          42     CHKIP=`ifconfig $CHKIF | grep "inet addr" | tr -s ' ' ' ' \
                                                                          43         | cut -d ' ' -f3 | cut -d ':' -f2`
                                                                          44     CHKMASK=`ifconfig $CHKIF | grep "inet addr" | tr -s ' ' ' ' \
                                                                          45         | cut -d ' ' -f5 | cut -d ':' -f2`
                                                                          46     echo
                                                                          47     echo "The interface of $CHKNET network is $CHKIF using $CHKIP/$CHKMASK."
                                                                          48     echo
                                                                          49     return 0
                                                                          50 }
                                                                          51
                                                                          52 # start of main body
                                                                          53 for CHKNET in EXTERNAL INTERNAL DMZ ; do
                                                                          54     getif		# invoke the first function
                                                                          55     unset GETNONE
                                                                          56     unset CHKIFOK
                                                                          57 done
                                                                          

          在這個(gè) script 中﹐目前有三個(gè) sub function﹕

             

          • getif ()﹕這裡用 until 迴圈從鍵盤那裡讀入指定的網(wǎng)路卡。如果直接按 Enter 表示沒(méi)有界面﹐然則﹐回報(bào)一個(gè)信息﹐並將 GETNONE 變數(shù)設(shè)定為 1 ﹐同時(shí)退出這個(gè) function﹔否則﹐執(zhí)行下一個(gè) function 。

             

             

          • chkif ()﹕當(dāng)上一個(gè) function 順利讀入網(wǎng)路卡名稱之後﹐會(huì)檢查這個(gè)界面是否存在。這裡是用 /sbin/ifconfig 和 grep 來(lái)檢查﹐如果命令結(jié)果抓不到 IP 位址﹐表示這張卡還沒(méi)設(shè)定好﹐然則﹐回報(bào)一個(gè)錯(cuò)誤信息﹐並退出 function﹐返回狀態(tài)為 1 ﹔否則﹐執(zhí)行下一個(gè) function﹐然後退出 function﹐返回狀態(tài)為 0。

             

            (注意﹕這裡的 function 有使用 return 退出以及設(shè)定返回狀態(tài)﹔但上一個(gè) function 沒(méi)有使用 retuen﹐是因?yàn)?getif () 有使用 until 迴圈﹐如果那裡用 return 的話﹐就會(huì)打斷 until 迴圈。)

             

          • getip ()﹕當(dāng)上一個(gè) function 通過(guò)界面檢測(cè)之後﹐就將界面的 IP 和 netmask 抓出來(lái)﹐同時(shí)告訴您相關(guān)的網(wǎng)路資訊﹐最後退出 function﹐返回狀態(tài)為 0。

           

          當(dāng)所有 sub function 都定義完畢之後﹐接下來(lái)就是 main body 的開(kāi)始。這裡用一個(gè) for 迴圈﹐分別對(duì) EXTERNAL﹑INTERNAL﹑和 DMZ 網(wǎng)路進(jìn)行檢查﹐執(zhí)行第一 function 就開(kāi)始一連串的動(dòng)作了。因?yàn)?sub function 裡面的變數(shù)會(huì)重複使用﹐所以﹐在每次使用過(guò)其中的功能之後﹐要將某些影響下一個(gè)判斷的變數(shù)清空﹐用 unset 命令即可。

           

          事實(shí)上﹐用在 script 上面的迴圈有非常多的變化﹐恐怕我也沒(méi)此功力為大家一一介紹。還是留待您自己去慢慢摸索了。

          Regular Expression

          常規(guī)表示式 (RE -- Regular Expression) 應(yīng)該是所有學(xué)習(xí)程式的人員必須具備的基本功夫。雖然﹐我的程式能力很差﹐而且這裡的文章也不是以程式為主﹐不過(guò)﹐在日後的管理生涯當(dāng)中﹐如果會(huì)運(yùn)用 RE 的話﹐將令許多事情都時(shí)半功倍﹐同時(shí)也讓您在管理過(guò)程中如虎添翼。下面﹐我們只接觸最基本的 RE 常識(shí)﹐至於進(jìn)階的技巧﹐將留給有興趣的朋友自己發(fā)揮。

          首先﹐不妨讓我們認(rèn)識(shí)最基本的 RE 符號(hào)﹕

          符號(hào) 代表意思 範(fàn)例
          ^ 句子前端 "^dear" ﹕句子必須以 dear 開(kāi)頭。
          $ 句子末端 "dear$"﹕句子必須以 dear 結(jié)尾﹔"^$" ﹕空白行。
          \ 跳脫字符 "\\" ﹕\ 符號(hào)本身﹔"\." ﹕小數(shù)點(diǎn)﹔"\ " ﹕空白鍵。 
          . 任何單元字符 ".ear" : 可以是 dear, bear, tear﹐但不能是 ear 。
          ? 前一個(gè) RE 出現(xiàn) 0 次或 1 次

          "^[0-9]?$" ﹕ 空白行或只含 1 個(gè)數(shù)字的字行。

          * 前一個(gè) RE 可出現(xiàn) 0 次或多次

          "^.*" ﹕所有字行﹔

          "^[0-9][0-9]*$" ﹕ 含一或多個(gè)數(shù)字的字行。

          + 前一個(gè) RE 可出現(xiàn) 1 次或多次

          "^[0-9][0-9]+$" ﹕ 含兩個(gè)或多個(gè)數(shù)字的字行。

          \{n\} 接在前一字符的 n 個(gè)相同範(fàn)圍字符 "^[0-9]\{3\}[^0-9]" ﹕句子開(kāi)頭連續(xù) 3 個(gè)數(shù)字﹐然後是一個(gè)非數(shù)字。
          \{n,\} 接在前一字符的最少  n 個(gè)相同範(fàn)圍的字符 "^[0-9]\{3,\}" ﹕句子開(kāi)頭最少要有連續(xù) 3 個(gè)數(shù)字。
          \{n,m\} 接在前一字符的 n 到 m 個(gè)相同範(fàn)圍的字符 "^[0-9]\{3,5\}" ﹕句子開(kāi)頭連續(xù) 3 或 5 個(gè)數(shù)字。
          [list] 列表中任何單元字符 "t[ear]." ﹕可以是 tea, tar, try ﹐但不能是 tim 。
          [range] 範(fàn)圍中任何單元字符 "t[e-r]." ﹕可以是 tea, tim, try ﹐但不能是 tar 。
          [^range] 任何不在範(fàn)圍中的單一字符 "t[^e-r]." ﹕可以是 tar﹐但不能是 tea, tim, try。

          通常﹐我們用來(lái)處理 RE 的程式有 grep﹑egrep﹑sed﹑awk﹑vi﹑等等﹐各程式的語(yǔ)法和功能都相差很多﹐需要詳細(xì)研究過(guò)才能摸熟。在某些程式中﹐例如 egrep 和 awk﹐還可以處理某些延伸字符﹐例如﹕" | " 是兩個(gè) RE 的或一關(guān)係﹔" ( )" 可用來(lái)組合多個(gè) RE ﹔等等。有興趣的話﹐網(wǎng)路上都有許多資料可以找得到﹐例如網(wǎng)站 龍門少尉的窩 的 「正規(guī)表示式的入門與應(yīng)用」等系列文章。

          sed & awk

          許多人提到 RE 的時(shí)候﹐都少不了介紹一下 sedawk 這對(duì)寶貝﹐它們都可以用來(lái)處理字串﹐但處理手法上卻有所不同。有人說(shuō)用 sed 對(duì)‘字行’為單位的處理比較方便﹔而 awk 則在列表處理上面有獨(dú)到的神通。是否如此﹐大家不妨自己玩玩看囉。

          讓我們先看看 sed 這個(gè)程式﹐它的命令語(yǔ)法有點(diǎn)類型 vi 裡面的編輯功能﹕

          • 以單一字母來(lái)做命令名稱﹔
          • 命令所需的參數(shù)置於命令之後﹔
          • 您可以將行數(shù)或 RE 置於命令之前﹐以特指命令要處理的對(duì)象。

           

          關(guān)於 sed 的常用命令﹐請(qǐng)參考下表﹕

          命令 語(yǔ)法 說(shuō)明
          a a\ string 在字行後面增加特定字串(新行)。
          c c\ string 將字行換成特定字串。
          d d 刪除字行。
          i i\ string 在字行前面插入特定字串(新行)。
          p p 顯示字行。除非用 -n 指明﹐預(yù)設(shè)會(huì)在處理完畢之後顯示子行。
          s s/oldstring/newstring/flag

          用新的字串替換舊的字串。其中可用的旗標(biāo)有﹕

          g﹕替換行中的所有舊字串(預(yù)設(shè)只換第一個(gè))﹔

          p﹕顯示﹔

          wfile﹕寫入特定檔案。

          例如﹐您要輸入﹕
          sed 1,3d src.file

          所顯示的結(jié)果﹐就會(huì)將 src.file 的前面三行砍掉。如果您輸入﹕
          sed '3,$d' src.file

          這樣﹐所顯示的結(jié)果﹐就會(huì)從第 3 行到最後一行都砍掉﹐只剩下第 1 和第 2 行而已。 上面的命令別忘了加引號(hào)﹐否則要 \$ 來(lái)跳脫。不過(guò)﹐我強(qiáng)烈建議您用單引號(hào)將 sed 的命令括起來(lái)。如果您要將空白行拿掉﹐用 RE 來(lái)做非常簡(jiǎn)單﹕
          sed '/^$/d' src.file

          在 sed 裡面引用 RE 的時(shí)候﹐ 通常都會(huì)用兩個(gè) / / 符號(hào)將 RE 括起來(lái)﹐然後才是命令。 如果您想要更換字串﹐那就要用 s 命令了﹕
          sed 's/red/blue/g' src.file

          這樣﹐所有的 red 字串都會(huì)被換成 blue ﹔如果沒(méi)有加上 g 旗標(biāo)﹐那麼只有每一行的第一個(gè) red 被替換而已。

          除了 d 和 s 命令之外﹐我們還可以用 a 命令在句子後面新增一行﹐內(nèi)容為字串部份﹔或用 i 命令在句子前面插入一行﹐內(nèi)容為字串部份﹔也可以用 c 命令將整行換成字串部份。不過(guò)﹐您在執(zhí)行這幾個(gè)命令的時(shí)候﹐必須要用 ' ' 將命令和參數(shù)括起來(lái)﹐然後用 \ 符號(hào)在命令後面跳脫 Enter 鍵﹐然後才能完成。嗯﹐說(shuō)起來(lái)蠻難理解的﹐不如實(shí)作一下吧﹕
          sed '$a \
          New line appened at the end.' src.file

          這樣﹐就會(huì)在檔案最後面增加一行句子了。再比方說(shuō)﹐您要將第 3 行換成另外的文字﹐可以這樣玩﹕
          sed '3c \
          The third line is replace with this line.' src.file

          再比方說(shuō)﹐您想將您存儲(chǔ)郵件的檔案 ~/mbox 用虛線分開(kāi)每一封郵件﹐可以這樣試試﹕
          sed '/^From /i \
          \
          -------------------------\
          \
          ' ~/mbox

          我想﹐您應(yīng)該不會(huì)忘記我們?cè)谇懊娴奈恼轮些o用 ifconfig | grep | tr | cut 這些命令管線來(lái)抓出網(wǎng)路卡的界面吧。事實(shí)上﹐我們用 sed 命令也一樣可以得到同樣的結(jié)果﹕
          ifconfig eth0 | grep "inet addr" | sed -e 's/^.*addr://' | sed 's/ *Bcast.*$//'

          第一個(gè) sed 是將 addr: 到句子前面的字串用 s 命令替換為無(wú)字串(也就是在最後的 // 中間沒(méi)任何字符)﹔然後第二個(gè) sed 將 Bcast 連同前面的空白﹐到句子末端也用 s 替換為無(wú)字串(注意﹕/ *Bcast 的 / 和 * 之間是空白鍵)﹔這樣﹐剩下來(lái)的就是 IP 位址了。

          目前﹐我們所進(jìn)行的命令輸出﹐都是在熒幕上﹐既然您已經(jīng)學(xué)會(huì)命令的重導(dǎo)向了﹐要將結(jié)果保存到其它檔案去﹐應(yīng)是易如反掌了吧。 ^_^

          至於 sed 的應(yīng)用技巧﹐您可以到如下網(wǎng)站好好研究一下﹕

          http://www.ptug.org/sed/sedfaq.htm

          學(xué)習(xí)過(guò) sed 之後﹐讓我們?cè)倏纯?awk 這個(gè)命令究竟有什麼神通。就拿剛纔所舉的抓 IP 的例子來(lái)說(shuō)好了﹐換成 awk 也行哦﹕
          ifconfig eth0 | grep "inet addr" | awk -F ' ' '{print $2}' | awk -F ':' '{print $2}'

          這裡的 awk 和 cut 命令很相似﹕首先﹐用 -F 定義出分隔符號(hào)(注意﹕第一個(gè)命令用空白做分隔符﹐所以 -F 後面的兩個(gè) ' ' 之間是空白鍵)﹐然後再用 print 命令將相應(yīng)的列抓出來(lái)。對(duì) awk 而言﹐變數(shù) $0 代表每一行被處理的句子 ﹐然後第一個(gè)欄位是 $1﹑第二個(gè)欄位是 $2﹑.... ﹐如此類推。

          如果您以為 awk 只能做這些事情﹐就實(shí)在是太小看它了﹗例如您有這樣一個(gè)文字檔(dog.txt)﹐裡面只有這麼一行文字﹕
          The brown fox jumped on the lazy dog quickly.
                                                                          

          然後我們用 awk 來(lái)進(jìn)行處理﹕
          # awk '{ $2="black"; $3="dog"; $8="fox"; print}' dog.txt
          The black dog jumped on the lazy fox quickly.

          從上面的例子中﹐我們發(fā)現(xiàn) awk 具有處理變數(shù)的能力﹐事實(shí)上﹐它也有自己內(nèi)建的變數(shù)﹕

          變數(shù)名稱 代表意思
          FS 欄位分隔符號(hào)(預(yù)設(shè)是空白鍵)。
          NF 當(dāng)前句子中的欄位數(shù)目。
          NR 當(dāng)前句子的行數(shù)。
          FILENAME 當(dāng)前處理的檔案名稱。

          甚至﹐awk 還能進(jìn)行數(shù)值上的比對(duì)﹕

          變數(shù)名稱 代表意思
          > 大於。
          < 小於。
          >= 大於或等於。
          <= 小於或等於。
          == 等於。
          != 不等於。

          另外﹐如果嚴(yán)格來(lái)執(zhí)行的話﹐awk 命令一共分成三個(gè)部份﹕BEGINmain﹑和 END。在 awk 命令中﹐BEGIN 的部份﹐是讓程式開(kāi)始時(shí)執(zhí)行一些一次性的命令﹔而 END 部份則在程式退出的時(shí)候執(zhí)行一些一次性的命令﹔而 main 呢﹐則以迴圈的形式逐行處理輸入。一般來(lái)說(shuō)﹐我們無(wú)須定義 BEGIN 和 END﹐直接定義 main 的部份就可以執(zhí)行 awk 命令了。例如﹕
          # echo a b c d | awk 'BEGIN { x=1;y=2;z=x+y } {print $x $y $z}'
          abc

          這個(gè)例子有點(diǎn)多餘﹐僅作示範(fàn)而已。因?yàn)椹o我們?cè)?BEGIN 定義了 x﹑y﹑z 的值﹕( 1﹑2﹑3 )﹐然後我們?cè)賹?$x﹑$y﹑$z (也就是 $1﹑$2﹑$3 ) 的欄位列引出來(lái)。所以﹐執(zhí)行結(jié)果是第四欄的 d 就沒(méi)有顯示了。

          再例如﹐您有一個(gè)檔案 (result.txt)﹐其內(nèi)容如下﹕
          FName   LName   English Chinese Math
                                                                          Kenny   Chen    80      80      50
                                                                          Eward   Lee     70      90      90
                                                                          Amigo   Chu     50      80      80
                                                                          John    Smith   90      50      75
                                                                          

          您可以用下面的命令﹐找出 Chinese 及格的名單﹐而只顯示其名(忽略其姓)﹕
          
                                                                          # awk '{ if ($4 >= 60) print $1" : "$4}' result.txt | tail +2
                                                                          Kenny : 80
                                                                          Eward : 90
                                                                          Amigo : 80

          如果您不想顯示作為標(biāo)頭的第一行句子﹐可以 pipe 到 tail 命令進(jìn)行過(guò)濾。不如﹐讓我們?cè)偻嫘└}雜的﹐比方說(shuō)計(jì)算所有名單的平均成績(jī)算﹐並且以最後一列顯示出來(lái)﹐可以這樣設(shè)計(jì)﹕
          
                                                                          # awk '{
                                                                          total = $3 + $4 + $5
                                                                          number = NF - 2
                                                                          average = total/number
                                                                          if (NR < 2) printf("%s\t%s\n", $0, "AVERAGE");
                                                                          if (NR >= 2) printf("%s\t%3.2f\n", $0, average)
                                                                          }' result.txt
                                                                          FName   LName   English Chinese Math    AVERAGE
                                                                          Kenny   Chen    80      80      50      70.00
                                                                          Eward   Lee     70      90      90      83.33
                                                                          Amigo   Chu     50      80      80      70.00
                                                                          John    Smith   90      50      75      71.67
                                                                          

          這個(gè)命令看起來(lái)有點(diǎn)複雜﹐需要說(shuō)明一下﹕

          1. 首先﹐我們用一對(duì) { } 將 awk 的命令括起來(lái)﹐然後在其外面再加一對(duì) ' ' ﹐這樣您可以在單引號(hào)之間敲 Enter 將長(zhǎng)命令分成多行輸入。

             

             

          2. 然後定義了 total 的變數(shù)為第 3﹑4﹑5 欄的總和 (也就是 English + Chinese + Math)﹐以及變數(shù) number 為欄位數(shù)目減掉 2 (也就是 NF - FName - LName )。

             

             

          3. 然後﹐平均值就是 total 除以 number 。

             

             

          4. 因?yàn)闄n案中的第一行是不能用來(lái)運(yùn)算的﹐而且還必須再加上一個(gè)叫 AVERAGE 的欄位標(biāo)頭﹐所以這裡首先用一個(gè) if 來(lái)判斷行號(hào)是否少於 2 (不過(guò)﹐我在測(cè)試的時(shí)候﹐發(fā)現(xiàn)不能用 = 1 來(lái)設(shè)定﹐我也不知道為什麼﹖)﹐然則﹐用 printf 命令(注意﹕在 print 後面有一個(gè) f 字母) ﹐以指定格式進(jìn)行顯示。這裡的格式是﹕首先是一個(gè)字串 ( %s )﹐也就是後面所對(duì)應(yīng)的 $0 (整行句子) 以字串格式顯示﹔然後是一個(gè) tab 鍵 ( \t )﹔再下來(lái)又是一個(gè)字串﹐也就後面的 "AVERAGE" (非變數(shù)值必須用 " " 括起來(lái))﹔最後輸入一個(gè)斷行符號(hào) ( \n ﹐newline 的意思)。這裡﹐您會(huì)發(fā)現(xiàn)﹐凡是用 % 表示的格式﹐必須依順序?qū)?yīng)到後面的顯示欄位﹔而用 \ 開(kāi)頭的﹐則是可以從鍵盤輸入的符號(hào)。(或許﹐剛開(kāi)始可能比較難看出個(gè)所以然﹐多比較一下﹐就不難發(fā)現(xiàn)它的規(guī)則啦。後面還有一個(gè)範(fàn)例。)

             

             

          5. 接下來(lái)的﹐會(huì)先用 if 判斷行號(hào)是否大於或等於 2 (您也可以用 > 1 ﹐也就是從第二行開(kāi)始)﹐然則﹐再用 printf 命令﹐按 %s\t%3.2f\n 的格式來(lái)顯示。其中的 %s﹑\t﹑\n 相信您都知道了﹐只有 %3.2f 沒(méi)見(jiàn)過(guò)而已。它定義出浮點(diǎn)數(shù)字( floating point )的顯示格式是﹕小數(shù)點(diǎn)左邊 3 位數(shù)和小數(shù)點(diǎn)右邊兩位數(shù)。所以這行的格式是﹕先用字串顯示整行﹑然後一個(gè) tab 鍵﹑然後以 3.2 小數(shù)點(diǎn)格式顯示前面定義好的 average 變數(shù)﹑最後是一個(gè)斷行符號(hào)﹕

             

                          %s                     \t   %3.2f  \n
                                                                    |                                  |    |      |
                                                                    Kenny   Chen    80      80      50       70.00
                                                                    |                                  |    |      |
                                                                    $0                         average
                                                                    

             

             

          6. 然後是 '{ }' 這些括號(hào)及引號(hào)的關(guān)閉﹐最後是要處理的檔案名稱。

           

          而每一行的輸出結(jié)果﹐就會(huì)在字行後面按指定的格式加上 tab 鍵和平均值了。是否很神奇呢﹖﹗呵呵﹐這只是 awk 的牛刀少試而已﹐若要完全發(fā)揮 awk 的強(qiáng)大火力﹐恐怕已經(jīng)不是我所能介紹的了。

           

           


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 泗阳县| 龙井市| 涞源县| 葵青区| 磴口县| 体育| 屏山县| 托里县| 老河口市| 合川市| 酒泉市| 临漳县| 石台县| 昌邑市| 安徽省| 罗源县| 宁波市| 克东县| 吴忠市| 汉阴县| 濮阳县| 清水河县| 阿合奇县| 潮州市| 五台县| 宾川县| 铁岭市| 镇巴县| 松江区| 环江| 桦甸市| 利川市| 桃园县| 濮阳县| 隆德县| 喀喇| 东光县| 长岭县| 营口市| 柘城县| 渝北区|