|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
或許﹐許多人都已經聽過 shell 或 bash 這些名字﹐但不知道您是否知道它們究竟是什麼東東呢﹖ 先回到電腦基礎常識上吧﹕所有的電腦都是由硬體和軟體構成的﹐硬體就是大家能摸得著看得見的部份﹐例如﹕鍵盤﹑熒幕﹑CPU﹑記憶體﹑硬碟﹑等等。離開了硬體﹐所謂的電腦是不存在的﹐因為整個系統的輸入和輸出以及運算都離不開硬體。請問﹕如果沒有鍵盤和熒幕您是怎樣使用電腦的﹖但是﹐您透過鍵盤進行的輸入﹐以及從熒幕看到的輸出﹐真正發揮功能的﹐是軟體的功勞。而直接負責和這些硬體進行溝通的軟體﹐就是所謂的核心(kernel)﹐kernel 必須能夠接管鍵盤的輸入﹐然後交由 CPU 進行處理﹐最後將執行結果輸出到熒幕上。當然﹐除了鍵盤和熒幕外﹐所有的硬體都必須獲得 kernel 的支援才能使用。 那麼﹐kernel 又如何知道我們鍵盤輸入的東西是什麼呢﹖那就是我們這裡介紹的 shell 所負責的事情了。因為電腦本身所處理的數據﹐都是二進位的機器碼﹐和我們人類習慣使用的語言很不一樣。比方說﹐輸入 pwd 命令﹐我們知道這是 print working directory 的意思(非常簡單的人類語音)﹐但作為 kernel 來說﹐它並不知道 pwd 是什麼﹐kernel 只會看機器碼﹐這時候﹐shell 就會幫我們將 pwd 翻譯為 kernel 能理解的程式碼。所以﹐我們在使用電腦的時候﹐基本上就是和 shell 打交道﹐而不是直接和 kernel 溝通﹐更不是直接控制硬體。 簡單來看﹐我們就這樣來看待它們的關係﹕光從字面來解析的話﹐shell 就是“殼”﹐kernel 就是“核”。好比一個果實一樣﹐您第一眼看到的就是殼﹐把殼扒開才看的到裡面的核。shell 就是使用者和 kernel 之間的界面﹐將使用者下的命令翻譯給 kernel 處理﹐關係如下圖﹕
![]()
我們在 shell 輸入一個命令﹐shell 會嘗試搜索整個命令行﹐並對其中的一些特殊字符做出處理﹐如果遇到 CR 字符( Enter ) 的時候﹐就嘗試重組整行命令﹐並解釋給 kernel 執行。而一般的命令格式(syntax)大致如下﹕
各命令都有自己的選項(options, 通常用“ - ”符號帶領)﹐可輸入也可以不輸入﹐如果沒有額外指定﹐命令通常都有自己的預設選項﹔而參數(argument)則視各程式要求而定﹐有些很嚴格﹐有些也有預設的參數。例如 "ls -l" 這個命令﹐選項是 -l (long list)﹐而預設的參數則是當前目錄。在命令行中,選項和參數都被稱為參項(parameter)。 我們經常談到的 Linux﹐其實是指 kernel 這部份﹐而在 kernel 之外﹐則是各種各樣的程式和工具﹐整合起來才成為一個完整的 Linux 發行套件。無論如何﹐Linux 的 kernel 只有一個(儘管有許多不同的版本﹐都由 Linus Tovalds 負責維護)﹐但 kernel 之外的 shell 卻有許多種﹐例如 bourne Shell﹑C Shell﹑Korn Shell﹑Zsh Shell﹑等等﹐但我們最常接觸到的名叫 BASH (Bourne Again SHell)﹐為 GNU 所加強的一個 burne shell 版本﹐ 也是大多數 Linux 套件的預設 shell 。不同的 shell 都各自有其不同的優缺點﹐有興趣您可以自行找這方面的資料來看﹐我這裡就不一一介紹了。 BASH 這個優秀的 shell﹐之所以會被各大 Linux 套件採用為預設的 shell﹐除了它本身是 open source 程式之外﹐它的強大功能應該是吸引大家目光的重要因素之一。BASH 的功能很多﹐實在很難全部介紹﹐下面只列舉其中一少部份而已﹕
還記得上一章裡面﹐我曾經提到過﹕當我們登入系統的時候﹐首先就獲得一 shell﹐而且它也佔據一個行程﹐然後再輸入的命令都屬於這個 shell 的子程式。如果您學習夠細心﹐不難發現我們的 shell 都在 /etc/passwd 這個檔裡面設定的﹐也就是帳號設定的最後一欄﹐預設是 /bin/bash 。 事實上﹐當我們獲得一個 shell 之後﹐我們才真正能和系統溝通﹐例如輸入您的命令﹑執行您的程式﹑等等。您也可以在獲得一個 shell 之後﹐再進入另外一個 shell (也就是啟動一個子程式)﹐然後還可以再進入更深一層的 shell (再進入子程式的子程式)﹐直到您輸入 exit 才退回到上一個 shell 裡面(退回上一級的父程式)。假如您已經閱讀過上一章所說過的子程式概念﹐應該不難理解。不過﹐您的行為也不是無限制的﹐而且﹐有許多設定都必須事先得到定義。所以﹐當您獲得 shell 的時候﹐同時也獲得一些環境設定﹐或稱為“環境變數( Environment variables)”。 所謂的 變數( variable )﹐就是用特定的名稱(或標籤)保存一定的設定值﹐然後供程式將來使用。例如﹐姓=chen ﹔名=kenny ﹐那麼‘姓’和‘名’就是變數名稱﹐而 chen 和 kenny 就是變數所保存的值。由 shell 所定義和管理的變數﹐我們稱為環境變數﹐因為這些變數可以供 shell 所產生的所有子程式使用。環境變數名稱一般都用大寫字母表示﹐例如﹐我們常用的環境變數有這些﹕
假如您想看看這些變數值是什麼﹐只要在變數名稱前面加上一個“$”符號﹐然後用 echo 命令來查看就可以了﹕
第一個命令就是將當前目錄的路徑顯示出來﹐和您執行 pwd 命令的結果是一樣的﹔第二個命令將當前這個 shell 的 PID 顯示出來﹐也就是 1206。如果您這時候輸入 kill -9 1206 的話﹐會將當前的 shell 砍掉﹐那您就要重新登錄才能獲得另外一個 shell﹐而它的 PID 也是新的﹔第三行命令是上一個命令的返回狀態﹕如果命令順利執行﹐並沒有錯誤﹐那通常是 0﹔如果命令遇到錯誤﹐那返回狀態則是非 0 ﹐其值視程式設計者而定(我們在後面的 shell script 的時候會介紹)。關於最後一個命令﹐不妨比較一下如下結果﹕
您會發現﹕第一命令成功執行﹐所以其返回狀態是 0 ﹔而第二個命令執行失敗﹐其返回狀態是 1 。假如程式設計者為不同的錯誤設定不同的返回狀態等級﹐那您可以根據返回值推算出問題是哪種錯誤引起的。
我們隨時都可以用一個 = (等號) 來定義一個新的變數或改變一個原有變數。例如﹕
假如您要取消一個定義好的變數﹐那麼﹐您可以使用 unset 命令﹕
不過﹐環境變數的特性之一﹐是單向輸出的。也就是說﹕一個 shell 的特定變數﹐只能在這個 shell 裡面使用。如果您要分享給同一個 shell 裡面的其它程式﹑script﹑命令使用﹐或它們的子程式使用﹐那您必須用 export 命令將這個變數進行輸出。但無論如何﹐如果您在一個子程式中定義了一個變數﹐那麼這個變數的值﹐只影響這個子程式本身以及它自己的子程式﹐而永遠不會影像到父程式或父程式產生的其它子程式。 比方說﹐您在一個程式中定義一個新的變數﹐或改變一個原有變數值﹐在程式結束的時候﹐那它所設定的變數均被取消﹔如果您想將變數值分享給該程式所產生的子程式﹐您必須用 export 命令才能保留這個變數值﹐除非子程式另外重新定義。但無論如何﹐當前程式所定義的變數值﹐是無法傳回父程式那邊的。不妨做做如下的實驗﹕
關於變數的另一個特性﹐是的變數值是可以繼承的。也就是說﹐您可以將一個變數值來設定另外一個變數名稱。比方說﹕
另外﹐在定義變數的時候您還要注意一些規則﹕
關於後兩項﹐或許我們再找些例子來體會一下﹕
我這裡解釋一下最後面的例子好了﹕'the $TOPIC is '"$TOPIC"\.。首先用單引號將 'the $TOPIC is ' 這段文字括好﹐其中用 3 個空白鍵和一個 $ 符號﹔然後用雙引號保留 $TOPIC 的變數值﹔最後用 \ 跳脫小數點。 在引用 " " 和 ' ' 符號的時候﹐基本上﹐ ' ' 所包括的內容﹐會變成單一的字串﹐任何特殊字符都失去其特殊的功能﹐而變成一般字符而已﹐但其中不能再使用 ' 符號﹐而在 " " 中間﹐則沒有 ' ' 那麼嚴格﹐某些特殊字符﹐例如 $ 號﹐仍然保留著它特殊的功能。您不妨實作一下﹐比較看看 echo ' "$HOME" ' 和 echo " '$HOME' " 的差別。
如果﹐您想對一些變數值進行過濾﹐例如﹕MY_FILE=' ~/tmp/test.sh' ﹐而您想將變數值換成 test.sh (也就是將前面的路徑去掉)﹐那您可以將 $MY_FILE 換成 ${MY_FILE##*/}。這是一個變數值字串過濾﹕## 是用來比對變數前端部份﹐然後 */ 是比對的色樣 (也就是任何字母到 / 之間)﹐然後將最長的部份刪除掉。您可以參考如下範例﹕ 當 FNAME="/home/kenny/tmp/test.1.sh" 的時候﹕
您除了能夠對變數進行過濾之外﹐您也能對變數做出限制﹑和改變其變數值﹕
一開始或許比較難理解上面的兩個表格說明的意思﹐真的很混亂~~ 但只要多做一些練習﹐那您就知道怎麼使用了。比方說﹕
請記住這些變數的習性﹐日後您要寫 shell script 的時候就不會將變數搞混亂了。假如您想看看當前 shell 的環境變數有哪些﹐您可以輸入 set 命令﹔如果只想檢查 export 出來的變數﹐可以輸入 export 或 env (前者是 shell 預設的輸出變數)。 到這裡﹐您或許會問﹕shell 的環境變數在哪裡定義呢﹖可以調整嗎﹖ 嗯﹐第一個問題我不大了解﹐我猜那是 shell 設計者預設定義好的﹐我們一登錄獲得 shell 之後就有了。不過﹐第二個問題﹐我卻可以肯定答復您﹕您可以隨時調整您的環境變數。您可以在進入 shell 之後用在命令行裡面重新定義﹐也可以透過一些 shell 設定檔來設定。 先讓我們看看﹐當您在進行登錄的時候﹐系統會檢查哪些檔案吧﹕
您可以透過修改上面提到的檔案﹐來調整您進入 shell 之後的變數值。一般使用者可以修改其家目錄( ~/ )中的檔案﹐以進行個人化的設定﹔而作為 root﹐您可以修改 /etc/下面的檔案﹐設定大家共用的變數值。至於 bash 的變數值如何設定﹖有哪些變數﹖各變數的功能如何﹖您打可以執行 man bash 參考手冊資料。
好了﹐相信您已經對您的 shell 有一定的了解了。然後﹐讓我們看看 shell 上面的一些命令功能吧﹐這些技巧都是作為一個系統管理員基本要素。其中之一就是﹕命令重導向 (command redirection) 和 命令管線 (command pipe) 。 在深入講解這兩個技巧之前﹐先讓我們了解一下 shell 命令的基本概念﹕
表格中分別是我們在 shell 中一個命令的標準 I/O (輸出與輸入)。當我們執行一個命令的時候﹐先讀入輸入 (STDIN)﹐然後進行處理﹐最後將結果進行輸出 (STDOUT)﹔如果處理過程中遇到錯誤﹐那麼命令也會顯示錯誤 (STDERR)。我們可以很容易發現﹕一般的標準輸入﹐都是從我們的鍵盤讀取﹔而標準輸出和標準錯誤﹐都從我們的銀幕顯示。 同時﹐在系統上﹐我們通常用號碼來代表各不同的 I/O﹕STDIN 是 0﹑STDOUT 是 1﹑STDERR 是 2。 當您了解各個 I/O 的意思和所代表號碼之後﹐讓我們看比較如下命令的結果﹕
請小心看第二個命令﹕在命令的後面多了一個 1 ﹐而緊接著(沒有空白﹗)是一個大於符號 (>)﹐然後是另外一個檔案名稱。但是﹐熒幕上卻沒有顯示命令的執行結果﹐也就是說﹕ STDOUT 不見了﹗那到底發生什麼事情了呢﹖ 呵﹐相信您不會這麼快忘記了 STDOUT 的代號是 1 吧﹗沒錯了﹐因為我們這裡將 1 用一個 > 符號重導到一個檔案中了。結果過是﹕我們將標準輸出從熒幕改變到檔案中﹐所以我們在銀幕就看不到 STDOUT﹐而原先的 STDOUT 結果則保存在大於符號右邊的檔中了。不信﹐您看看這個檔案的內容就知道了﹕
當我們用一個 > 將命令的 STDOUT 導向到一個檔案的時候﹐如果檔案不存在﹐則會建立一個新檔﹔如果檔案已經存在﹐那麼﹐這個檔案的內容就換成 STDOUT 的結果。有時候﹐您或許想保留原有檔案的內容﹐而將結果增加在檔案末端而已。那您可以多加一個 >﹐也就是使用 >> 就是了。您可以自己玩玩看哦~~﹐通常﹐我們要將一些命令或錯誤記錄下來﹐都用這個方法。
不過﹐仍可以用 >| 來強迫寫入。
上前面的例子中﹐我們指定了 I/O 1 (STDOUT) 進行重導向﹐這也是預設值﹐如果您沒有指定代號﹐那麼就是進行 STDOUT 的重導向﹐所以 1> 和 > 是一樣的﹔1>> 和 >> 也是一樣的。但如果您使用了數字﹐那麼數字和 > 之間一定不能有空白存在。 好了﹐下面再比較兩個命令﹕
嗯﹐相信不用我多解釋了吧﹖(如果檔案不存在﹐>> 和 > 都會建立新的。) 事實上﹐在我們的日常管理中﹐重導向的應用是非常普遍的。我只舉下面這個例子就好了﹕ 當我們進行核心編譯的時候(我們下一章再介紹)﹐熒幕會飛快的顯示出成千上萬行信息﹔其中有大部份是 STDOUT﹐但也有些是 STDERR。除非您的眼睛真的那麼厲害﹐否則您很難分辯出哪些是正常信息﹐哪些是錯誤信息。當您要編譯失敗﹐嘗試找錯誤的時候﹐如果已經將 STDERR 重導出來﹐就非常方便了﹕
這裡﹐我一共有三個打算﹕(1) 將標準輸出送到一個叫 null 的設備上﹐如果您記性夠好﹐我在前面的文章中曾比喻它為黑洞﹕所有東西進去之後都會消失掉。憑我個人的習慣﹐我會覺得編譯核心時跑出來的信息﹐如果您不感興趣的話﹐那都是垃圾﹐所以我將 STDOUT 給重導到 null 去﹐眼不見為乾淨﹔ (2) 然後﹐我將 STDERR 重導到 /tmp/kernel.err 這個檔去﹐等命令結束後﹐我就可以到那裡看看究竟有部份有問題。有些問題可能不是很重要﹐有些則可能需要重新再編核心﹐看您經驗啦。(3) 最後﹐我將命令送到 background 中執行 (呵~~ 相信您還沒忘記吧﹗)。因為﹐編譯核心都比較花時間﹐所以我將之送到背景去﹐這樣我可以繼續做其它事情。
前面的例子﹐我們是分開將 STDOUT 和 STDERR 重導到不同的檔案去﹐那麼﹐我們能否把兩者都重導到同一個檔呢﹖當然是可以的﹐請比較下面三行﹕
我這裡告訴您﹕第一行的命令不怎麼正確﹐因為這樣會造成這兩個輸出同時在‘搶’一個檔案﹐寫入的順序很難控制。而第 2 行和第 3 行的結果都是一樣的﹐看您喜歡用哪個格式了。不過﹐要小心的是﹕& 符號後面不能有空白鍵﹐否則會當成將命令送到背景執行﹐而不是將 STDOUT 和 STDERR 整合。 好了﹐前面我們都在談 STDOUT 和 STDERR 的重導向﹐那麼﹐我們是否能重導 STDIN 呢﹖ 當然可以啦~~~ 有些命令﹐當我們執行之後﹐它會停在那裡等待鍵盤的 STDIN 輸入﹐直到遇到 EOF (Ctrl+D) 標籤才會真正結束命令。比方說﹐在同一個系統上﹐如果有多位使用者同時登入的話﹐您可以用 write 命令向特的使用者送出短訊。而短訊的內容就是鍵盤敲入的文字﹐這時候命令會進入輸入模式﹐您每輸入一行並按 Enter 之後﹐那麼訊息就會在另外一端﹐直到您按 Ctrl+D 鍵才離開並結束命令。
這樣通常都需要花一些時間輸入﹐假如對方在寫什麼東西和查看某些資料的時候﹐就很混亂。這時候﹐您或許可以先將短訊的內容寫在一個檔案裡面﹐例如 greeting.msg﹐然後這樣輸入就可以了﹕
就這樣﹐這裡我們用小於符號 (<) 來重導 STDIN 。簡單吧﹖^_^ 不過﹐我們用 cat 命令建立簡單的檔案的時候﹐卻是使用 > 符號的﹕
查字典﹐pipe 這個英文是水管﹑管道﹑管線的意思。那麼﹐它和命令又有什麼牽連呢﹖簡單的說﹐一個命令管線﹐就是將一個命令的 STDOUT 作為另一個命令的 STDIN 。 其實﹐這樣的例子我們前面已經碰到多次了﹐例如上一章介紹 tr 命令的時候﹕
上面這個命令行﹐事實上有兩個命令﹕cat 和 tr ﹐在這兩個命令之間﹐我們用一個 “ | ”符號作為這兩個命令的管線﹐也就是將 cat 命令的 STDOUT 作為 tr 命令的 STDIN ﹔然後﹐tr 命令的 STDOUT 用 > 重導到另外一個檔案去。 上面只是一個非常簡單的例子而已﹐事實上﹐我們可以用多個管線連接多個程式﹐最終獲得我們確切想要的結果。比方說﹕我想知道目前有多少人登錄在系統上面﹕
我們不妨解讀一下這個命令行﹕(1) w 命令會顯示出當前登錄者的資源使用情況﹐並且每一個登錄者佔一行﹔(2) 再用 tail 命令抓取第 3 行開始的字行﹔(3) 然後用 wc -l 計算出行數。這樣﹐就可以知道當前的登錄人數了。 許多朋友目前都採用撥接 ADSL 上網﹐每次連線的 IP 都未必一樣﹐只要透過簡單的命令管線﹐您就可以將當前的 IP 抓出來了﹕
這裡﹐我們一共用 5 個 pipe 將 4 個命令連接起來﹐就抓出機器當前的 IP 位址了。是否很好用呢﹖ 在同一個命令行裡面出現多個命令的情形﹐除了 “ | ”之外﹐或許您會看到 " ` ` " 符號﹐也就是和 ~ 鍵同一個鍵的符號(不用按 Shift )。它必須是一對使用的﹐其中可以包括單一命令﹐或命令管線。那它的效果和命令管線又有什麼分別呢﹖ 我們使用 pipe 將一個命令的 STDOUT 傳給下一個命令的 STDIN﹐但使用 `` 的時候﹐它所產生的 STDOUT 或 STDERR 僅作為命令行中的一個參數而已。嗯﹐不如看看下面命令好了﹕
從結果我們可以看出﹐我們用 `` 將 date 這個命令括起來(可含參數)﹐那麼它的執行結果可以作為 TODAY 的變數值。我們甚至還可以將一串命令管線直接用在命令行上面﹕
註意﹕第一行的 CR 被 \ 跳脫了﹐所以這個命令行‘看起來’有兩行。我之所以弄這麼複雜﹐是告訴您這對 `` 符號可以適用的範圍。
除了這對 `` 和 | 之外﹐還有另外一個符號 “ ; ”來分隔命令的。不過﹐這個比較簡單﹕就是當第一命令結束之後﹐再執行第二個命令﹐如此類推﹕
呵~~ 如果您對您的安裝程式有絕對信心﹐用上面一行命令就夠了﹗ 當我們對 shell 變數和命令行有一定認識之後﹐那麼﹐我們就可以嘗試寫自己的 shell script 囉~~ 這可是非常好玩而又有成就感的事情呢﹗^_^ 在 linux 裡面的 shell script 可真是無處不在﹕我們開機執行的 run level 基本上都是一些 script ﹔登錄之後的環境設定﹐也是些 script ﹔甚至工作排程和記錄維護也都是 script 。您不妨隨便到 /etc/rc.d/init.d 裡面抓兩個程式回來看看﹐不難發現它們都有一個共同的之處﹕第一行一定是如下這樣的﹕
其實﹐這裡的 #! 後面要定義的就是命令的解釋器(command interpreter)﹐如果是 /bin/bash 的話﹐那下面的句子就都用 bash 來解釋﹔如果是 /usr/bin/perl 的話﹐那就用 perl 來解釋。不同的解釋器所使用的句子語法都不一樣﹐非常嚴格﹐就算同是用 shell 來解釋﹐不同的 shell 之間的格式也不僅相同。所以﹐如果您看到 script 的解釋器是 /bin/sh 的話﹐那就要小心了﹕如果您仔細看這個檔案﹐事實上它僅是一個 link 而已﹐有些系統或許會將它 link 到其它 shell 去。假如您的 script 句子使用的語法是 bash 的話﹐而這個 sh 卻 link 到 csh ﹐那執行起來可能會有問題。所以﹐最好還是直接指定 shell 的路徑比較安全一些﹕在這裡的範例都使用 /bin/bash 來作為 script 的解釋器。 在真正開始寫 script 之前﹐先讓我們認識 script 的一些基本概念﹕ 簡單來說﹐shell script 裡面就是一連串命令行而已﹐再加上條件判斷﹑流程控制﹑迴圈﹑等技巧﹐聰明地執行正確的命令和使用正確的參數選項。和我們在 shell 裡面輸入命令一樣﹐shell script 也有這樣的特性﹕
一個良好的 script 作者﹐在程式開頭的時候﹐都會用註解說明 script 的名稱﹑用途﹑作者﹑日期﹑版本﹑等信息。如果您有這個機會寫自己的 script﹐也應該有這個良好習慣。 shell script 檔的命名沒一定規則﹐可以使用任何檔案名稱(參考檔案系統)﹐但如果您喜歡的話﹐可以用 .sh 來做它的副檔名﹐不過這不是硬性規定的。不過﹐要執行一個 shell script﹐使用者必須對它有執行權限( x )﹐用文件編輯器新建立的檔案都是沒有 x permission 的﹐請用 chmod 命令加上。執行的時候﹐除非該 script 已經至於 PATH 環境變數之內的路徑內﹐否則您必須指定路徑。例如﹐您寫了一個叫 test.sh 的 shell script﹐放在家目錄內﹐假設這也是您的當前工作目錄﹐您必須加上路徑才能執行﹕./test.sh 或 ~/test.sh 。所以﹐建議您在 script 測試無誤之後﹐放在 ~/bin 目錄裡面﹐那就可以在任何地方執行自己的 script 了﹐當然﹐您要確定 ~/bin 已經出現在您的 PATH 變數裡面。 script 之所以聰明﹐在於它能夠對一些條件進行測試( test )。您可以直接用 test 命令﹐也可以用 if 敘述﹐例如﹕test -f ~/test.sh 。它的意思是測試一下 ~/test.sh 這個檔案是否存在﹐這個 -f 通常用在檔案上面的測試﹐除了它﹐還有很多﹕
事實上﹐關於這些測試項目還有很多很多﹐您可以 man bash 然後參考 CONDITIONAL EXPRESSIONS 那部份。另外﹐我們還可以同時對兩個檔案進行測試﹐例如﹕test file1 -nt file2 就是測試 file1 是否比 file2 要新。這種測試使用的標籤是﹕
我們這裡所說的這些測試﹐不單只用來測試檔案﹐而且還常會用來比對‘字串 (string)’或數字(整數)。那什麼是字串呢﹖字面來介紹就是一串文字嘛。在一個測試中﹐~/test.sh 本身是一個檔案﹔但 '~/test.sh' ﹐則是在引號裡面(單引號或雙引號)﹐那就是字串了。 在數字和字串上面的比對(或測試)﹐所使用的標籤大約有﹕
在上面提到的比對中﹐雖然有些意思一樣﹐但使用場合卻不盡相同。例如 = 和 -eq 都是‘等於’的意思﹐但 = 只能比對字串﹐而 -eq 則可以用來比對字串﹐也能用來比對表示色樣(我們在 regular expression 會碰到)。 我們之所以要進行測試或比對﹐主要是用來做判斷的﹕假如測試或比對成立﹐那就返回一個‘真實 (true)’否則返回‘虛假 (false)’。也就是說﹕如果條件成立那麼就會如何如何﹔如果條件不成立又會如何如何﹐從而讓 script 有所‘智慧’。基本上﹐我們的程式之所以那麼聰明﹐都是從這些簡單到複雜的判斷開始的。 比方說﹐上面的 -a (AND) 和 -o (OR) 是用來測試兩個條件﹕A 和 B 。如果使用 test A -a B ﹐那麼 A 和 B 都必須成立那條件才成立﹔如果使用 test A -o B ﹐那麼只要 A 或 B 成立那條件就成立。至於其它的比對和測試﹐應該更好理解吧﹖ 另外﹐還有一個特殊符號﹕“ !”您可不能不會運用。它是‘否’的意思﹐例如﹕"! -f"是非檔案﹔ "-ne" 和 "! -eq" 都是‘不等於’的意思。 我們在命令行上面已經知道如何定義和改變一個變數﹐那在 shell script 裡面就更是司空見慣了。而且﹐越會利用變數﹐您的 script 能力就越高。在 shell script 中所定義的變數有更嚴格的定義﹕
我們除了用 “ = ”來定義變數之外﹐還可以用 declare 命令來明確定義變數。例如﹕
您這裡會發現﹕如果沒有使用 declare 命令將變數定義為整數的話﹐那麼 A 和 B 的變數值都只是字串而已。 您現在已經知道什麼是變數﹑如何定義變數﹑什麼是字串﹑如何比對和測試字串和檔案﹐這些都是 script 的基本技巧。寫一些簡單的 script 應該不成問題了﹐例如在家目錄寫一個 test.sh ﹐其內容如下﹕
先讓我們看第一行﹕#!/bin/bash﹐就是定義出 bash 是這個 script 的 command interpreter 。 然後是一些註解﹐說明了這個 script 的用途﹑作者﹑日期﹑版本﹐等資訊。 在註解之後﹐第 7 行才是 script 的真正開始﹕首先定義出一個變數 CHK_FILE ﹐目前內容是家目錄中 tmp 子目錄的 test.sh 檔案。
第一行是開始一個 if 的判斷命令﹐它一定要用一個 fi 命令來結束(您可以在最後一行找到它)﹔然後在 if 和 fi 之間必須有一個 then 命令。這是典型的 if-then-fi 邏輯判斷﹕如果某某條件成立﹐然後如何如何﹔還有 if-then-else-fi 判斷﹕如果某某條件成立﹐然後如何如何﹐否則如何如何﹔另外﹐也有 if-then-elif-then-else-fi 判斷﹕如果某某成立﹐然後如何如何﹔否則﹐再如果某某成立﹐然後如何如何﹔如果還是不成立﹐那就如何如何。 上面那幾行﹐主要目的是將 CHK_FILE 這個變數值定義為 $1。嗯﹖您或許會問 $1 是什麼啊﹖那是當您執行這個 script 的時候所輸入的第一個參數﹔而 $0 則是 script 命令行本身。所以﹐這裡是先判斷一下 $1 是否為空的 ( -z )﹐然則(then)﹐告訴您語法錯誤﹐並告訴您正確的格式﹐同時退出﹐並返回一個狀態值(後面再談)﹔否則(else)﹐就將 CHK_FILE 定義為 $1。
接下來第 9 行﹐您可以將 "if [ ! -e $CHK_FILE ]" 換成 "if test ! -e $CHK_FILE " ﹐意思都是一個測試。但如果用 [ ] 的話有一個地方要注意﹕"[ " 的右邊必須保留一個空白﹔" ]" 的左邊必須保留一個空白。 在目前這個 script 中﹐判斷邏輯如下﹕
目前這個 script 僅提供一些 script 概念給您而已﹐例如﹕定義和使用變數﹑if-then-else-fi 判斷式﹑條件測試﹑邏輯關係﹑退出狀態﹑等等。同時﹐這個範例也提供了一些基本的 script 書寫慣例﹕用不同的縮排來書寫不同的判斷或迴圈。例如這裡一共有兩個 if-then-else-fi 判斷﹐第一個 if﹑then﹑else﹑fi 都沒有縮排﹐然後﹐緊接這些命令後面的敘述就進行縮排﹔當碰到第二層的 if-then-else-fi 的時候﹐也如此類推。事實上﹐並非一定如此寫法﹐但日後如果您的程式越寫越長﹐您自然會這樣安排的啦~~ 剛纔我們認識了一個 if-then-else-fi 的判斷﹐事實上﹐在 script 的應用上﹐還有其它的許多判斷技巧﹐在我們開發更強大和複雜的 script 之前﹐不妨先認識一下﹕
事實上﹐用在 script 上面的迴圈有非常多的變化﹐恐怕我也沒此功力為大家一一介紹。還是留待您自己去慢慢摸索了。 常規表示式 (RE -- Regular Expression) 應該是所有學習程式的人員必須具備的基本功夫。雖然﹐我的程式能力很差﹐而且這裡的文章也不是以程式為主﹐不過﹐在日後的管理生涯當中﹐如果會運用 RE 的話﹐將令許多事情都時半功倍﹐同時也讓您在管理過程中如虎添翼。下面﹐我們只接觸最基本的 RE 常識﹐至於進階的技巧﹐將留給有興趣的朋友自己發揮。 首先﹐不妨讓我們認識最基本的 RE 符號﹕
通常﹐我們用來處理 RE 的程式有 grep﹑egrep﹑sed﹑awk﹑vi﹑等等﹐各程式的語法和功能都相差很多﹐需要詳細研究過才能摸熟。在某些程式中﹐例如 egrep 和 awk﹐還可以處理某些延伸字符﹐例如﹕" | " 是兩個 RE 的或一關係﹔" ( )" 可用來組合多個 RE ﹔等等。有興趣的話﹐網路上都有許多資料可以找得到﹐例如網站 龍門少尉的窩 的 「正規表示式的入門與應用」等系列文章。 許多人提到 RE 的時候﹐都少不了介紹一下 sed 和 awk 這對寶貝﹐它們都可以用來處理字串﹐但處理手法上卻有所不同。有人說用 sed 對‘字行’為單位的處理比較方便﹔而 awk 則在列表處理上面有獨到的神通。是否如此﹐大家不妨自己玩玩看囉。 讓我們先看看 sed 這個程式﹐它的命令語法有點類型 vi 裡面的編輯功能﹕
關於 sed 的常用命令﹐請參考下表﹕
例如﹐您要輸入﹕
所顯示的結果﹐就會將 src.file 的前面三行砍掉。如果您輸入﹕
這樣﹐所顯示的結果﹐就會從第 3 行到最後一行都砍掉﹐只剩下第 1 和第 2 行而已。 上面的命令別忘了加引號﹐否則要 \$ 來跳脫。不過﹐我強烈建議您用單引號將 sed 的命令括起來。如果您要將空白行拿掉﹐用 RE 來做非常簡單﹕
在 sed 裡面引用 RE 的時候﹐ 通常都會用兩個 / / 符號將 RE 括起來﹐然後才是命令。 如果您想要更換字串﹐那就要用 s 命令了﹕
這樣﹐所有的 red 字串都會被換成 blue ﹔如果沒有加上 g 旗標﹐那麼只有每一行的第一個 red 被替換而已。 除了 d 和 s 命令之外﹐我們還可以用 a 命令在句子後面新增一行﹐內容為字串部份﹔或用 i 命令在句子前面插入一行﹐內容為字串部份﹔也可以用 c 命令將整行換成字串部份。不過﹐您在執行這幾個命令的時候﹐必須要用 ' ' 將命令和參數括起來﹐然後用 \ 符號在命令後面跳脫 Enter 鍵﹐然後才能完成。嗯﹐說起來蠻難理解的﹐不如實作一下吧﹕
這樣﹐就會在檔案最後面增加一行句子了。再比方說﹐您要將第 3 行換成另外的文字﹐可以這樣玩﹕
再比方說﹐您想將您存儲郵件的檔案 ~/mbox 用虛線分開每一封郵件﹐可以這樣試試﹕
我想﹐您應該不會忘記我們在前面的文章中﹐用 ifconfig | grep | tr | cut 這些命令管線來抓出網路卡的界面吧。事實上﹐我們用 sed 命令也一樣可以得到同樣的結果﹕
第一個 sed 是將 addr: 到句子前面的字串用 s 命令替換為無字串(也就是在最後的 // 中間沒任何字符)﹔然後第二個 sed 將 Bcast 連同前面的空白﹐到句子末端也用 s 替換為無字串(注意﹕/ *Bcast 的 / 和 * 之間是空白鍵)﹔這樣﹐剩下來的就是 IP 位址了。 目前﹐我們所進行的命令輸出﹐都是在熒幕上﹐既然您已經學會命令的重導向了﹐要將結果保存到其它檔案去﹐應是易如反掌了吧。 ^_^ 至於 sed 的應用技巧﹐您可以到如下網站好好研究一下﹕
學習過 sed 之後﹐讓我們再看看 awk 這個命令究竟有什麼神通。就拿剛纔所舉的抓 IP 的例子來說好了﹐換成 awk 也行哦﹕
這裡的 awk 和 cut 命令很相似﹕首先﹐用 -F 定義出分隔符號(注意﹕第一個命令用空白做分隔符﹐所以 -F 後面的兩個 ' ' 之間是空白鍵)﹐然後再用 print 命令將相應的列抓出來。對 awk 而言﹐變數 $0 代表每一行被處理的句子 ﹐然後第一個欄位是 $1﹑第二個欄位是 $2﹑.... ﹐如此類推。 如果您以為 awk 只能做這些事情﹐就實在是太小看它了﹗例如您有這樣一個文字檔(dog.txt)﹐裡面只有這麼一行文字﹕
然後我們用 awk 來進行處理﹕
從上面的例子中﹐我們發現 awk 具有處理變數的能力﹐事實上﹐它也有自己內建的變數﹕
甚至﹐awk 還能進行數值上的比對﹕
另外﹐如果嚴格來執行的話﹐awk 命令一共分成三個部份﹕BEGIN﹑main﹑和 END。在 awk 命令中﹐BEGIN 的部份﹐是讓程式開始時執行一些一次性的命令﹔而 END 部份則在程式退出的時候執行一些一次性的命令﹔而 main 呢﹐則以迴圈的形式逐行處理輸入。一般來說﹐我們無須定義 BEGIN 和 END﹐直接定義 main 的部份就可以執行 awk 命令了。例如﹕
這個例子有點多餘﹐僅作示範而已。因為﹐我們在 BEGIN 定義了 x﹑y﹑z 的值﹕( 1﹑2﹑3 )﹐然後我們再將 $x﹑$y﹑$z (也就是 $1﹑$2﹑$3 ) 的欄位列引出來。所以﹐執行結果是第四欄的 d 就沒有顯示了。 再例如﹐您有一個檔案 (result.txt)﹐其內容如下﹕
您可以用下面的命令﹐找出 Chinese 及格的名單﹐而只顯示其名(忽略其姓)﹕
如果您不想顯示作為標頭的第一行句子﹐可以 pipe 到 tail 命令進行過濾。不如﹐讓我們再玩些更複雜的﹐比方說計算所有名單的平均成績算﹐並且以最後一列顯示出來﹐可以這樣設計﹕
這個命令看起來有點複雜﹐需要說明一下﹕
而每一行的輸出結果﹐就會在字行後面按指定的格式加上 tab 鍵和平均值了。是否很神奇呢﹖﹗呵呵﹐這只是 awk 的牛刀少試而已﹐若要完全發揮 awk 的強大火力﹐恐怕已經不是我所能介紹的了。
|
http://www.study-area.net/linux/system/linux_shell.htm