# 第一章 為什么使用shell編程
> 沒有任何一種程序設計語言是完美的,甚至沒有一個最好的語言。只有在特定環境下適合的語言。
>
> —— Herbert Mayer
無論你是否打算真正編寫shell腳本,只要你想要在一定程度上熟悉系統管理,了解掌握shell腳本的相關知識都是非常有必要的。例如Linux系統在啟動的時候會執行`/etc/rc.d`目錄下的shell腳本來恢復系統配置和準備服務。詳細了解這些啟動腳本對分析系統行為大有益處,何況,你很有可能會去修改它們呢。
編寫shell腳本并不困難,shell腳本由許多小的部分組成,而其中只有數量相當少的與shell本身特性,操作和選項[^1]有關的部分才需要去學習。Shell語法非常簡單樸素,很像是在命令行中調用和連接工具,你只需遵循很少一部分的"規則"就可以了。大部分短小的腳本通常在第一次就可以正常工作,即使是一個稍長一些的腳本,調試起來也十分簡單。
> 在個人計算機發展的早期,BASIC語言讓計算機專業人士能夠在早期的微機上編寫程序。幾十年后,Bash腳本可以讓所有僅對Linux或UNIX系統有初步了解的用戶在現代計算機上做同樣的事。
>
> 我們現在已經可以做出一些又小又快的單板機,比如[樹莓派](http://www.raspberrypi.org/)。Bash腳本提供了一種發掘這些有趣設備潛力的方式。
使用shell腳本構建一個復雜應用原型(prototype),不失為是一種雖有缺陷但非常快速的方式。在項目開發初期,使用腳本實現部分功能往往顯得十分有用。在使用C/C++,Java,Perl或Python編寫最終代碼前,可以使用shell腳本測試,修補應用結構,提前發現重大缺陷。
Shell腳本與經典的UINX哲學相似,將復雜的任務劃分為簡單的子任務,將組件與工具連接起來。許多人認為比起新一代功能強大、高度集成的語言,例如Perl,shell腳本至少是一種在美學上更加令人愉悅的解決問題的方式,Perl試圖做到面面俱到,但你必須強迫自己改變思維方式適應它。
Herbert Mayer曾說:“有用的語言需要數組、指針以及構建數據結構的通用機制”。如果依據這些標準,那shell腳本距“有用”還差得很遠,甚至是“無用”的。
### 什么時候不應該使用shell腳本
- 資源密集型的任務,尤其是對速度有要求(如排序、散列、遞歸[^2]等)
- 需要做大量的數學運算,例如浮點數運算,高精度運算或者復數運算(使用C++或FORTRAN代替)
- 有跨平臺需求(使用C或者Java代替)
- 必須使用結構化編程的復雜應用(如變量類型檢查、函數原型等)
- 影響系統全局的關鍵性任務
- 對安全性有高要求,需要保證系統的完整性以及阻止入侵、破解、惡意破壞
- 項目包含有連鎖依賴關系的組件
- 需要大量的文件操作(Bash只能訪問連續的文件,并且是以一種非常笨拙且低效的逐行訪問的方式進行的)
- 需要使用多維數組
- 需要使用如鏈表、樹等數據結構
- 需要產生或操作圖像和圖形用戶接口(GUI)
- 需要直接訪問系統硬件或外部設備
- 需要使用端口或套接字輸入輸出端口(Socket I/O)
- 需要使用庫或舊程序的接口
- 私有或閉源的項目(Shell腳本直接將源代碼公開,所有人都可以看到)
如果你的應用滿足上述任意一條,你可以考慮使用更加強大的腳本語言,如Perl,Tcl,Python,Ruby等,或考慮使用編譯型語言,如C,C++或Java等。即使如此,在開發階段使用shell腳本建立應用原型也是十分有用的。
我們接下來將使用Bash。Bash是"Bourne-Again shell"的首字母縮略詞[^3],Bash來源于Stephen Bourne開發的Bourne shell(sh)。如今Bash已成為了大部分UNIX衍生版中shell腳本事實上的標準。本書所涉及的大部分概念在其他shell中也是適用的,例如Korn Shell,Bash從它當中繼承了一部分的特性[^4];又如C Shell及其變體(需要注意的是,1993年10月Tom Christiansen在[Usenet帖子](http://www.faqs.org/faqs/unix-faq/shell/csh-whynot/)中指出,因C Shell內部固有的問題,不推薦使用C Shell編程)
接下來的部分將是一些編寫shell腳本的指導。這些指導很大程度上依賴于實例來闡述shell的特性。本書所有的例子都能夠正常工作,并在盡可能的范圍內進行過測試,其中的一部分已經運用在實際生產生活中。讀者們可以使用這些在存檔中的例子(文件名為`scriptname.sh`或`scriptname.bash`)[^5],賦予它們可執行權限(`chmod u+rx scriptname`),然后執行它們看看會發生什么。如果[存檔](http://bash.deta.in/abs-guide-latest.tar.bz2)不可用,讀者朋友也可以從本書的HTML或者PDF版本中復制粘貼代碼出來。需要注意的是,在部分例子中使用了一些暫時還未被解釋的特性,這需要讀者暫時跳過它們。
除特別說明,本書所有例子均由[本書作者](mailto:thegrendel.abs@gmail.com)編寫。
> His countenance was bold and bashed not.
>
> —— Edmund Spenser
[^1]: 這些操作和選項被稱為內建命令(builtin),是shell的內部特征。
[^2]: 雖然遞歸可以在shell腳本中實現,但是它的效率很低且實現起來很復雜、不具有美感。
[^3]: 首字母縮略詞是由每一個單詞的首字母拼接而成的易讀的代替短語。這不是一個好習慣,通常會引起一些不必要的麻煩。
[^4]: ksh88中的許多特性,甚至一些ksh93的特性都被合并到Bash中了。
[^5]: 按照慣例,用戶編寫的Bourne shell腳本應該在文件名后加上`.sh`的擴展名。而那些系統腳本,比如在`/etc/rc.d`中的腳本通常不遵循這種規范。
- 第一部分 初見shell
- 1. 為什么使用shell編程
- 2. 和Sha-Bang(#!)一起出發
- 2.1 調用一個腳本
- 2.2 牛刀小試
- 第二部分 shell基礎
- 3. 特殊字符
- 4. 變量與參數
- 4.1 變量替換
- 4.2 變量賦值
- 4.3 Bash弱類型變量
- 4.4 特殊變量類型
- 5. 引用
- 5.1 引用變量
- 5.2 轉義
- 6. 退出與退出狀態
- 7. 測試
- 7.1 測試結構
- 7.2 文件測試操作
- 7.3 其他比較操作
- 7.4 嵌套 if/then 條件測試
- 7.5 牛刀小試
- 8. 運算符相關話題
- 8.1 運算符
- 8.2 數字常量
- 8.3 雙圓括號結構
- 8.4 運算符優先級
- 第三部分 shell進階
- 10. 變量處理
- 10.1 字符串處理
- 10.1.1 使用 awk 處理字符串
- 10.1.2 參考資料
- 10.2 參數替換
- 11. 循環與分支
- 11.1 循環
- 11.2 嵌套循環
- 11.3 循環控制
- 11.4 測試與分支
- 12. 命令替換
- 13. 算術擴展
- 14. 休息時間
- 第五部分 進階話題
- 19. 嵌入文檔
- 20. I/O 重定向
- 20.1 使用 exec
- 20.2 重定向代碼塊
- 20.3 應用程序
- 22. 限制模式的Shell
- 23. 進程替換
- 26. 列表結構
- 25. 別名