天天看點

《UNIX程式設計環境》——5.2 which

本節書摘來自異步社群《unix程式設計環境》一書中的第5章,第5.2節,作者:【美】brian w. kernighan , rob pike著,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

建立自己的指令版本,如cal指令的新版本,會帶來一些其他的問題。最明顯的例子是,如果mary一起工作,并且以mary登入,則此時的cal還是标準的版本,除非mary把新的cal指令連接配接到她的bin目錄裡。你可能會非常疑惑—原先的cal指令給出的錯誤資訊不足以使人弄清發生錯誤的原因。但是這隻是這類問題的一個例子。因為shell通過path指定的一組目錄搜尋指令,得到的可能不是所期望的版本。例如,鍵入一條指令:echo,而實際運作的檔案全路徑名可能是./echo、/bin/echo、/usr/bin/echo或是别的什麼目錄,這取決于搜尋指令的目錄path以及檔案存在的具體位置。如果存在一個名字相同,但行為完全不同的指令出現在比預期早的路徑中,就可能把人弄糊塗。最常見的情況是test指令,關于這一指令我們在以後還要讨論到,因為大家往往喜歡用test來命名程式的臨時版本,但通常會執行錯誤的test程式1。在這種情況下,一個能報告即将執行的程式版本的指令,能為使用者提供很有用的服務。

一種實作方法是對path裡的目錄進行循環搜尋,找出給定名字的每一個執行檔案。在第3章裡我們用for語句實作對檔案名和參數的循環,這裡我們給出所要求的循環結構:

《UNIX程式設計環境》——5.2 which

因為我們可以在反括号…裡運作任何指令,是以顯而易見的解決方法是在$path上運作sed程式,将冒号轉換為空格符。我們仍用echo程式做這一試驗工作:

《UNIX程式設計環境》——5.2 which

顯然還存在一個問題。path中的空符号串與“.”意義相同。是以,将path裡的分号轉換成空格符不是個好辦法-這将丢失空符号串元素。為了生成一個正确的目錄清單,我們必須把path的空符号串元素轉換為點字元(即.)。空字元串部分可以位于字元串中間或字元串任意一端,因而要做少許工作以處理所有情況,即:

《UNIX程式設計環境》——5.2 which

我們可以把它寫成4個分開的sed指令,不過由于sed的4個指令是按順序執行的,是以實際上調用一次sed即可。

一旦我們有了path中的目錄元素,前面提到的test(1)指令就可以告訴我們檔案是否存在于各個目錄裡。test指令實際上是一個比較笨拙的unix程式。例如,test–r file測試file是否存在并且可讀;test -w file指令測試檔案是否存在而且可寫,但第7版沒有提供test–x(盡管system v和其他版本提供了這一功能),否則我們就可以使用它了。我們将使用test–f,這一指令測試檔案是否存在,并且不是一個目錄,而是一個普通檔案。應該檢視系統上關于test指令的手冊,因為有好幾個版本都在使用。

每條指令傳回一個“退出狀态”,即傳回一個值給shell,指出執行的情況。退出狀态是一個小整數,通常0表示“真”(指令運作成功),非0表示“假”(指令運作不成功)。注意,這個值和c語言中的真假正好相反。

由于很多不同的值可以表示“假”,是以不同的失敗原因經常用不同的“假”退出狀态值來表示,例如,grep程式在存在比對時傳回0,沒有比對時傳回1,模式或檔案名有錯時傳回2。每個程式傳回一個狀态,一般情況下我們對這個值不感興趣,但對test程式卻不一樣,它的唯一目的是要傳回一個狀态值。除此外,test不産生任何輸出,也不修改檔案。

shell将上一個程式的退出狀态存放在變量$?中:

《UNIX程式設計環境》——5.2 which

有幾個指令,如cmp和grep,都有選擇項-s,使它們僅以适當的狀态傳回,但不産生任何輸出。

shell的if語句按照指令的退出狀态執行選擇,例如:

《UNIX程式設計環境》——5.2 which

其中換行是很重要的,fi、then和else僅在換行符或分号之後才能被識别。else部分是可選的。

if語句至少要運作一條指令—條件中的指令,而case語句直接在shell中做模式比對。在某些unix版本,包括system v中,test是shell的内部函數,是以if和test能和case運作得一樣快。如果test不是内部函數,case語句就比if語句更有效,在模式比對時應該使用case語句來執行,即:

《UNIX程式設計環境》——5.2 which

這就是為什麼有時我們使用shell中的case語句進行條件測試,盡管在大多數程式設計語言使用if語句就可以了;但另一方面,case語句不能友善地判斷檔案是否可以讀;這時最好使用test指令和if語句。

現在我們開始編寫which指令的第一個版本,它将報告與給定指令相比對的檔案:

《UNIX程式設計環境》——5.2 which

開始的case語句進行錯誤檢查。注意echo後的重定向語句1>&2,表示不把錯誤資訊送往管道。shell内部指令exit用于傳回狀态。當指令不工作時,exit 2用于傳回錯誤狀态;當沒有找到檔案時傳回exit1,當找到檔案時傳回exit0。如果程式中這一位置沒有給出exit語句,則退出狀态就是shell檔案最後一個指令執行的狀态。

假如在目前目錄中有一個test程式,那麼會發生什麼情況呢?(假定test不是shell内部指令。)

《UNIX程式設計環境》——5.2 which

需要做更多的錯誤檢查。可以運作which(假設在目前目錄裡沒有test程式!)去尋找test檔案的全路徑,并列印它。但這還是不能令人滿意:因為在不同的系統中,test可能位于不同的目錄裡,而且which要依靠sed和echo,是以還要指定它們的路徑名。一個比較簡單易行的辦法是:在shell檔案裡固定path,使得在執行which指令時,僅搜尋/bin和/usr/bin目錄。當然,對于which指令,必須儲存以前的path中的目錄搜尋順序。

《UNIX程式設計環境》——5.2 which

shell提供了另外兩個操作符用于組合指令,這就是¦¦ 和&&,它們的使用形式比if語句更加簡練友善。例如,¦¦ 能夠代替一些if語句:

《UNIX程式設計環境》——5.2 which

盡管形式上相似,但操作符¦¦ 并不是管道,而是表示“或”的條件操作符。執行¦¦ 左邊的指令後,若退出狀态為0(成功),則忽略¦¦ 右邊的指令;若退出狀态為非0(失敗),執行右邊的指令,并且整個表達式的值是右邊指令退出時的狀态值。換言之,¦¦ 是“或”條件操作符,左邊的指令執行成功時不執行右邊的指令。對應的&&條件操作符表示“與”,僅當左邊的指令成功才執行右邊的指令。

練習5-4 為什麼which檔案不在退出前把路徑path恢複成opath?

練習5-5 shell使用esac終止case,使用fi終止if,為什麼使用done終止do呢?

練習5-6 把選擇項-a加到which指令中,使它列印在path裡的所有檔案,而不是在列印第一個檔案之後便退出。提示:match=’exit 0’

練習5-7 修改which,使之可以識别shell的内部函數,如exit。

練習5-8 修改which,使之能檢查檔案的執行權限。當不能找到檔案時,改變which使之列印錯誤資訊。

繼續閱讀