|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
或許﹐許多人都已經(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)大致如下﹕
各命令都有自己的選項(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í)在很難全部介紹﹐下面只列舉其中一少部份而已﹕
還記得上一章裡面﹐我曾經(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ù)值是什麼﹐只要在變數(shù)名稱前面加上一個(gè)“$”符號(hào)﹐然後用 echo 命令來(lái)查看就可以了﹕
第一個(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é)果﹕
您會(huì)發(fā)現(xiàn)﹕第一命令成功執(zhí)行﹐所以其返回狀態(tài)是 0 ﹔而第二個(gè)命令執(zhí)行失敗﹐其返回狀態(tài)是 1 。假如程式設(shè)計(jì)者為不同的錯(cuò)誤設(shè)定不同的返回狀態(tài)等級(jí)﹐那您可以根據(jù)返回值推算出問(wèn)題是哪種錯(cuò)誤引起的。
我們隨時(shí)都可以用一個(gè) = (等號(hào)) 來(lái)定義一個(gè)新的變數(shù)或改變一個(gè)原有變數(shù)。例如﹕
假如您要取消一個(gè)定義好的變數(shù)﹐那麼﹐您可以使用 unset 命令﹕
不過(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)﹕
關(guān)於變數(shù)的另一個(gè)特性﹐是的變數(shù)值是可以繼承的。也就是說(shuō)﹐您可以將一個(gè)變數(shù)值來(lái)設(shè)定另外一個(gè)變數(shù)名稱。比方說(shuō)﹕
另外﹐在定義變數(shù)的時(shí)候您還要注意一些規(guī)則﹕
關(guān)於後兩項(xiàng)﹐或許我們?cè)僬倚├觼?lái)體會(huì)一下﹕
我這裡解釋一下最後面的例子好了﹕'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' " 的差別。
如果﹐您想對(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í)候﹕
您除了能夠?qū)ψ償?shù)進(jìn)行過(guò)濾之外﹐您也能對(duì)變數(shù)做出限制﹑和改變其變數(shù)值﹕
一開(kāi)始或許比較難理解上面的兩個(gè)表格說(shuō)明的意思﹐真的很混亂~~ 但只要多做一些練習(xí)﹐那您就知道怎麼使用了。比方說(shuō)﹕
請(qǐng)記住這些變數(shù)的習(xí)性﹐日後您要寫 shell script 的時(shí)候就不會(huì)將變數(shù)搞混亂了。假如您想看看當(dāng)前 shell 的環(huán)境變數(shù)有哪些﹐您可以輸入 set 命令﹔如果只想檢查 export 出來(lái)的變數(shù)﹐可以輸入 export 或 env (前者是 shell 預(yù)設(shè)的輸出變數(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ì)檢查哪些檔案吧﹕
您可以透過(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è)資料。
好了﹐相信您已經(jīng)對(duì)您的 shell 有一定的了解了。然後﹐讓我們看看 shell 上面的一些命令功能吧﹐這些技巧都是作為一個(gè)系統(tǒng)管理員基本要素。其中之一就是﹕命令重導(dǎo)向 (command redirection) 和 命令管線 (command pipe) 。 在深入講解這兩個(gè)技巧之前﹐先讓我們了解一下 shell 命令的基本概念﹕
表格中分別是我們?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é)果﹕
請(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)容就知道了﹕
當(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è)方法。
不過(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è)命令﹕
嗯﹐相信不用我多解釋了吧﹖(如果檔案不存在﹐>> 和 > 都會(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)﹐就非常方便了﹕
這裡﹐我一共有三個(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ù)做其它事情。
前面的例子﹐我們是分開(kāi)將 STDOUT 和 STDERR 重導(dǎo)到不同的檔案去﹐那麼﹐我們能否把兩者都重導(dǎo)到同一個(gè)檔呢﹖當(dāng)然是可以的﹐請(qǐng)比較下面三行﹕
我這裡告訴您﹕第一行的命令不怎麼正確﹐因?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é)束命令。
這樣通常都需要花一些時(shí)間輸入﹐假如對(duì)方在寫什麼東西和查看某些資料的時(shí)候﹐就很混亂。這時(shí)候﹐您或許可以先將短訊的內(nèi)容寫在一個(gè)檔案裡面﹐例如 greeting.msg﹐然後這樣輸入就可以了﹕
就這樣﹐這裡我們用小於符號(hào) (<) 來(lái)重導(dǎo) STDIN 。簡(jiǎn)單吧﹖^_^ 不過(guò)﹐我們用 cat 命令建立簡(jiǎn)單的檔案的時(shí)候﹐卻是使用 > 符號(hào)的﹕
查字典﹐pipe 這個(gè)英文是水管﹑管道﹑管線的意思。那麼﹐它和命令又有什麼牽連呢﹖簡(jiǎn)單的說(shuō)﹐一個(gè)命令管線﹐就是將一個(gè)命令的 STDOUT 作為另一個(gè)命令的 STDIN 。 其實(shí)﹐這樣的例子我們前面已經(jīng)碰到多次了﹐例如上一章介紹 tr 命令的時(shí)候﹕
上面這個(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)上面﹕
我們不妨解讀一下這個(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)了﹕
這裡﹐我們一共用 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ù)而已。嗯﹐不如看看下面命令好了﹕
從結(jié)果我們可以看出﹐我們用 `` 將 date 這個(gè)命令括起來(lái)(可含參數(shù))﹐那麼它的執(zhí)行結(jié)果可以作為 TODAY 的變數(shù)值。我們甚至還可以將一串命令管線直接用在命令行上面﹕
註意﹕第一行的 CR 被 \ 跳脫了﹐所以這個(gè)命令行‘看起來(lái)’有兩行。我之所以弄這麼複雜﹐是告訴您這對(duì) `` 符號(hào)可以適用的範(fàn)圍。
除了這對(duì) `` 和 | 之外﹐還有另外一個(gè)符號(hào) “ ; ”來(lái)分隔命令的。不過(guò)﹐這個(gè)比較簡(jiǎn)單﹕就是當(dāng)?shù)谝幻罱Y(jié)束之後﹐再執(zhí)行第二個(gè)命令﹐如此類推﹕
呵~~ 如果您對(duì)您的安裝程式有絕對(duì)信心﹐用上面一行命令就夠了﹗ 當(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è)共同的之處﹕第一行一定是如下這樣的﹕
其實(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 也有這樣的特性﹕
一個(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è)試﹐除了它﹐還有很多﹕
事實(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)籤是﹕
我們這裡所說(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)籤大約有﹕
在上面提到的比對(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)格的定義﹕
我們除了用 “ = ”來(lái)定義變數(shù)之外﹐還可以用 declare 命令來(lái)明確定義變數(shù)。例如﹕
您這裡會(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)容如下﹕
先讓我們看第一行﹕#!/bin/bash﹐就是定義出 bash 是這個(gè) script 的 command interpreter 。 然後是一些註解﹐說(shuō)明了這個(gè) script 的用途﹑作者﹑日期﹑版本﹐等資訊。 在註解之後﹐第 7 行才是 script 的真正開(kāi)始﹕首先定義出一個(gè)變數(shù) CHK_FILE ﹐目前內(nèi)容是家目錄中 tmp 子目錄的 test.sh 檔案。
第一行是開(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 中﹐判斷邏輯如下﹕
目前這個(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í)一下﹕
事實(shí)上﹐用在 script 上面的迴圈有非常多的變化﹐恐怕我也沒(méi)此功力為大家一一介紹。還是留待您自己去慢慢摸索了。 常規(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)﹕
通常﹐我們用來(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)用」等系列文章。 許多人提到 RE 的時(shí)候﹐都少不了介紹一下 sed 和 awk 這對(duì)寶貝﹐它們都可以用來(lái)處理字串﹐但處理手法上卻有所不同。有人說(shuō)用 sed 對(duì)‘字行’為單位的處理比較方便﹔而 awk 則在列表處理上面有獨(dú)到的神通。是否如此﹐大家不妨自己玩玩看囉。 讓我們先看看 sed 這個(gè)程式﹐它的命令語(yǔ)法有點(diǎn)類型 vi 裡面的編輯功能﹕
關(guān)於 sed 的常用命令﹐請(qǐng)參考下表﹕
例如﹐您要輸入﹕
所顯示的結(jié)果﹐就會(huì)將 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 裡面引用 RE 的時(shí)候﹐ 通常都會(huì)用兩個(gè) / / 符號(hào)將 RE 括起來(lái)﹐然後才是命令。 如果您想要更換字串﹐那就要用 s 命令了﹕
這樣﹐所有的 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í)作一下吧﹕
這樣﹐就會(huì)在檔案最後面增加一行句子了。再比方說(shuō)﹐您要將第 3 行換成另外的文字﹐可以這樣玩﹕
再比方說(shuō)﹐您想將您存儲(chǔ)郵件的檔案 ~/mbox 用虛線分開(kāi)每一封郵件﹐可以這樣試試﹕
我想﹐您應(yīng)該不會(huì)忘記我們?cè)谇懊娴奈恼轮些o用 ifconfig | grep | tr | cut 這些命令管線來(lái)抓出網(wǎng)路卡的界面吧。事實(shí)上﹐我們用 sed 命令也一樣可以得到同樣的結(jié)果﹕
第一個(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)站好好研究一下﹕
學(xué)習(xí)過(guò) sed 之後﹐讓我們?cè)倏纯?awk 這個(gè)命令究竟有什麼神通。就拿剛纔所舉的抓 IP 的例子來(lái)說(shuō)好了﹐換成 awk 也行哦﹕
這裡的 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)﹐裡面只有這麼一行文字﹕
然後我們用 awk 來(lái)進(jìn)行處理﹕
從上面的例子中﹐我們發(fā)現(xiàn) awk 具有處理變數(shù)的能力﹐事實(shí)上﹐它也有自己內(nèi)建的變數(shù)﹕
甚至﹐awk 還能進(jìn)行數(shù)值上的比對(duì)﹕
另外﹐如果嚴(yán)格來(lái)執(zhí)行的話﹐awk 命令一共分成三個(gè)部份﹕BEGIN﹑main﹑和 END。在 awk 命令中﹐BEGIN 的部份﹐是讓程式開(kāi)始時(shí)執(zhí)行一些一次性的命令﹔而 END 部份則在程式退出的時(shí)候執(zhí)行一些一次性的命令﹔而 main 呢﹐則以迴圈的形式逐行處理輸入。一般來(lái)說(shuō)﹐我們無(wú)須定義 BEGIN 和 END﹐直接定義 main 的部份就可以執(zhí)行 awk 命令了。例如﹕
這個(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)容如下﹕
您可以用下面的命令﹐找出 Chinese 及格的名單﹐而只顯示其名(忽略其姓)﹕
如果您不想顯示作為標(biāo)頭的第一行句子﹐可以 pipe 到 tail 命令進(jìn)行過(guò)濾。不如﹐讓我們?cè)偻嫘└}雜的﹐比方說(shuō)計(jì)算所有名單的平均成績(jī)算﹐並且以最後一列顯示出來(lái)﹐可以這樣設(shè)計(jì)﹕
這個(gè)命令看起來(lái)有點(diǎn)複雜﹐需要說(shuō)明一下﹕
而每一行的輸出結(jié)果﹐就會(huì)在字行後面按指定的格式加上 tab 鍵和平均值了。是否很神奇呢﹖﹗呵呵﹐這只是 awk 的牛刀少試而已﹐若要完全發(fā)揮 awk 的強(qiáng)大火力﹐恐怕已經(jīng)不是我所能介紹的了。
|
http://www.study-area.net/linux/system/linux_shell.htm
只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。 | ||
![]() |
||
網(wǎng)站導(dǎo)航:
博客園
IT新聞
Chat2DB
C++博客
博問(wèn)
管理
|
||