NO IMAGE

Steps by the Bay 海灣的樓梯 點選放大

立刻加入部落格人自己的廣告網

譯者序

毫無疑問,UNIX/Linux最重要的軟體之一就是shell,目前最流行的shell被稱為Bash(Bourne Again Shell),幾乎所有的Linux和絕大部分的UNIX都可以使用Bash。作為系統與使用者之間的互動介面,shell幾乎是你在UNIX工作平臺上最親密的朋友,因此,學好shell,是學習Linux/UNIX的的開始,並且它會始終伴隨你的工作學習。

shell是如此地重要,但令人驚奇的是,介紹shell的書沒有真正令人滿意的。所幸的是,我看到了這本被人稱為abs的書,這本書介紹了bash大量的細節和廣闊的範圍,我遇到的絕大部分的技術問題–無論是我忘記的或是以前沒有發現的–都可以在這本書裡找到答案。這本使用大量的例子詳細地介紹了Bash的語法,各種技巧,除錯等等的技術,以循序漸進的學習方式,讓你瞭解Bash的所有特性,在書中還有許多練習可以引導你思考,以得到更深入的知識。無論你是新手還是老手,或是使用其他語言的程式設計師,我能肯定你能在此書用受益。而本書除了介紹BASH的知識之外,也有許多有用的關於Linux/UNIX的知識和其他shell的介紹。

在看到本書的英文版後,我決定把它翻譯出來,在Linuxsir論壇上結識了譯者之一楊春敏共同翻譯這本書,600多頁的書是本大部頭的書,我們花了6個月的業餘時間才翻譯完了。

關於版權的問題,英文版的作者Mendel Cooper對英文版的版權做了詳細的約定,請參考:Appendix Q. Copyright。中文版版權由譯者楊春敏和黃毅共同所有,在遵守英文版版權相應條款的條件下,歡迎在保留本書譯者名字和版權說明以非盈利的方式自由釋出此中文版,以盈利目的的所有行為必須聯絡英文作者和兩位中文譯者以獲得許可。

本書得以成稿,我(黃毅)要多謝我的女朋友,本該給予她的時間我用來了翻譯,多謝你的理解,你是一個很棒的女朋友!

                       譯者 楊春敏 黃毅
                           2006.5.15

Advanced Bash-Scripting Guide
<<高階Bash指令碼程式設計指南>>
一本深入學習shell指令碼藝術的書籍

Version 3.7.2
2005/11/16

作者:Mendel Cooper

mail:[email protected]

這本書假定你沒有任何指令碼或一般程式的程式設計知識,但是如果你有相關的知識,那麼你將很容易
達到中高階的水平…all the while sneaking in little snippets of UNIX? wisdom and
lore(這句不知道怎麼譯).你可以把本書作為教材,自學手冊,或者你獲得shell指令碼技術的文件.
書中的練習和例子指令碼中的註釋將會與讀者有更好的互動,但是最關鍵的前提是:
想真正學習指令碼程式設計的唯一途徑就是編寫指令碼.

這本書也可作為教材來講解一般的程式設計概念.

下載本書最新版本,http://personal.riverusers…,
這是一個以tar和bzip2進行打包的,並且是以HTML來發行的.當然,你也可以獲得本書的pdf版本
http://www.tldp.org/LDP/ab…可以在
http://personal.riverusers…中檢視修訂歷史.

譯者:楊春敏,黃毅
mail:[email protected]

一直想好好學習一下bash,可惜網上的資料都雜亂不堪,我還是喜歡通過一本書系統的學習.這本
書來得正是時候.本書的作者真是非常的嚴謹,從例子裡的改進人名單就能看出來.可惜我水平真
的是非常有限,好多地方估計譯得都有問題.希望閱讀的朋友們多多提些修改建議.我會盡我的最
大努力去修正它.

目錄

第一部分. 熱身

   1. 為什麼使用shell程式設計
   2. 帶著一個Sha-Bang出發(Sha-Bang指的是#!)

       2.1. 呼叫一個指令碼
       2.2. 初步的練習

第二部分. 基本

   3. 特殊字元
   4. 變數和引數的介紹

       4.1. 變數替換
       4.2. 變數賦值
       4.3. Bash變數是不分型別的
       4.4. 特殊的變數型別

   5. 引用(翻譯的可能有問題,特指引號)

       5.1. 引用變數
       5.2. 轉義(/)

   6. 退出和退出狀態
   7. Tests

       7.1. Test結構
       7.2. 檔案測試操作
       7.3. 其他比較操作
       7.4. 巢狀的if/then條件test
       7.5. 檢查你的test知識

   8. 操作符和相關的主題

       8.1. 操作符
       8.2. 數字常量

第三部分. 超越基本

   9. 變數重遊

       9.1. 內部變數
       9.2. 操作字串
       9.3. 引數替換
       9.4. 指定型別的變數:declare或者typeset
       9.5. 變數的間接引用
       9.6. $RANDOM: 產生隨機整數
       9.7. 雙圓括號結構

   10. 迴圈和分支

       10.1. 迴圈
       10.2. 巢狀迴圈
       10.3. 迴圈控制
       10.4. 測試與分支(case和select結構)

   11. 內部命令與內建

       11.1. 作業控制命令

   12. 外部過濾器,程式和命令

       12.1. 基本命令
       12.2. 複雜命令
       12.3. 時間/日期 命令
       12.4. 文字處理命令
       12.5. 檔案與歸檔命令
       12.6. 通訊命令
       12.7. 終端控制命令
       12.8. 數學計算命令
       12.9. 混雜命令

   13. 系統與管理命令

       13.1. 分析一個系統指令碼

   14. 命令替換
   15. 算術擴充套件
   16. I/O 重定向

       16.1. 使用exec
       16.2. 程式碼塊的重定向
       16.3. 應用

   17. Here Documents

       17.1. Here Strings

   18. 休息時間

Part 4. 高階

   19. 正規表示式

       19.1. 一個簡要的正規表示式介紹
       19.2. 通配

   20. 子shell(Subshells)
   21. 受限shell(Restricted Shells)
   22. 程序替換
   23. 函式

       23.1. 複雜函式和函式複雜性
       23.2. 區域性變數
       23.3. 不使用區域性變數的遞迴

   24. 別名(Aliases)
   25. 列表結構
   26. 陣列
   27. /dev 和 /proc

       27.1. /dev
       27.2. /proc

   28. 關於Zeros和Nulls
   29. 除錯
   30. 選項
   31. Gotchas
   32. 指令碼程式設計風格

       32.1. 非官方的Shell指令碼風格

   33. 雜項

       33.1. 互動式和非互動式的shells和指令碼
       33.2. Shell 包裝
       33.3. 測試和比較: 另一種方法
       33.4. 遞迴
       33.5. 彩色指令碼
       33.6. 優化
       33.7. 各種小技巧
       33.8. 安全話題

           33.8.1.    被感染的指令碼
           33.8.2. 隱藏Shell指令碼原始碼

       33.9. 移植話題
       33.10. 在Windows下進行Shell程式設計

   34. Bash, 版本 2 和 3

       34.1. Bash, 版本2
       34.2. Bash, 版本3

35. 後記

   35.1. 作者後記
   35.2. 關於作者
   35.3. 哪裡可以取得幫助?
   35.4. 製作這本書的工具

       35.4.1. 硬體
       35.4.2. 軟體和排版軟體

   35.5. Credits

Bibliography
A. Contributed Scripts
B. Reference Cards
C. A Sed and Awk Micro-Primer

   C.1. Sed
   C.2. Awk

D. Exit Codes With Special Meanings
E. A Detailed Introduction to I/O and I/O Redirection
F. Standard Command-Line Options
G. Important Files
H. Important System Directories
I. Localization
J. History Commands
K. A Sample .bashrc File
L. Converting DOS Batch Files to Shell Scripts
M. Exercises

   M.1. Analyzing Scripts
   M.2. Writing Scripts

N. Revision History
O. Mirror Sites
P. To Do List
Q. Copyright

表格清單:

11-1. 作業識別符號
30-1. Bash 選項
33-1. 轉義序列中數值和彩色的對應
B-1. Special Shell Variables
B-2. TEST Operators: Binary Comparison
B-3. TEST Operators: Files
B-4. Parameter Substitution and Expansion
B-5. String Operations
B-6. Miscellaneous Constructs
C-1. Basic sed operators
C-2. Examples of sed operators
D-1. "Reserved" Exit Codes
L-1. Batch file keywords / variables / operators, and their shell equivalents
L-2. DOS commands and their UNIX equivalents
N-1. Revision History

例子清單:

2-1. 清除:清除/var/log下的log檔案
2-2. 清除:一個改良的清除指令碼
2-3. cleanup:一個增強的和廣義的刪除logfile的指令碼
3-1. 程式碼塊和I/O重定向
3-2. 將一個程式碼塊的結果儲存到檔案
3-3. 在後臺執行一個迴圈
3-4. 備份最後一天所有修改的檔案.
4-1. 變數賦值和替換
4-2. 一般的變數賦值
4-3. 變數賦值,一般的和比較特殊的
4-4. 整型還是string?
4-5. 位置引數
4-6. wh,whois節點名字查詢
4-7. 使用shift
5-1. echo一些詭異的變數
5-2. 轉義符
6-1. exit/exit狀態
6-2. 否定一個條件使用!
7-1. 什麼情況下為真?
7-2. 幾個等效命令test,/usr/bin/test,[],和/usr/bin/[
7-3. 算數測試使用(( ))
7-4. test死的連結檔案
7-5. 數字和字串比較
7-6. 測試字串是否為null
7-7. zmore
8-1. 最大公約數
8-2. 使用算術操作符
8-3. 使用&&和||進行混合狀態的test
8-4. 數字常量的處理
9-1. $IFS和空白
9-2. 時間輸入
9-3. 再來一個時間輸入
9-4. Timed read
9-5. 我是root?
9-6. arglist:通過$*和[email protected]列出所有的引數
9-7. 不一致的$*和[email protected]行為
9-8. 當$IFS為空時的$*和[email protected]
9-9. 下劃線變數
9-10. 在一個文字檔案的段間插入空行
9-11. 利用修改檔名,來轉換圖片格式
9-12. 模仿getopt命令
9-13. 提取字串的一種可選的方法
9-14. 使用引數替換和error messages
9-15. 引數替換和"usage"messages
9-16. 變數長度
9-17. 引數替換中的模式匹配
9-18. 重新命名副檔名
9-19. 使用模式匹配來分析比較特殊的字串
9-20. 對字串的字首或字尾使用匹配模式
9-21. 使用declare來指定變數的型別
9-22. 間接引用
9-23. 傳遞一個間接引用給awk
9-24. 產生隨機數
9-25. 從一副撲克牌中取出一張隨機的牌
9-26. 兩個指定值之間的隨機數
9-27. 使用隨機數來搖一個骰子
9-28. 重新分配隨機數種子
9-29. 使用awk產生偽隨機數
9-30. C風格的變數處理
10-1. 迴圈的一個簡單例子
10-2. 每個

    元素帶兩個引數的for迴圈
    10-3. 檔案資訊:對包含在變數中的檔案列表進行操作
    10-4. 在for迴圈中操作檔案
    10-5. 在for迴圈中省略

      10-6. 使用命令替換來產生for迴圈的

        10-7. 對於二進位制檔案的一個grep替換
        10-8. 列出系統上的所有使用者
        10-9. 在目錄的所有檔案中查詢源字串
        10-10. 列出目錄中所有的符號連線檔案
        10-11. 將目錄中的符號連線檔名儲存到一個檔案中
        10-12. 一個C風格的for迴圈
        10-13. 在batch mode中使用efax
        10-14. 簡單的while迴圈
        10-15. 另一個while迴圈
        10-16. 多條件的while迴圈
        10-17. C風格的while迴圈
        10-18. until迴圈
        10-19. 巢狀迴圈
        10-20. break和continue命令在迴圈中的效果
        10-21. 多層迴圈的退出
        10-22. 多層迴圈的continue
        10-23. 在實際的任務中使用"continue N"
        10-24. 使用case
        10-25. 使用case來建立選單
        10-26. 使用命令替換來產生case變數
        10-27. 簡單字串匹配
        10-28. 檢查是否是字母輸入
        10-29. 用select來建立選單
        10-30. 用函式中select結構來建立選單
        11-1. 一個fork出多個自己例項的指令碼
        11-2. printf
        11-3. 使用read,變數分配
        11-4. 當使用一個不帶變數引數的read命令時,將會發生什麼?
        11-5. read命令的多行輸入
        11-6. 檢測方向鍵
        11-7. 通過檔案重定向來使用read
        11-8. 管道輸出到read中的問題
        11-9. 修改當前的工作目錄
        11-10. 用"let"命令來作算術操作.
        11-11. 顯示eval命令的效果
        11-12. 強制登出(log-off)
        11-13. 另一個"rot13"的版本
        11-14. 在Perl指令碼中使用eval命令來強制變數替換
        11-15. 使用set來改變指令碼的位置引數
        11-16. 重新分配位置引數
        11-17. Unset一個變數
        11-18. 使用export命令傳遞一個變數到一個內嵌awk的指令碼中
        11-19. 使用getopts命令來讀取傳遞給指令碼的選項/引數.
        11-20. "Including"一個資料檔案
        11-21. 一個沒什麼用的,source自身的指令碼
        11-22. exec的效果
        11-23. 一個exec自身的指令碼
        11-24. 在繼續處理之前,等待一個程序的結束
        11-25. 一個結束自身的指令碼.
        12-1. 使用ls命令來建立一個燒錄CDR的內容列表
        12-2. Hello or Good-bye
        12-3. 刪除當前目錄下檔名中包含一些特殊字元(包括空白)的檔案..
        12-4. 通過檔案的 inode 號來刪除檔案
        12-5. Logfile: 使用 xargs 來監控系統 log
        12-6. 把當前目錄下的檔案拷貝到另一個檔案中
        12-7. 通過名字Kill程序
        12-8. 使用xargs分析單詞出現的頻率
        12-9. 使用 expr
        12-10. 使用 date 命令
        12-11. 分析單詞出現的頻率
        12-12. 那個檔案是指令碼?
        12-13. 產生10進位制隨機數
        12-14. 使用 tail 命令來監控系統log
        12-15. 在一個指令碼中模仿 "grep" 的行為
        12-16. 在1913年的韋氏詞典中查詢定義
        12-17. 檢查列表中單詞的正確性
        12-18. 轉換大寫: 把一個檔案的內容全部轉換為大寫.
        12-19. 轉換小寫: 將當前目錄下的所有文全部轉換為小寫.
        12-20. Du: DOS 到 UNIX 文字檔案的轉換.
        12-21. rot13: rot13, 弱智加密.
        12-22. Generating "Crypto-Quote" Puzzles
        12-23. 格式化檔案列表.
        12-24. 使用 column 來格式化目錄列表
        12-25. nl: 一個自己計算行號的指令碼.
        12-26. manview: 檢視格式化的man頁
        12-27. 使用 cpio 來拷貝一個目錄樹
        12-28. 解包一個 rpm 歸檔檔案
        12-29. 從 C 檔案中去掉註釋
        12-30. Exploring /usr/X11R6/bin
        12-31. 一個"改進過"的 strings  命令
        12-32. 在一個指令碼中使用 cmp 來比較2個檔案.
        12-33. basename 和 dirname
        12-34. 檢查檔案完整性
        12-35. Uudecod 編碼後的檔案
        12-36. 查詢濫用的連線來報告垃圾郵件傳送者
        12-37. 分析一個垃圾郵件域
        12-38. 獲得一份股票報價
        12-39. 更新 Fedora Core 4
        12-40. 使用 ssh
        12-41. 一個可以mail自己的指令碼
        12-42. 按月償還貸款
        12-43. 數制轉換
        12-44. 使用 "here document" 來呼叫 bc
        12-45. 計算圓周率
        12-46. 將10進位制數字轉換為16進位制數字
        12-47. 因子分解
        12-48. 計算直角三角形的斜邊
        12-49. 使用 seq 來產生迴圈引數
        12-50. 字母統計
        12-51. 使用getopt來分析命令列選項
        12-52. 一個拷貝自身的指令碼
        12-53. 練習dd
        12-54. 記錄按鍵
        12-55. 安全的刪除一個檔案
        12-56. 檔名產生器
        12-57. 將米轉換為英里
        12-58. 使用 m4
        13-1. 設定一個新密碼
        13-2. 設定一個擦除字元
        13-3. 關掉終端對於密碼的echo
        13-4. 按鍵檢測
        13-5. Checking a remote server for identd
        13-6. pidof 幫助殺掉一個程序
        13-7. 檢查一個CD映象
        13-8. 在一個檔案中建立檔案系統
        13-9. 新增一個新的硬碟驅動器
        13-10. 使用umask來將輸出檔案隱藏起來
        13-11. killall, 來自於 /etc/rc.d/init.d
        14-1. 愚蠢的指令碼策略
        14-2. 從迴圈的輸出中產生一個變數
        14-3. 找anagram(迴文構詞法, 可以將一個有意義的單詞, 變換為1個或多個有意義的單詞, 但是還是原來的子母集合)
        16-1. 使用exec重定向標準輸入
        16-2. 使用exec來重定向stdout
        16-3. 使用exec在同一指令碼中重定向stdin和stdout
        16-4. 避免子shell
        16-5. while迴圈的重定向
        16-6. 另一種while迴圈的重定向
        16-7. until迴圈重定向
        16-8. for迴圈重定向
        16-9. for迴圈重定向 loop (將標準輸入和標準輸出都重定向了)
        16-10. 重定向if/then測試結構
        16-11. 用於上面例子的"names.data"資料檔案
        16-12. 記錄日誌事件
        17-1. 廣播: 傳送訊息給每個登入上的使用者
        17-2. 仿造檔案: 建立一個兩行的仿造檔案
        17-3. 使用cat的多行訊息
        17-4. 帶有抑制tab功能的多行訊息
        17-5. 使用引數替換的here document
        17-6. 上傳一個檔案對到"Sunsite"的incoming目錄
        17-7. 關閉引數替換
        17-8. 一個產生另外一個指令碼的指令碼
        17-9. Here documents與函式
        17-10. "匿名" here Document
        17-11. 註釋掉一段程式碼塊
        17-12. 一個自文件化(self-documenting)的指令碼
        17-13. 在一個檔案的開頭新增文字
        20-1. 子shell中的變數作用域
        20-2. 列出使用者的配置檔案
        20-3. 在子shell裡進行序列處理
        21-1. 在受限的情況下執行指令碼
        23-1. 簡單函式
        23-2. 帶著引數的函式
        23-3. 函式和被傳給指令碼的命令列引數
        23-4. 傳遞間接引用給函式
        23-5. 解除傳遞給函式的引數引用
        23-6. 再次嘗試解除傳遞給函式的引數引用
        23-7. 兩個數中的最大者
        23-8. 把數字轉化成羅馬數字
        23-9. 測試函式最大的返回值
        23-10. 比較兩個大整數
        23-11. 使用者名稱的真實名
        23-12. 區域性變數的可見範圍
        23-13. 用區域性變數來遞迴
        23-14. 漢諾塔
        24-1. 指令碼中的別名
        24-2. unalias: 設定和刪除別名
        25-1. 使用"與列表(and list)"來測試命令列引數
        25-2. 用"與列表"的另一個命令列引數測試
        25-3. "或列表"和"與列表"的結合使用
        26-1. 簡單的陣列用法
        26-2. 格式化一首詩
        26-3. 多種陣列操作
        26-4. 用於陣列的字串操作符
        26-5. 將指令碼的內容傳給陣列
        26-6. 一些陣列專用的工具
        26-7. 關於空陣列和空陣列元素
        26-8. 初始化陣列
        26-9. 複製和連線陣列
        26-10. 關於連線陣列的更多資訊
        26-11. 一位老朋友: 氣泡排序
        26-12. 內嵌陣列和間接引用
        26-13. 複雜陣列應用: 埃拉托色尼素數篩子
        26-14. 模擬下推的堆疊
        26-15. 複雜的陣列應用: 列出一種怪異的數學序列
        26-16. 模擬二維陣列,並使它傾斜
        27-1. 利用/dev/tcp 來檢修故障
        27-2. 搜尋與一個PID相關的程序
        27-3. 網路連線狀態
        28-1. 隱藏cookie而不再使用
        28-2. 用/dev/zero建立一個交換臨時檔案
        28-3. 建立ramdisk
        29-1. 一個錯誤的指令碼
        29-2. 丟失關鍵字(keyword)
        29-3. 另一個錯誤指令碼
        29-4. 用"assert"測試條件
        29-5. 捕捉 exit
        29-6. 在Control-C後清除垃圾
        29-7. 跟蹤變數
        29-8. 執行多程序 (在多處理器的機器裡)
        31-1. 數字和字串比較是不相等同的
        31-2. 子SHELL缺陷
        31-3. 把echo的輸出用管道輸送給read命令
        33-1. shell 包裝
        33-2. 稍微複雜一些的shell包裝
        33-3. 寫到日誌檔案的shell包裝
        33-4. 包裝awk的指令碼
        33-5. 另一個包裝awk的指令碼
        33-6. 把Perl嵌入Bash指令碼
        33-7. Bash 和 Perl 指令碼聯合使用
        33-8. 遞迴呼叫自己本身的(無用)指令碼
        33-9. 遞迴呼叫自己本身的(有用)指令碼
        33-10. 另一個遞迴呼叫自己本身的(有用)指令碼
        33-11. 一個 "彩色的" 地址資料庫
        33-12. 畫盒子
        33-13. 顯示彩色文字
        33-14. "賽馬" 遊戲
        33-15. 返回值技巧
        33-16. 整型還是string?
        33-17. 傳遞和返回陣列
        33-18. anagrams遊戲
        33-19. 在shell指令碼中呼叫的視窗部件
        34-1. 字串擴充套件
        34-2. 間接變數引用 – 新方法
        34-3. 使用間接變數引用的簡單資料庫應用
        34-4. 用陣列和其他的小技巧來處理四人隨機打牌

        A-1. mailformat: Formatting an e-mail message
        A-2. rn: A simple-minded file rename utility
        A-3. blank-rename: renames filenames containing blanks
        A-4. encryptedpw: Uploading to an ftp site, using a locally encrypted password
        A-5. copy-cd: Copying a data CD
        A-6. Collatz series
        A-7. days-between: Calculate number of days between two dates
        A-8. Make a "dictionary"
        A-9. Soundex conversion
        A-10. "Game of Life"
        A-11. Data file for "Game of Life"
        A-12. behead: Removing mail and news message headers
        A-13. ftpget: Downloading files via ftp
        A-14. password: Generating random 8-character passwords
        A-15. fifo: Making daily backups, using named pipes
        A-16. Generating prime numbers using the modulo operator
        A-17. tree: Displaying a directory tree
        A-18. string functions: C-like string functions
        A-19. Directory information
        A-20. Object-oriented database
        A-21. Library of hash functions
        A-22. Colorizing text using hash functions
        A-23. Mounting USB keychain storage devices
        A-24. Preserving weblogs
        A-25. Protecting literal strings
        A-26. Unprotecting literal strings
        A-27. Spammer Identification
        A-28. Spammer Hunt
        A-29. Making wget easier to use
        A-30. A "podcasting" script
        A-31. Basics Reviewed
        A-32. An expanded cd command
        C-1. Counting Letter Occurrences
        K-1. Sample .bashrc file
        L-1. VIEWDATA.BAT: DOS Batch File
        L-2. viewdata.sh: Shell Script Conversion of VIEWDATA.BAT
        P-1. Print the server environment

        第一部分    熱身

        shell是一個命令直譯器.是介於作業系統kernel與使用者之間的一個絕緣層.準確地說,它也是一
        一種強力的計算機語言.一個shell程式,被稱為一個指令碼,是一種很容易使用的工具,它可以通過
        將系統呼叫,公共程式,工具,和編譯過的二進位制程式粘合在一起來建立應用.事實上,所有的UNIX
        命令和工具再加上公共程式,對於shell指令碼來說,都是可呼叫的.如果這些你還覺得不夠,那麼
        shell內建命令,比如test與迴圈結構,也會給指令碼新增強力的支援和增加靈活性.Shell指令碼對於
        管理系統任務和其它的重複工作的例程來說,表現的非常好,根本不需要那些華而不實的成熟
        緊湊的程式語言.

        第1章    為什麼使用shell程式設計
        ===========================
        沒有程式語言是完美的.甚至沒有一個唯一最好的語言,只有對於特定目的,比較適合和不適合
        的程式語言.
                                                                       Herbert Mayer

        對於任何想適當精通一些系統管理知識的人來說,掌握shell指令碼知識都是最基本的,即使這些
        人可能並不打算真正的編寫一些指令碼.想一下Linux機器的啟動過程,在這個過程中,必將執行
        /etc/rc.d目錄下的指令碼來儲存系統配置和建立服務.詳細的理解這些啟動指令碼對於分析系統的
        行為是非常重要的,並且有時候可能必須修改它.

        學習如何編寫shell指令碼並不是一件很困難的事,因為指令碼可以分為很小的塊,並且相對於shell
        特性的操作和選項[1]部分,只需要學習很小的一部分就可以了.語法是簡單並且直觀的,編寫腳
        本很像是在命令列上把一些相關命令和工具連線起來,並且只有很少的一部分規則需要學習.
        絕大部分指令碼第一次就可以正常的工作,而且即使除錯一個長一些的指令碼也是很直觀的.

        一個shell指令碼是一個類似於小吃店的(quick and dirty)方法,在你使用原型設計一個複雜的
        應用的時候.在工程開發的第一階段,即使從功能中取得很有限的一個子集放到shell指令碼中來
        完成往往都是非常有用的.使用這種方法,程式的結果可以被測試和嘗試執行,並且在處理使用
        諸如C/C ,Java或者Perl語言編寫的最終程式碼前,主要的缺陷和陷阱往往就被發現了.

        Shell指令碼遵循典型的UNIX哲學,就是把大的複雜的工程分成小規模的子任務,並且把這些部件
        和工具組合起來.許多人認為這種辦法更好一些,至少這種辦法比使用那種高/大/全的語言更
        美,更愉悅,更適合解決問題.比如Perl就是這種能幹任何事能適合任何人的語言,但是代價就是
        你需要強迫自己使用這種語言來思考解決問題的辦法.

        什麼時候不使用Shell指令碼
           
           資源密集型的任務,尤其在需要考慮效率時(比如,排序,hash等等)

           需要處理大任務的數學操作,尤其是浮點運算,精確運算,或者複雜的算術運算
           (這種情況一般使用C 或FORTRAN來處理)

           有跨平臺移植需求(一般使用C或Java)

           複雜的應用,在必須使用結構化程式設計的時候(需要變數的型別檢查,函式原型,等等)

           對於影響系統全域性性的關鍵任務應用。

           對於安全有很高要求的任務,比如你需要一個健壯的系統來防止入侵,破解,惡意破壞等等.

           專案由連串的依賴的各個部分組成。

           需要大規模的檔案操作

           需要多維陣列的支援

           需要資料結構的支援,比如連結串列或數等資料結構

           需要產生或操作圖形化介面GUI

           需要直接作業系統硬體

           需要I/O或socket介面

           需要使用庫或者遺留下來的老程式碼的介面

           私人的,閉源的應用(shell指令碼把程式碼就放在文字檔案中,全世界都能看到)

        如果你的應用符合上邊的任意一條,那麼就考慮一下更強大的語言吧–或許是Perl,Tcl,Python,
        Ruby — 或者是更高層次的編譯語言比如C/C ,或者是Java.即使如此,你會發現,使用shell
        來原型開發你的應用,在開發步驟中也是非常有用的.

        我們將開始使用Bash,Bash是"Bourne-Again shell"首字母的縮寫,也是Stephen Bourne的經典
        的Bourne shell的一個雙關語,(譯者:說實話,我一直搞不清這個雙關語是什麼意思,為什麼叫
        "Bourn-Again shell",這其中應該有個什麼典故吧,哪位好心,告訴我一下^^).Bash已經成為了
        所有UNIX中shell指令碼的事實上的標準了.同時這本書也覆蓋了絕大部分的其他一些shell的原
        則,比如Korn Shell,Bash從ksh中繼承了一部分特性,[2]C Shell和它的變種.(注意:C Shell
        程式設計是不被推薦的,因為一些特定的內在問題,Tom Christiansen在1993年10月指出了這個問題
        請在http://www.etext.org/Quart…中檢視具體內容.)

        接下來是指令碼的一些說明.在展示shell不同的特徵之前,它可以減輕一些閱讀書中例子
        的負擔.本書中的例子指令碼,都在儘可能的範圍內進行了測試,並且其中的一些將使用在真
        實的生活中.讀者可以執行這些例子指令碼(使用scriptname.sh或者scriptname.bash的形式),
        [3]並給這些指令碼執行許可權(chmod u rx scriptname),然後執行它們,看看發生了什麼.如果存
        檔的指令碼不可用,那麼就從本書的HTML,pdf或者text的發行版本中把它們拷貝貼上出來.考慮到
        這些指令碼中的內容在我們還沒解釋它之前就被列在這裡,可能會影響讀者的理解,這就需要讀者
        暫時忽略這些內容.

        除非特別註明,本書作者編寫了本書中的絕大部分例子指令碼.

        注意事項:
        [1]        這些在builtins章節被引用,這些是shell的內部特徵.
        [2]        ksh88的許多特性,甚至是一些ksh93的特性都被合併到Bash中了.
        [3]        根據慣例,使用者編寫的Bourne shell指令碼應該在指令碼的名字後邊加上.sh副檔名.
               一些系統指令碼,比如那些在/etc/rc.d中的指令碼,則不遵循這種命名習慣.

        第2章    帶著一個Sha-Bang出發(Sha-Bang指的是#!)
        ==============================================
        在一個最簡單的例子中,一個shell指令碼其實就是將一堆系統命令列在一個檔案中.它的最基本的
        用處就是,在你每次輸入這些特定順序的命令時可以少敲一些字.

        Example 2-1 清除:清除/var/log下的log檔案
        ################################Start Script#######################################
        1 # Cleanup
        2 # 當然要使用root身份來執行這個指令碼
        3
        4 cd /var/log
        5 cat /dev/null > messages
        6 cat /dev/null > wtmp
        7 echo "Logs cleaned up."
        ################################End Script#########################################
        這根本就沒什麼稀奇的, 只不過是命令的堆積, 來讓從console或者xterm中一個一個的輸入命
        令更方便一些.好處就是把所有命令都放在一個指令碼中,不用每次都敲它們.這樣的話,對於特定
        的應用來說,這個指令碼就很容易被修改或定製.

        Example 2-2 清除:一個改良的清除指令碼
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # 一個Bash指令碼的正確的開頭部分.
        3
        4 # Cleanup, 版本 2
        5
        6 # 當然要使用root身份來執行.
        7 # 在此處插入程式碼,來列印錯誤訊息,並且在不是root身份的時候退出.
        8
        9 LOG_DIR=/var/log
        10 # 如果使用變數,當然比把程式碼寫死的好.
        11 cd $LOG_DIR
        12
        13 cat /dev/null > messages
        14 cat /dev/null > wtmp
        15
        16
        17 echo "Logs cleaned up."
        18
        19 exit # 這個命令是一種正確並且合適的退出指令碼的方法.
        ################################End Script#########################################

        現在,讓我們看一下一個真正意義的指令碼.而且我們可以走得更遠…
        Example 2-3. cleanup:一個增強的和廣義的刪除logfile的指令碼
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # 清除, 版本 3
        3
        4 #  Warning:
        5 #  ——-
        6 #  這個指令碼有好多特徵,這些特徵是在後邊章節進行解釋的,大概是進行到本書的一半的
        7 #  時候,
        8 #  你就會覺得它沒有什麼神祕的了.
        9 #
        10
        11
        12
        13 LOG_DIR=/var/log
        14 ROOT_UID=0     # $UID為0的時候,使用者才具有根使用者的許可權
        15 LINES=50       # 預設的儲存行數
        16 E_XCD=66       # 不能修改目錄?
        17 E_NOTROOT=67   # 非根使用者將以error退出
        18
        19
        20 # 當然要使用根使用者來執行
        21 if [ "$UID" -ne "$ROOT_UID" ]
        22 then
        23   echo "Must be root to run this script."
        24   exit $E_NOTROOT
        25 fi  
        26
        27 if [ -n "$1" ]
        28 # 測試是否有命令列引數(非空).
        29 then
        30   lines=$1
        31 else  
        32   lines=$LINES # 預設,如果不在命令列中指定
        33 fi  
        34
        35
        36 #  Stephane Chazelas 建議使用下邊
        37 # 的更好方法來檢測命令列引數.
        38 # 但對於這章來說還是有點超前.
        39 #
        40 #    E_WRONGARGS=65  # 非數值引數(錯誤的引數格式)
        41 #
        42 #    case "$1" in
        43 #    ""      ) lines=50;;
        44 #    *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;;
        45 #    *       ) lines=$1;;
        46 #    esac
        47 #
        48 #* 直到"Loops"的章節才會對上邊的內容進行詳細的描述.
        49
        50
        51 cd $LOG_DIR
        52
        53 if [ `pwd` != "$LOG_DIR" ]  # 或者    if[ "$PWD" != "$LOG_DIR" ]
        54                             # 不在 /var/log中?
        55 then
        56   echo "Can’t change to $LOG_DIR."
        57   exit $E_XCD
        58 fi  # 在處理log file之前,再確認一遍當前目錄是否正確.
        59
        60 # 更有效率的做法是
        61 #
        62 # cd /var/log || {
        63 #   echo "Cannot change to necessary directory." >&2
        64 #   exit $E_XCD;
        65 # }
        66
        67
        68
        69
        70 tail -$lines messages > mesg.temp # 儲存log file訊息的最後部分.
        71 mv mesg.temp messages             # 變為新的log目錄.
        72
        73
        74 # cat /dev/null > messages
        75 #* 不再需要了,使用上邊的方法更安全.
        76
        77 cat /dev/null > wtmp  #  ‘: > wtmp’ 和 ‘> wtmp’具有相同的作用
        78 echo "Logs cleaned up."
        79
        80 exit 0
        81 #  退出之前返回0,返回0表示成功.
        82 #
        ################################End Script#########################################

        因為你可能希望將系統log全部消滅,這個版本留下了log訊息最後的部分.你將不斷地找到新
        的方法來完善這個指令碼,並提高效率.

        要注意,在每個指令碼的開頭都使用"#!",這意味著告訴你的系統這個檔案的執行需要指定一個解
        釋器.#!實際上是一個2位元組[1]的魔法數字,這是指定一個檔案型別的特殊標記, 換句話說, 在
        這種情況下,指的就是一個可執行的指令碼(鍵入man magic來獲得關於這個迷人話題的更多詳細
        資訊).在#!之後接著是一個路徑名.這個路徑名指定了一個解釋指令碼中命令的程式,這個程式可
        以是shell,程式語言或者是任意一個通用程式.這個指定的程式從頭開始解釋並且執行指令碼中
        的命令(從#!行下邊的一行開始),忽略註釋.[2]
        如:
        1 #!/bin/sh
        2 #!/bin/bash
        3 #!/usr/bin/perl
        4 #!/usr/bin/tcl
        5 #!/bin/sed -f
        6 #!/usr/awk -f

        上邊每一個指令碼頭的行都指定了一個不同的命令直譯器,如果是/bin/sh,那麼就是預設shell
        (在Linux系統中預設是Bash).[3]使用#!/bin/sh,在大多數商業發行的UNIX上,預設是Bourne
        shell,這將讓你的指令碼可以正常的執行在非Linux機器上,雖然這將會犧牲Bash一些獨特的特徵.
        指令碼將與POSIX[4] 的sh標準相一致.

        注意: #! 後邊給出的路徑名必須是正確的,否則將會出現一個錯誤訊息,通常是
        "Command not found",這將是你執行這個指令碼時所得到的唯一結果.

        當然"#!"也可以被忽略,不過這樣你的指令碼檔案就只能是一些命令的集合,不能夠使用shell內建
        的指令了,如果不能使用變數的話,當然這也就失去了指令碼程式設計的意義了.

           注意:這個例子鼓勵你使用模組化的方式來編寫指令碼,平時也要注意收集一些零碎的程式碼,
               這些零碎的程式碼可能用在你將來編寫的指令碼中.這樣你就可以通過這些程式碼片段來構
               造一個較大的工程用例. 以下邊指令碼作為序,來測試指令碼被呼叫的引數是否正確.
        ################################Start Script#######################################
        1 E_WRONG_ARGS=65
        2 script_parameters="-a -h -m -z"
        3 #                  -a = all, -h = help, 等等.
        4
        5 if [ $# -ne $Number_of_expected_args ]
        6 then
        7   echo "Usage: `basename $0` $script_parameters"
        8   # `basename $0`是這個指令碼的檔名
        9   exit $E_WRONG_ARGS
        10 fi
        ################################End Script#########################################
        大多數情況下,你需要編寫一個指令碼來執行一個特定的任務,在本章中第一個指令碼就是一個這樣
        的例子, 然後你會修改它來完成一個不同的,但比較相似的任務.用變數來代替寫死的常量,就是
        一個好方法,將重複的程式碼放到一個函式中,也是一種好習慣.

        2.1 呼叫一個指令碼
        —————-
        編寫完指令碼之後,你可以使用sh scriptname,[5]或者bash scriptname來呼叫它.
        (不推薦使用sh 更方便的方法是讓指令碼本身就具有可執行許可權,通過chmod命令可以修改.

        比如:
           chmod 555 scriptname (允許任何人都具有 可讀和執行許可權) [6]  
        或:
           chmod rx scriptname (允許任何人都具有 可讀和執行許可權)
           chmod u rx scriptname (只給指令碼的所有者 可讀和執行許可權)

        既然指令碼已經具有了可執行許可權,現在你可以使用./scriptname.[7]來測試它了.如果這個指令碼
        以一個"#!"行開頭,那麼指令碼將會呼叫合適的命令直譯器來執行.

        最後一步,在指令碼被測試和debug之後,你可能想把它移動到/usr/local/bin(當然是以root身份)
        ,來讓你的指令碼對所有使用者都有用.這樣使用者就可以直接敲指令碼名字來執行了.

        注意事項:
        [1]        那些具有UNIX味道的指令碼(基於4.2BSD)需要一個4位元組的魔法數字,在#!後邊需要一個
               空格#! /bin/sh.
        [2]        指令碼中的#!行的最重要的任務就是命令直譯器(sh或者bash).因為這行是以#開始的,
               當命令直譯器執行這個指令碼的時候,會把它作為一個註釋行.當然,在這之前,這行語句
               已經完成了它的任務,就是呼叫命令直譯器.

               如果在指令碼的裡邊還有一個#!行,那麼bash將把它認為是一個一般的註釋行.
                1 #!/bin/bash
                2
                3 echo "Part 1 of script."
                4 a=1
                5
                6 #!/bin/bash
                7 # 這將不會開始一個新指令碼.
                8
                9 echo "Part 2 of script."
               10 echo $a  # Value of $a stays at 1.
        [3]        這裡可以玩一些小技巧.
                1 #!/bin/rm
                2 # 自刪除指令碼.
                3
                4 # 當你執行這個指令碼時,基本上什麼都不會發生…除非這個檔案消失不見.
                5
                6 WHATEVER=65
                7
                8 echo "This line will never print (betcha!)."
                9
               10 exit $WHATEVER  # 沒關係,指令碼是不會在這退出的.
               當然,你還可以試試在一個README檔案的開頭加上#!/bin/more,並讓它具有執行許可權.
               結果將是文件自動列出自己的內容.(一個使用cat命令的here document可能是一個
               更好的選則,–見Example 17-3).
        [4]        可移植的作業系統介面,標準化類UNIX作業系統的一種嘗試.POSIX規範可以在
               http://www.opengroup.org/o…中查閱.
        [5]        小心:使用sh scriptname來呼叫指令碼的時候將會關閉一些Bash特定的擴充套件,指令碼可能
               因此而呼叫失敗.
        [6]        指令碼需要讀和執行許可權,因為shell需要讀這個指令碼.
        [7]        為什麼不直接使用scriptname來呼叫指令碼?如果你當前的目錄下($PWD)正好有你想要
               執行的指令碼,為什麼它執行不了呢?失敗的原因是,出於安全考慮,當前目錄並沒有被
               加在使用者的$PATH變數中.因此,在當前目錄下呼叫指令碼必須使用./scriptname這種
               形式.

        2.2 初步的練習
        ————–
        1. 系統管理員經常會為了自動化一些常用的任務而編寫指令碼.舉出幾個這種有用的指令碼的例項.
        2. 編寫一個指令碼,顯示時間和日期,列出所有的登入使用者,顯示系統的更新時間.然後這個指令碼
          將會把這些內容儲存到一個log file中.

        第二部分    基本

        第3章    特殊字元
        ================

        #        註釋,行首以#開頭為註釋(#!是個例外).

               1 # This line is a comment.

               註釋也可以存在於本行命令的後邊.

               1 echo "A comment will follow." # 註釋在這裡
               2 #                            ^ 注意#前邊的空白

               註釋也可以在本行空白的後邊.

               1     # A tab precedes this comment.

               注意:命令是不能跟在同一行上註釋的後邊的,沒有辦法,在同一行上,註釋的後邊想
               要再使用命令,只能另起一行.
               當然,在echo命令中被轉義的#是不能作為註釋的.
               同樣的,#也可以出現在特定的引數替換結構中或者是數字常量表示式中.

               1 echo "The # here does not begin a comment."
               2 echo ‘The # here does not begin a comment.’
               3 echo The /# here does not begin a comment.
               4 echo The # 這裡開始一個註釋
               5
               6 echo ${PATH#*:}       # 引數替換,不是一個註釋
               7 echo $(( 2#101011 ))  # 數制轉換,不是一個註釋
               8
               9 # Thanks, S.C.

               標準的引用和轉義字元("’/)可以用來轉義#

        ;        命令分隔符,可以用來在一行中來寫多個命令.

               1 echo hello; echo there
               2
               3
               4 if [ -x "$filename" ]; then    # 注意:"if"和"then"需要分隔
               5                                # 為啥?
               6   echo "File $filename exists."; cp $filename $filename.bak
               7 else
               8   echo "File $filename not found."; touch $filename
               9 fi; echo "File test complete."

               有時候需要轉義

        ;;        終止"case"選項.

               1 case "$variable" in
               2 abc)  echo "/$variable = abc" ;;
               3 xyz)  echo "/$variable = xyz" ;;
               4 esac

        .        .命令等價於source命令(見Example 11-20).這是一個bash的內建命令.

        .        .作為檔名的一部分.如果作為檔名的字首的話,那麼這個檔案將成為隱藏檔案.
               將不被ls命令列出.

               bash$ touch .hidden-file
               bash$ ls -l          
               total 10
               -rw-r–r–    1 bozo      4034 Jul 18 22:04 data1.addressbook
               -rw-r–r–    1 bozo      4602 May 25 13:58 data1.addressbook.bak
               -rw-r–r–    1 bozo       877 Dec 17  2000 employment.addressbook
               
               
               bash$ ls -al          
               total 14
               drwxrwxr-x    2 bozo  bozo      1024 Aug 29 20:54 ./
               drwx——   52 bozo  bozo      3072 Aug 29 20:51 ../
               -rw-r–r–    1 bozo  bozo      4034 Jul 18 22:04 data1.addressbook
               -rw-r–r–    1 bozo  bozo      4602 May 25 13:58 data1.addressbook.bak
               -rw-r–r–    1 bozo  bozo       877 Dec 17  2000 employment.addressbook
               -rw-rw-r–    1 bozo  bozo         0 Aug 29 20:54 .hidden-file

               .命令如果作為目錄名的一部分的話,那麼.表達的是當前目錄.".."表示上一級目錄.

               bash$ pwd
               /home/bozo/projects

               bash$ cd .
               bash$ pwd
               /home/bozo/projects

               bash$ cd ..
               bash$ pwd
               /home/bozo/

               .命令經常作為一個檔案移動命令的目的地.

               bash$ cp /home/bozo/current_work/junk/* .

        .        .字元匹配,這是作為正則表達是的一部分,用來匹配任何的單個字元.

        "        部分引用."STRING"阻止了一部分特殊字元,具體見第5章.

        ‘        全引用. ‘STRING’ 阻止了全部特殊字元,具體見第5章.

        ,        逗號連結了一系列的算術操作,雖然裡邊所有的內容都被執行了,但只有最後一項被
               返回.

               如:
               1 let "t2 = ((a = 9, 15 / 3))"  # Set "a = 9" and "t2 = 15 / 3"

        /        轉義字元,如/X等價於"X"或’X’,具體見第5章.

        /        檔名路徑分隔符.或用來做除法操作.

        `        後置引用,命令替換,具體見第14章

        :        空命令,等價於"NOP"(no op,一個什麼也不幹的命令).也可以被認為與shell的內建命令(true)作用相同.":"命令是一
               個bash的內建命令,它的返回值為0,就是shell返回的true.

               如:
               1 :
               2 echo $?   # 0

               死迴圈,如:

                1 while :
                2 do
                3    operation-1
                4    operation-2
                5    …
                6    operation-n
                7 done
                8
                9 # 與下邊相同:
               10 #    while true
               11 #    do
               12 #      …
               13 #    done

               在if/then中的佔位符,如:
               1 if condition
               2 then :   # 什麼都不做,引出分支.
               3 else
               4    take-some-action
               5 fi

               在一個2元命令中提供一個佔位符,具體見Example 8-2,和"預設引數".如:
               1 : ${username=`whoami`}
               2 # ${username=`whoami`}   如果沒有":"的話,將給出一個錯誤,除非"username"是
               3 #                        個命令
               在here document中提供一個佔位符,見Example 17-10.

               使用"引數替換"來評估字串變數(見Example 9-14).如:
               1 : ${HOSTNAME?} ${USER?} ${MAIL?}
               2 #     如果一個或多個必要的環境變數沒被設定的話,
               3 # 就列印錯誤資訊.

               "變數擴充套件/子串替換"
               在和 > (重定向操作符)結合使用時,把一個檔案截斷到0長度,沒有修改它的許可權.
               如果檔案在之前並不存在,那麼就建立它.如:
               1 : > data.xxx        #檔案"data.xxx"現在被清空了.
               2
               3 #與 cat /dev/null >data.xxx 的作用相同
               4 #然而,這不會產生一個新的程序,因為":"是一個內建命令.
               具體參見Example 12-14.

               在和>>重定向操作符結合使用時,將不會對想要附加的檔案產生任何影響.
               如果檔案不存在,將建立.
               注意: 這隻適用於正規檔案,而不是管道,符號連線,和某些特殊檔案.

               也可能用來作為註釋行,雖然我們不推薦這麼做.使用#來註釋的話,將關閉剩餘行的
               錯誤檢查,所以可以在註釋行中寫任何東西.然而,使用:的話將不會這樣.如:
               1 : This is a comment thar generates an error,(if [ $x -eq 3] ).

               ":"還用來在/etc/passwd和$PATH變數中用來做分隔符.
               bash$    echo $PATH
               /usr/local/bin:/bin:/usr/X11R6/bin:/sbin:/usr/sbin:/usr/games
        !        取反操作符,將反轉"退出狀態"結果,(見Example 6-2).也會反轉test操作符的意義.比
               如修改=為!=.!操作是Bash的一個關鍵字.

               在一個不同的上下文中,!也會出現在"間接變數引用"見Example 9-22.

               在另一種上下文中,!還能反轉bash的"history mechanism"(見附錄J 歷史命令)
               需要注意的是,在一個指令碼中,"history mechanism"是被禁用的.

        *        萬能匹配字元,用於檔名匹配(這個東西有個專有名詞叫file globbing),或者是正則
               表示式中.注意:在正規表示式匹配中的作用和在檔名匹配中的作用是不同的.
               bash$ echo *
               abs-book.sgml add-drive.sh agram.sh alias.sh
        *        數學乘法.
               **是冪運算.
        ?        測試操作.在一個確定的表示式中,用?來測試結果.
               (())結構可以用來做數學計算或者是寫c程式碼,那?就是c語言的3元操作符的
               一個.
               在"引數替換"中,?測試一個變數是否被set了.
        ?        在file globbing中和在正規表示式中一樣匹配任意的單個字元.

        $        變數替換
               1 var1=5
               2 var2=23skidoo
               3
               4 echo $var1     # 5
               5 echo $var2     # 23skidoo
        $        在正規表示式中作為行結束符.
        ${}        引數替換,見9.3節.
        $*,[email protected]    位置引數
        $?        退出狀態變數.$?儲存一個命令/一個函式或者指令碼本身的退出狀態.
        $$        程序ID變數.這個$$變數儲存執行指令碼程序ID
        ()        命令組.如:
               1 (a=hello;echo $a)
               注意:在()中的命令列表,將作為一個子shell來執行.
               在()中的變數,由於是在子shell中,所以對於指令碼剩下的部分是不可用的.
               如:
               1 a=123
               2 ( a=321; )          
               3
               4 echo "a = $a"   # a = 123
               5 # 在圓括號中a變數,更像是一個區域性變數.

               用在陣列初始化,如:
               1 Array=(element1,element2,element3)

        {xxx,yyy,zzz…}
               大括號擴充套件,如:
               1 cat {file1,file2,file3} > combined_file
               2 # 把file1,file2,file3連線在一起,並且重定向到combined_file中.
               3
               4
               5 cp file22.{txt,backup}
               6 # 拷貝"file22.txt" 到"file22.backup"中

               一個命令可能會對大括號中的以逗號分割的檔案列表起作用[1]. file globbing將對
               大括號中的檔名作擴充套件.
               注意: 在大括號中,不允許有空白,除非這個空白是有意義的.
               echo {file1,file2}/ :{/ A," B",’ C’}
               file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C
        {}        程式碼塊.又被稱為內部組.事實上,這個結構建立了一個匿名的函式.但是與函式不同的
               是,在其中宣告的變數,對於指令碼其他部分的程式碼來說還是可見的.如:
               bash$
               {
                   local a;
                   a= 123;
               }
               bash中的local申請的變數只能夠用在函式中.

               1 a=123
               2 { a=321; }
               3 echo "a = $a"   # a = 321   (說明在程式碼塊中對變數a所作的修改,影響了外邊的變數a)
               4
               5 # Thanks, S.C.

               下邊的程式碼展示了在{}結構中程式碼的I/O重定向.

        Example 3-1. 程式碼塊和I/O重定向
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # 從 /etc/fstab中讀行
        3
        4 File=/etc/fstab
        5
        6 {
        7 read line1
        8 read line2
        9 } < $File
        10
        11 echo "First line in $File is:"
        12 echo "$line1"
        13 echo
        14 echo "Second line in $File is:"
        15 echo "$line2"
        16
        17 exit 0
        18
        19 # 現在,你怎麼分析每行的分割域
        20 # 暗示: 使用 awk.
        ################################End Script#########################################

        Example 3-2. 將一個程式碼塊的結果儲存到檔案
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # rpm-check.sh
        3
        4 # 這個指令碼的目的是為了描述,列表,和確定是否可以安裝一個rpm包.
        5 # 在一個檔案中儲存輸出.
        6 #
        7 # 這個指令碼使用一個程式碼塊來展示
        8
        9 SUCCESS=0
        10 E_NOARGS=65
        11
        12 if [ -z "$1" ]
        13 then
        14   echo "Usage: `basename $0` rpm-file"
        15   exit $E_NOARGS
        16 fi  
        17
        18 {
        19   echo
        20   echo "Archive Description:"
        21   rpm -qpi $1       # 查詢說明
        22   echo
        23   echo "Archive Listing:"
        24   rpm -qpl $1       # 查詢列表
        25   echo
        26   rpm -i –test $1  # 查詢rpm包是否可以被安裝
        27   if [ "$?" -eq $SUCCESS ]
        28   then
        29     echo "$1 can be installed."
        30   else
        31     echo "$1 cannot be installed."
        32   fi  
        33   echo
        34 } > "$1.test"       # 把程式碼塊中的所有輸出都重定向到檔案中
        35
        36 echo "Results of rpm test in file $1.test"
        37
        38 # 檢視rpm的man頁來檢視rpm的選項
        39
        40 exit 0
        ################################End Script#########################################
               注意: 與()中的命令不同的是,{}中的程式碼塊將不能正常地開啟一個新shell.[2]

        {} /;    路徑名.一般都在find命令中使用.這不是一個shell內建命令.
               注意: ";"用來結束find命令序列的-exec選項.

        []        test.
               test的表示式將在[]中.
               值得注意的是[是shell內建test命令的一部分,並不是/usr/bin/test中的擴充套件命令
               的一個連線.

        [[]]    test.
               test表示式放在[[]]中.(shell關鍵字)
               具體檢視[[]]結構的討論.

        []        陣列元素
               Array[1]=slot_1
               echo ${Array[1]}

        []        字元範圍
               在正規表示式中使用,作為字元匹配的一個範圍

        (())    數學計算的擴充套件
               在(())結構中可以使用一些數字計算.
               具體參閱((…))結構.

        >&>>&>><
               重定向.
               scriptname >filename 重定向指令碼的輸出到檔案中.覆蓋檔案原有內容.
               command &>filename 重定向stdout和stderr到檔案中
               command >&2    重定向command的stdout到stderr
               scriptname >>filename 重定向指令碼的輸出到檔案中.新增到檔案尾端,如果沒有檔案,
               則建立這個檔案.

               程序替換,具體見"程序替換部分",跟命令替換極其類似.
               (command)>
               <(command)

               <和> 可用來做字串比較
               <和> 可用在數學計算比較

        <<        重定向,用在"here document"

        <<<        重定向,用在"here string"

        <,>        ASCII比較
                1 veg1=carrots
                2 veg2=tomatoes
                3
                4 if [[ "$veg1" < "$veg2" ]]
                5 then
                6   echo "Although $veg1 precede $veg2 in the dictionary,"
                7   echo "this implies nothing about my culinary preferences."
                8 else
                9   echo "What kind of dictionary are you using, anyhow?"
               10 fi

        /<,/>    正規表示式中的單詞邊界.如:
               bash$grep ‘/’ textfile
           
        |        管道.分析前邊命令的輸出,並將輸出作為後邊命令的輸入.這是一種產生命令鏈的
               好方法.
               1 echo ls -l | sh
               2 #  傳遞"echo ls -l"的輸出到shell中,
               3 # 與一個簡單的"ls -l"結果相同.
               4
               5
               6 cat *.lst | sort | uniq
               7 # 合併和排序所有的".lst"檔案,然後刪除所有重複的行.
               
               管道是程序間通訊的一個典型辦法,將一個程序的stdout放到另一個程序的stdin中.
               標準的方法是將一個一般命令的輸出,比如cat或echo,傳遞到一個過濾命令中(在這個
               過濾命令中將處理輸入),得到結果,如:
               cat $filename1 | $filename2 | grep $search_word

               當然輸出的命令也可以傳遞到指令碼中.如:
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # uppercase.sh : 修改輸出,全部轉換為大寫
        3
        4 tr ‘a-z’ ‘A-Z’
        5 #  字元範圍必須被""引用起來
        6 # 來阻止產生單字元的檔名.
        7
        8 exit 0
        ################################End Script#########################################
               
               現在讓我們輸送ls -l的輸出到一個指令碼中.
               bash$ ls -l | ./uppercase.sh
               -RW-RW-R–    1 BOZO  BOZO       109 APR  7 19:49 1.TXT
               -RW-RW-R–    1 BOZO  BOZO       109 APR 14 16:48 2.TXT
               -RW-R–R–    1 BOZO  BOZO       725 APR 20 20:56 DATA-FILE

               注意:管道中的一個程序的stdout必須被下一個程序作為stdin讀入.否則,資料流會阻
               塞,並且管道將產生非預期的行為.
               如:
               1 cat file1 file2 | ls -l | sort
               2 #從"cat file1 file2"中的輸出並沒出現

               作為子程序的執行的管道,不能夠改變指令碼的變數.
               1 variable="initial_value"
               2 echo "new_value" | read variable
               3 echo "variable = $variable"            #variable = initial_value
               如果管道中的某個命令產生了一個異常,並中途失敗,那麼這個管道將過早的終止.
               這種行為被叫做a broken pipe,並且這種狀態下將傳送一個SIGPIPE訊號.

        >|        強制重定向(即使設定了noclobber選項–就是-C選項).這將強制的覆蓋一個現存檔案.

        ||        或-邏輯操作.

        &        後臺執行命令.一個命令後邊跟一個&,將表示在後臺執行.
               bash$sleep 10 &
               [1] 850
               [1]    Done            sleep 10
               在一個指令碼中,命令和迴圈都可能執行在後臺.

        Example 3-3. 在後臺執行一個迴圈
        ################################Start Script#######################################
        1 #!/bin/bash
        2 #background-loop.sh
        3
        4 for i in 1 2 3 4 5 6 7 8 9 10                #第一個迴圈
        5 do
        6    echo -n "$i"
        7 done&                        #在後臺執行這個迴圈
        8                                #在第2個迴圈之後,將在某些時候執行.
        9
        10 echo    #這個’echo’某些時候將不會顯示.
        11
        12 for i in 11 12 13 14 15 16 17 18 19 20        #第二個迴圈
        13 do
        14     echo -n "$i"
        15 done
        16
        17 echo    #這個’echo’某些時候將不會顯示.
        18
        19 #——————————————————–
        20
        21 #期望的輸出應該是
        22 #1 2 3 4 5 6 7 8 9 10
        23 #11 12 13 14 15 16 17 18 19 20
        24
        25 #然而實際的結果有可能是
        26 #11 12 13 14 15 16 17 18 19 20
        27 #1 2 3 4 5 6 7 8 9 10 bozo $
        28 #(第2個’echo’沒執行,為什麼?)
        29
        30 #也可能是
        31 #1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
        32 #(第1個’echo’沒執行,為什麼?)
        33
        34 #非常少見的執行結果,也有可能是:
        35 #11 12 13 1 2 3 4 5 6 7 8 9 10 14 15 16 17 18 19 20
        36 #前臺的迴圈先於後臺的執行
        37
        38 exit 0
        39
        40 #    Nasimuddin Ansari 建議加一句 sleep 1
        41 #    在 6行和14行的 echo -n "$i"之後加
        42 #    將看到一些樂趣
        ################################End Script#########################################
               注意:在一個指令碼內後臺執行一個命令,有可能造成這個指令碼的掛起,等待一個按鍵
                   響應.幸運的是,我們可以在Example 11-24附近,看到這個問題的解決辦法.

        &&        與-邏輯操作.

        –        選項,字首.在所有的命令內如果想使用選項引數的話,前邊都要加上"-".

               COMMAND -[Option1][Option2][…]
               ls -al
               sort -dfu $filename
               set — $variable

                1 if [ $file1 -ot $file2 ]
                2 then
                3   echo "File $file1 is older than $file2."
                4 fi
                5
                6 if [ "$a" -eq "$b" ]
                7 then
                8   echo "$a is equal to $b."
                9 fi
               10
               11 if [ "$c" -eq 24 -a "$d" -eq 47 ]
               12 then
               13   echo "$c equals 24 and $d equals 47."
               14 fi

        –        用於重定向 stdin 或 stdout.

        ################################Start Script#######################################
        1 (cd /source/directory && tar cf – . ) | (cd /dest/directory && tar xpvf -)
        2 # 從一個目錄移動整個目錄樹到另一個目錄
        3 # [courtesy Alan Cox , with a minor change]
        4
        5 # 1) cd /source/directory    源目錄
        6 # 2) &&                      與操作,如果cd命令成功了,那麼就執行下邊的命令
        7 # 3) tar cf – .              ‘c’建立一個新文件,’f’後邊跟’-‘指定目標檔案作為stdout
        8 #                            ‘-‘後邊的’f'(file)選項,指明作為stdout的目標檔案.
        9 #                            並且在當前目錄(‘.’)執行.
        10 # 4) |                       管道…
        11 # 5) ( … )                 一個子shell
        12 # 6) cd /dest/directory      改變當前目錄到目標目錄.
        13 # 7) &&                      與操作,同上.
        14 # 8) tar xpvf –              ‘x’解檔,’p’保證所有權和檔案屬性,
        15 #                            ‘v’發完整訊息到stdout
        16 #                            ‘f’後邊跟’-‘,從stdin讀取資料
        17 #
        18 #                            注意:’x’ 是一個命令, ‘p’, ‘v’, ‘f’ 是選項.
        19 # Whew!
        20
        21
        22
        23 # 更優雅的寫法應該是
        24 #   cd source/directory
        25 #   tar cf – . | (cd ../dest/directory; tar xpvf -)
        26 #
        27 #     當然也可以這麼寫:
        28 # cp -a /source/directory/* /dest/directory
        29 #     或者:
        30 # cp -a /source/directory/* /source/directory/.[^.]* /dest/directory
        31 #     如果在/source/directory中有隱藏檔案的話.
        ################################End Script#########################################

        ################################Start Script#######################################
        1 bunzip2 linux-2.6.13.tar.bz2 | tar xvf –
        2 # –未解壓的tar檔案–    | –然後把它傳遞到"tar"中–
        3 # 如果 "tar" 沒能夠正常的處理"bunzip2",
        4 # 這就需要使用管道來執行2個單獨的步驟來完成它.
        5 # 這個練習的目的是解檔"bzipped"的kernel原始檔.
        ################################End Script#########################################
               注意:在上邊這個例子中’-‘不太象是bash的操作符,而更像是tar的引數.
               bash$echo "whatever" | cat –
               whatever

               在需要一個檔名的地方,-重定向輸出到stdout(如在tar和cf命令中),或者從
               stdin中接受輸入,而不是從一個檔案中接受輸入.這是在管道中作為一個過濾
               器,來使用檔案定位工具的一種辦法.
               bash$file
               用法: file [-bciknvzl] [-f namefile] [-m magicfiles] file…
               上邊這個例子file將會出錯,提示你如何使用file命令.

               新增一個"-"將得到一個更有用的結果.這將使得shell等待使用者輸入.
               bash$file –
               abc
               standard input:                    ASCII text

               bash$file –
               #!/bin/bash
               standard input:                    Bourn-Again shell script tesxt executable

               現在命令從stdin中接受了輸入,並分析它.

               "-"常用於管道後邊的命令,具體參看33.7節,來看使用技巧.
               使用diff命令來和另一個檔案的一部分進行比較.
               grep Linux file1 | diff file2 –

               最後,一個真實世界的使用tar命令的例子.

        Example 3-4. 備份最後一天所有修改的檔案.
        ################################Start Script#######################################
        1 #!/bin/bash
        2
        3 #  在一個"tarball"中(經過tar和gzip處理過的檔案)
        4 # 備份最後24小時當前目錄下d所有修改的檔案.
        5
        6 BACKUPFILE=backup-$(date %m-%d-%Y)
        7 #                 在備份檔案中嵌入時間.
        8 #                 Thanks, Joshua Tschida, for the idea.
        9 archive=${1:-$BACKUPFILE}
        10 #  如果在命令列中沒有指定備份檔案的檔名,
        11 # 那麼將預設使用"backup-MM-DD-YYYY.tar.gz".
        12
        13 tar cvf – `find . -mtime -1 -type f -print` > $archive.tar
        14 gzip $archive.tar
        15 echo "Directory $PWD backed up in archive file /"$archive.tar.gz/"."
        16
        17
        18 #  Stephane Chazelas指出上邊程式碼,
        19 # 如果在發現太多的檔案的時候,或者是如果檔案
        20 # 名包括空格的時候,將執行失敗.
        21
        22 # Stephane Chazelas建議使用下邊的兩種程式碼之一
        23 # ——————————————————————-
        24 #   find . -mtime -1 -type f -print0 | xargs -0 tar rvf "$archive.tar"
        25 #      使用gnu版本的find.
        26
        27
        28 #   find . -mtime -1 -type f -exec tar rvf "$archive.tar" ‘{}’ /;
        29 #         對於其他風格的UNIX便於移植,但是比較慢.
        30 # ——————————————————————-
        31
        32
        33 exit 0
        ################################End Script#########################################

               注意:以"-"開頭的檔名在使用"-"作為重定向操作符的時候,可能會產生問題.
               應該寫一個指令碼來檢查這個問題,並給這個檔案加上合適的字首.如:
               ./-FILENAME, $PWD/-FILENAME,或$PATHNAME/-FILENAME.

               如果變數的值以"-"開頭,可能也會引起問題.
               1 var="-n"
               2 echo $var
               3 #具有"echo -n"的效果了,這樣什麼都不會輸出的.

        –        之前工作的目錄."cd -"將回到之前的工作目錄,具體請參考"$OLDPWD"環境變數.
               注意:一定要和之前討論的重定向功能分開,但是隻能依賴上下文區分.

        –        算術減號.

        =        算術等號,有時也用來比較字串.
               1 a=28
               2 echo $a   # 28

               算術加號,也用在正規表示式中.
               選項,對於特定的命令來說使用" "來開啟特定的選項,用"-"來關閉特定的選項.

        %        算術取模運算.也用在正規表示式中.

        ~        home目錄.相當於$HOME變數.~bozo是bozo的home目錄,並且ls ~bozo將列出其中的
               內容. ~/就是當前使用者的home目錄,並且ls ~/將列出其中的內容,如:
               bash$ echo ~bozo
               /home/bozo

               bash$ echo ~
               /home/bozo

               bash$ echo ~/
               /home/bozo/

               bash$ echo ~:
               /home/bozo:

               bash$ echo ~nonexistent-user
               ~nonexistent-user

        ~        當前工作目錄,相當於$PWD變數.

        ~-        之前的工作目錄,相當於$OLDPWD內部變數.

        =~        用於正規表示式,這個操作將在正規表示式匹配部分講解,只有version3才支援.

        ^        行首,正規表示式中表示行首."^"定位到行首.

        控制字元
               修改終端或文字顯示的行為.控制字元以CONTROL key組合.
               控制字元在指令碼中不能正常使用.
               Ctl-B        游標後退,這應該依賴於bash輸入的風格,預設是emacs風格的.
               Ctl-C        Break,終止前臺工作.
               Ctl-D        從當前shell登出(和exit很像)
                           "EOF"(檔案結束符).這也能從stdin中終止輸入.
                           在console或者在xterm window中輸入的時候,Ctl-D將刪除游標下字元.
                           當沒有字元時,Ctrl-D將退出當前會話.在xterm window也有關閉視窗
                           的效果.
               Ctl-G        beep.在一些老的終端,將響鈴.
               Ctl-H        backspace,刪除游標前邊的字元.如:
                            1 #!/bin/bash
                            2 # 在一個變數中插入Ctl-H
                            3
                            4 a="^H^H"                  # 兩個 Ctl-H (backspaces).
                            5 echo "abcdef"             # abcdef
                            6 echo -n "abcdef$a "       # abcd f
                            7 # 注意結尾的空格 ^              ^ 兩個 twice.
                            8 echo -n "abcdef$a"        # abcdef
                            9 #  結尾沒有空格             沒有 backspace 的效果了(why?).
                           10                           # 結果並不像期望的那樣
                           11 echo; echo
               Ctl-I        就是tab鍵.
               Ctl-J        新行.
               Ctl-K        垂直tab.(垂直tab?新穎,沒聽過)
                           作用就是刪除游標到行尾的字元.
               Ctl-L        clear,清屏.
               Ctl-M        回車
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # Thank you, Lee Maschmeyer, for this example.
        3
        4 read -n 1 -s -p $’Control-M leaves cursor at beginning of this line. Press Enter. /x0d’
        5                                   #當然,’0d’就是二進位制的回車.
        6 echo >&2   #  ‘-s’引數使得任何輸入都不將回顯出來
        7            # 所以,明確的重起一行是必要的.
        8
        9 read -n 1 -s -p $’Control-J leaves cursor on next line. /x0a’
        10 echo >&2   #  Control-J 是換行.
        11
        12 ###
        13
        14 read -n 1 -s -p $’And Control-K/x0bgoes straight down.’
        15 echo >&2   #  Control-K 是垂直製表符.
        16
        17 # 關於垂直製表符效果的一個更好的例子見下邊:
        18
        19 var=$’/x0aThis is the bottom line/x0bThis is the top line/x0a’
        20 echo "$var"
        21 #  這句與上邊的例子使用的是同樣的辦法,然而:
        22 echo "$var" | col
        23 #  這將造成垂直製表符右邊的部分在左邊部分的上邊.
        24 #  這也解釋了為什麼我們要在行首和行尾加上一個換行符–
        25 # 來避免一個混亂的螢幕輸出.
        26
        27 # Lee Maschmeyer的解釋:
        28 # ———————
        29 #  In the [first vertical tab example] . . . the vertical tab
        29 #  在這裡[第一個垂直製表符的例子中] . . . 這個垂直製表符
        30 # makes the printing go straight down without a carriage return.
        31 #  This is true only on devices, such as the Linux console,
        32 # that can’t go "backward."
        33 #  The real purpose of VT is to go straight UP, not down.
        34 #  It can be used to print superscripts on a printer.
        34 #  它可以用來在一個印表機上列印上標.
        35 #  col的作用,可以用來模仿VT的合適的行為.
        36
        37 exit 0
        ################################End Script#########################################
               Ctl-Q        繼續(等價於XON字元),這個繼續的標準輸入在一個終端裡
               Ctl-S        掛起(等價於XOFF字元),這個被掛起的stdin在一個終端裡,用Ctl-Q恢復
               Ctl-U        刪除游標到行首的所有字元,在某些設定下,刪除全行.
               Ctl-V        當輸入字元時,Ctl-V允許插入控制字元.比如,下邊2個例子是等價的
                           echo -e ‘/x0a’
                           echo
                           Ctl-V在文字編輯器中十分有用,在vim中一樣.
               Ctl-W        刪除當前游標到前邊的最近一個空格之間的字元.
                           在某些設定下,刪除到第一個非字母或數字的字元.
               Ctl-Z        終止前臺工作.
               
        空白部分
               分割命令或者是變數.包括空格,tab,空行,或任何它們的組合.
               在一些特殊情況下,空白是不允許的,如變數賦值時,會引起語法錯誤.
               空白行在指令碼中沒有效果.
               "$IFS",對於某些命令輸入的特殊變數分割域,預設使用的是空白.
               如果想保留空白,使用引用.

        注意事項:
        [1]        shell做大括號的命令擴充套件.但是命令本身需要對擴充套件的結果作處理.
        [2]        例外:在pipe中的一個大括號中的程式碼段可能執行在一個子shell中.
               1 ls | { read firstline; read secondline; }
               2 #  錯誤,在打括號中的程式碼段,將執行到子shell中.
               3 # 所以ls的輸出將不能傳遞到程式碼塊中.
               4 echo "First line is $firstline; second line is $secondline"  # 不能工作
               5
               6 # Thanks, S.C.
        [3]        換行符也被認為是空白.這也解釋了為什麼一個空行也會被認為是空白.

        第4章 變數和引數的介紹
        ======================

        4.1 變數替換
        ————
        $        變數替換操作符
               只有在變數被宣告,賦值,unset或exported或者是在變數代表一個signal的時候,
               變數才會是以本來的面目出現在指令碼里.變數在被賦值的時候,可能需要使用"=",
               read狀態或者是在迴圈的頭部.
               在""中還是會發生變數替換,這被叫做部分引用,或叫弱引用.而在”中就不會發生變
               量替換,這叫做全引用,也叫強引用.具體見第5章的討論.

               注意:$var與${var}的區別,不加{},在某些上下文將引起錯誤,為了安全,使用2.
                   具體見9.3節 引數替換.

        Example 4-1. 變數賦值和替換
        ################################Start Script#######################################
        1 #!/bin/bash
        2
        3 # 變數賦值和替換
        4
        5 a=375
        6 hello=$a
        7
        8 #————————————————————————-
        9 # 強烈注意,在賦值的前後一定不要有空格.
        10 # 如果有空格會發生什麼?
        11
        12 #  如果"VARIABLE =value",
        13 #              ^
        14 # 指令碼將嘗試執行一個"VARIABLE"的命令,帶著一個"=value"引數.
        15
        16 #  如果"VARIABLE= value",
        17 #               ^
        18 # script tries to run "value" command with
        18 # 指令碼將嘗試執行一個"value"的命令,帶著
        19 # the environmental variable "VARIABLE" set to "".
        19 # 一個被賦成""值的環境變數"VARIABLE".
        20 #————————————————————————-
        21
        22
        23 echo hello    # 沒有變數引用,不過是個hello字串
        24
        25 echo $hello
        26 echo ${hello} # 同上
        27
        28 echo "$hello"
        29 echo "${hello}"
        30
        31 echo
        32
        33 hello="A B  C   D"
        34 echo $hello   # A B C D
        35 echo "$hello" # A B  C   D
        36 # 就象你看到的echo $hello   和    echo "$hello"   將給出不同的結果.
        37 #                                      ^      ^
        38 # Quoting a variable preserves whitespace.
        38 # 引用一個變數將保留其中的空白,當然,如果是變數替換就不會保留了.
        39
        40 echo
        41
        42 echo ‘$hello’  # $hello
        43 #    ^      ^
        44 # 全引用的作用
        45 # 將導致"$"變成一個單獨的字元.
        46
        47 # 注意兩種引用不同的效果
        48
        49
        50 hello=    # 設定為空值
        51 echo "/$hello (null value) = $hello"
        52 #  注意設定一個變數為空,與unset它,不是一回事,雖然看起來一樣
        53 #
        54
        55 # ————————————————————–
        56
        57 #  可以在同一行上設定多個變數.
        58 # 要以空白分隔
        59 #  小心,這會降低可讀性,和可移植性.
        60
        61 var1=21  var2=22  var3=$V3
        62 echo
        63 echo "var1=$var1   var2=$var2   var3=$var3"
        64
        65 # 在老版本的"sh"上,可能會有問題.
        66
        67 # ————————————————————–
        68
        69 echo; echo
        70
        71 numbers="one two three"
        72 #           ^   ^
        73 other_numbers="1 2 3"
        74 #               ^ ^
        75 #  如果變數中有空白,那麼引用就必要了.
        76 #
        77 echo "numbers = $numbers"
        78 echo "other_numbers = $other_numbers"   # other_numbers = 1 2 3
        79 echo
        80
        81 echo "uninitialized_variable = $uninitialized_variable"
        82 # Uninitialized變數為空值(根本就沒賦值).
        83 uninitialized_variable=   #  宣告,但是沒被初始化
        84                           # 其實和前邊設定為空值得作用是一樣的.
        85 echo "uninitialized_variable = $uninitialized_variable"
        86                           # 還是一個空值
        87
        88 uninitialized_variable=23       # 賦值
        89 unset uninitialized_variable    # Unset it.
        90 echo "uninitialized_variable = $uninitialized_variable"
        91                                 # 還是空值
        92 echo
        93
        94 exit 0
        ################################End Script#########################################
        注意: 一個空值變數,或者是根本就沒宣告的變數,在賦值之前使用它可能會引起問題.
               但是還是可以用來做算術運算
        ################################Start Script#######################################
        1 echo "$uninitialized"                                # (blank line)
        2 let "uninitialized = 5"                             # Add 5 to it.
        3 echo "$uninitialized"                                # 5
        4
        5 #  結論:
        6 #  對於一個空值變數在做算術操作的時候,就好像它的值為0一樣.
        8 #  This is undocumented (and probably non-portable) behavior.
        7 #  這並沒被文件化(可能是不可移植)的行為.
        ################################End Script#########################################
        具體參考    Example 11-21

        4.2 變數賦值
        ————
        =        賦值操作符(前後都不能有空白)
               不要與-eq混淆,那個是test,並不是賦值.
               注意,=也可被用來做test操作,這依賴於上下文.

        Example 4-2. 一般的變數賦值
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # "裸體"變數
        3
        4 echo
        5
        6 # 變數什麼時候是"裸體"的,比如前邊少了$的時候.
        7 # 當它被賦值的時候,而不是被引用的時候.
        8
        9 # 賦值
        10 a=879
        11 echo "The value of /"a/" is $a."
        12
        13 # 使用let賦值
        14 let a=16 5
        15 echo "The value of /"a/" is now $a."
        16
        17 echo
        18
        19 # 在for迴圈中
        20 echo -n "Values of /"a/" in the loop are: "
        21 for a in 7 8 9 11
        22 do
        23   echo -n "$a "
        24 done
        25
        26 echo
        27 echo
        28
        29 # 在read命令狀態中
        30 echo -n "Enter /"a/" "
        31 read a
        32 echo "The value of /"a/" is now $a."
        33
        34 echo
        35
        36 exit 0
        ################################End Script#########################################

        Example 4-3. 變數賦值,一般的和比較特殊的
        ################################Start Script#######################################
        1 #!/bin/bash
        2
        3 a=23              # Simple case
        4 echo $a
        5 b=$a
        6 echo $b
        7
        8 # 現在讓我們來點小變化
        9
        10 a=`echo Hello!`   # 把echo命令的結果傳給變數a
        11 echo $a
        12 #  注意,如果在命令擴充套件結構中使用一個(!)的話,在命令列中將不能工作
        13 # 因為這觸發了Bash的"歷史機制".
        14 #  但是,在校本里邊使用的話,歷史功能是被關閉的,所以就能夠正常執行.
        15
        16
        17 a=`ls -l`         # 把ls -l的結果給a
        18 echo $a           # 別忘了,這麼引用的話,ls的結果中的所有空白部分都沒了(包括換行)
        19 echo
        20 echo "$a"         # 這麼引用就正常了,保留了空白
        21                   # (具體參閱章節"引用")
        22
        23 exit 0
        ################################End Script#########################################
        使用$(…)機制進行的變數賦值(除去使用“來賦值的另外一種新方法).事實上這兩種方法都是
        命令替換的一種形式.
        # 來自於/ect/rc.d/rc.local
        R=$(cat /ect/redhat-release)
        arch=$(uname -m)

        4.3 Bash變數是不分型別的
        ————————
        不像其他程式語言一樣,Bash並不對變數區分"型別".本質上,Bash變數都是字串.
        但是依賴於上下文,Bash也允許比較操作和算術操作.決定這些的關鍵因素就是,變數中的值
        是否只有數字.

        Example 4-4 整型還是string?
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # int-or-string.sh: 整形還是string?
        3
        4 a=2334                   # 整型
        5 let "a = 1"
        6 echo "a = $a "           # a = 2335
        7 echo                     # 還是整型
        8
        9
        10 b=${a/23/BB}             # 將23替換成BB
        11                          # 這將把b變數從整型變為string
        12 echo "b = $b"            # b = BB35
        13 declare -i b             # 即使使用declare命令也不會對此有任何幫助,9.4節有解釋
        14 echo "b = $b"            # b = BB35
        15
        16 let "b = 1"             # BB35 1 =
        17 echo "b = $b"            # b = 1
        18 echo
        19
        20 c=BB34
        21 echo "c = $c"            # c = BB34
        22 d=${c/BB/23}             # S將BB替換成23
        23                          # 這使得$d變為一個整形
        24 echo "d = $d"            # d = 2334
        25 let "d = 1"             # 2334 1 =
        26 echo "d = $d"            # d = 2335
        27 echo
        28
        29 # 關於空變數怎麼樣?
        30 e=""
        31 echo "e = $e"            # e =
        32 let "e = 1"             # 算術操作允許一個空變數?
        33 echo "e = $e"            # e = 1
        34 echo                     # 空變數將轉換成一個整型變數
        35
        36 # 關於未宣告的變數怎麼樣?
        37 echo "f = $f"            # f =
        38 let "f = 1"             # 算術操作允許麼?
        39 echo "f = $f"            # f = 1
        40 echo                     # 未宣告的變數將轉換成一個整型變數
        41
        42
        43
        44 # 所以說Bash中的變數都是無型別的.
        45
        46 exit 0
        ################################End Script#########################################

        4.4 特殊的變數型別
        ——————
        local variables
               這種變數只有在程式碼塊或者是函式中才可見(具體見23.2和23章)
        environmental variables
               這種變數將改變使用者介面和shell的行為.

               在一般的上下文中,每個程序都有自己的環境,就是一組保持程序可能引用的資訊的
               變數.這種情況下,shell於一個一般程序是相同的.

               每次當shell啟動時,它都將建立自己的環境變數.更新或者新增新的環境變數,將導
               致shell更新它的環境,同時也會影響所有繼承自這個環境的所有子程序(由這個命令
               導致的).

               注意:分配給環境變數的空間是受限的.建立太多的環境變數將引起空間溢位,這會引
               起問題.
               關於eval命令,具體見第11章
               bash$ eval "`seq 10000 | sed -e ‘s/.*/export var&=ZZZZZZZZZZZZZZ/’`"
               bash$ du
               bash: /usr/bin/du: Argument list too long

               如果一個指令碼設定了環境變數,需要export它,來通知本指令碼的環境,這是export
               命令的功能,關於export命令,具體見11章.
               
               指令碼只能對它產生的子程序export變數.一個從命令列被呼叫的指令碼export的變數,將
               不能影響呼叫這個指令碼的那個命令列shell的環境.

        positional parameters
               就是從命令列中傳進來的引數,$0, $1, $2, $3…

               $0就是指令碼檔案的名字,$1是第一個引數,$2為第2個…,參見[1](有$0的說明),$9
               以後就需要打括號了,如${10},${11},${12}…
               兩個值得注意的變數$*和[email protected](第9章有具體的描述),表示所有的位置引數.

        Example 4-5 位置引數
        ################################Start Script#######################################
        1 #!/bin/bash
        2
        3 # 作為用例,呼叫這個指令碼至少需要10個引數,如
        4 # ./scriptname 1 2 3 4 5 6 7 8 9 10
        5 MINPARAMS=10
        6
        7 echo
        8
        9 echo "The name of this script is /"$0/"."
        10 # 新增./是為了當前目錄
        11 echo "The name of this script is /"`basename $0`/"."
        12 # 去掉目錄資訊,具體見’basename’命令
        13
        14 echo
        15
        16 if [ -n "$1" ]              # 測試變數被被引用
        17 then
        18  echo "Parameter #1 is $1"  # "#"沒被轉義
        19 fi
        20
        21 if [ -n "$2" ]
        22 then
        23  echo "Parameter #2 is $2"
        24 fi
        25
        26 if [ -n "$3" ]
        27 then
        28  echo "Parameter #3 is $3"
        29 fi
        30
        31 # …
        32
        33
        34 if [ -n "${10}" ]  # 大於9的引數必須出現在{}中.
        35 then
        36  echo "Parameter #10 is ${10}"
        37 fi
        38
        39 echo "———————————–"
        40 echo "All the command-line parameters are: "$*""
        41
        42 if [ $# -lt "$MINPARAMS" ]        #$#是傳到指令碼里的位置引數的個數
        43 then
        44   echo
        45   echo "This script needs at least $MINPARAMS command-line arguments!"
        46 fi  
        47
        48 echo
        49
        50 exit 0
        ################################End Script#########################################
               {}標記法是一種很好的使用位置引數的方法.這也需要間接引用(見Example 34-2)
               1 args=$#           # 位置引數的個數
               2 lastarg=${!args}
               3 # 或:       lastarg=${!#}
               4 # 注意 lastarg=${!$#} 將報錯

               一些指令碼可能會依賴於使用不同的呼叫名字,而表現出不同的行為,這樣一般都需要
               判斷$0,而其他的名字都是通過ln命令產生的連結.(具體參見Example 12-2)

               如果指令碼需要一個命令列引數,而呼叫的時候,沒用這個引數,這就有可能造成分配一個
               空變數,這樣估計就會引起問題.一種解決辦法就是在這個位置引數,和相關的變數後
               邊,都新增一個額外的字元.具體見下邊的例子.
        ################################Start Script#######################################
        1 variable1_=$1_  # 而不是 variable1=$1
        2 # 這將阻止一個錯誤,即使在呼叫時沒使用這個位置引數.
        3
        4 critical_argument01=$variable1_
        5
        6 # 這個擴充套件的字元是可以被消除掉的,就像這樣.
        7 variable1=${variable1_/_/}
        8 # 副作用就是$variable1_多了一個下劃線
        9 # 這裡使用了一個引數替換模版(後邊會有具體的討論)
        10 # (Leaving out the replacement pattern results in a deletion.)
        10 # (在一個刪除動作中,節省了一個替換模式)
        11
        12
        13 # 一個解決這種問題的更簡單的做法就是,判斷一下這個位置引數是否傳遞下來了
        14 if [ -z $1 ]
        15 then
        16   exit $E_MISSING_POS_PARAM
        17 fi
        18
        19
        20 #  但是上邊的方法將可能產生一個意外的副作用
        21 #  引數替換的更好的辦法應該是:
        22 #         ${1:-$DefaultVal}
        23 #  具體察看"Parameter Substition"節
        24 # 在第9章
        ################################End Script#########################################
               

        Example 4-6 wh,whois節點名字查詢
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # ex18.sh
        3
        4 # Does a ‘whois domain-name’ lookup on any of 3 alternate servers:
        5 #                    ripe.net, cw.net, radb.net
        6
        7 # 把這個指令碼重新命名為’wh’,然後放到/usr/local/bin下
        8
        9 # 需要3個符號連結
        10 # ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
        11 # ln -s /usr/local/bin/wh /usr/local/bin/wh-cw
        12 # ln -s /usr/local/bin/wh /usr/local/bin/wh-radb
        13
        14 E_NOARGS=65
        15
        16
        17 if [ -z "$1" ]
        18 then
        19   echo "Usage: `basename $0` [domain-name]"
        20   exit $E_NOARGS
        21 fi
        22
        23 # Check script name and call proper server.
        23 # 檢查指令碼名字,然後呼叫合適的伺服器
        24 case `basename $0` in    # Or:    case ${0##*/} in
        25     "wh"     ) whois [email protected];;
        26     "wh-ripe") whois [email protected];;
        27     "wh-radb") whois [email protected];;
        28     "wh-cw"  ) whois [email protected];;
        29     *        ) echo "Usage: `basename $0` [domain-name]";;
        30 esac
        31
        32 exit $?
        ################################End Script#########################################

        shift        shift命令重新分配位置引數,其實就是向左移動一個位置.
           $1 <— $2, $2 <— $3, $3 <— $4, 等等.
               老的$1將消失,但是$0(指令碼名)是不會改變的.如果你使用了大量的位置引數,那麼
               shift命令允許你存取超過10個引數.雖然{}表示法也允許這樣.

        Example 4-7 使用shift
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # 使用’shift’來穿過所有的位置引數.
        3
        4 #  把這個指令碼命名為shft,
        5 # 並且使用一些引數來呼叫它,如:
        6 #          ./shft a b c def 23 skidoo
        7
        8 until [ -z "$1" ]  # 知道所有引數都用光
        9 do
        10   echo -n "$1 "
        11   shift
        12 done
        13
        14 echo               # 額外的換行.
        15
        16 exit 0
        ################################End Script#########################################
               在將引數傳遞到函式中時,shift的工作方式也基本差不多.具體見Example 33-15

        注意事項:
        [1]        程序呼叫設定$0引數的指令碼.一般的,這個引數就是指令碼名字.具體察看execv的man頁.

        第5章 引用(翻譯的可能有問題,特指引號)
        ======================================
        引號的特殊效果就是,保護字串中的特殊字元不被shell或者是shell指令碼重新解釋或者擴充套件.
        (我們這裡所說的"特殊"指的是一些字元在shell中具有的特殊意義,比如*)
        如:
        bash$ ls -l [Vv]*
        -rw-rw-r–    1 bozo  bozo       324 Apr  2 15:05 VIEWDATA.BAT
        -rw-rw-r–    1 bozo  bozo       507 May  4 14:25 vartrace.sh
        -rw-rw-r–    1 bozo  bozo       539 Apr 14 17:11 viewdata.sh
           
        bash$ ls -l ‘[Vv]*’
        ls: [Vv]*: No such file or directory

        在我們一般的生活中,引號內的內容往往有特殊的含義,而在Bash中,當我們引用一個字串,
        我們是保護它的字面含義.

        特定的程式和工具能夠重新解釋或擴充套件特殊的字元.引用的一個重要的作用就是保護命令列中
        的引數,但還是允許正在呼叫的程式來擴充套件它.
        bash$ grep ‘[Ff]irst’ *.txt
        file1.txt:This is the first line of file1.txt.
        file2.txt:This is the First line of file2.txt.

        注意 grep [Ff]irst *.txt在Bash下的行為(其實就是正規表示式麼),[1]

        引用還可以抑制echo命令的換行作用.

        bash$ echo $(ls -l)
        total 8 -rw-rw-r– 1 bozo bozo 130 Aug 21 12:57 t222.sh -rw-rw-r– 1 bozo bozo 78 Aug 21 12:57 t71.sh

        bash$ echo "$(ls -l)"
        total 8
        -rw-rw-r–  1 bozo bozo 130 Aug 21 12:57 t222.sh
        -rw-rw-r–  1 bozo bozo  78 Aug 21 12:57 t71.sh

        5.1 引用變數
        ————
        在一個雙引號中直接使用變數名,一般都是沒有問題的.它阻止了所有在引號中的特殊字元的
        重新解釋–包括變數名[2]–但是$,`和/除外.[3]保留$,作為特殊字元的意義,是為了能夠在雙
        引號中也能夠正常地引用變數("$var").這樣在""中可以使用變數所表達的值(Example 4-1).

        使用""來防止單詞分割.[4]如果在引數列表中使用雙引號,將使得雙引號中的引數作為一個參
        數.即使雙引號中的字串包含多個單詞(也就是包含空白部分),也不會變為多個引數,如:
        1 variable1="a variable containing five words"
        2 COMMAND This is $variable1    # COMMAND將以7個引數來執行
        3 # "This" "is" "a" "variable" "containing" "five" "words"
        4
        5 COMMAND "This is $variable1"  # COMMAND將以1個引數來執行
        6 # "This is a variable containing five words"
        7
        8
        9 variable2=""    # 空值
        10
        11 COMMAND $variable2 $variable2 $variable2        # COMMAND將不帶引數執行
        12 COMMAND "$variable2" "$variable2" "$variable2"  # COMMAND將以3個空引數來執行
        13 COMMAND "$variable2 $variable2 $variable2"      # COMMAND將以1個引數來執行(2空格)
           用雙引號把引數封到echo中是很有必要的,只有在單詞分隔或時保留空白時的時候可能
           有些問題.

        Example 5-1 echo一些詭異的變數
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # weirdvars.sh: echo詭異的變數
        3
        4 var="'(]//{}/$/""
        5 echo $var        # ‘(]/{}$"
        6 echo "$var"      # ‘(]/{}$"    並沒有什麼不同
        7
        8 echo
        9
        10 IFS=’/’
        11 echo $var        # ‘(] {}$"     / 轉換成空格了?明顯和IFS有關係麼!又不傻!
        12 echo "$var"      # ‘(]/{}$"
        13
        14 exit 0
        ################################End Script#########################################

        單引號操作總體上和""很像,但不允許引用變數.因為$的特殊含義被關閉了.在”中除了’,其他
        字元都沒有特殊的含義了.所以單引號比雙引號嚴格.
           因為即使是/,在”中都被關閉了,所以你想在”中顯示’的含義,將得不到預期的效果.
          1 echo "Why can’t I write ‘s between single quotes"
          2
          3 echo
          4
          5 # 一種繞彎的方法
          6 echo ‘Why can’/”t I write ‘"’"’s between single quotes’
          7 #    |——-|  |———-|   |———————–|
          8 # 包含了2個單引號字元,原書好像有錯誤

        注意事項:
        [1]        除非當前目錄下,正好有個叫first的檔案.
        [2]        即使是變數的值也是有副作用的(見下邊)
        [3]        如果在""中包含"!"的話,在命令列中將會出現錯誤.因為這個"!"被當作歷史命令來解釋了.
               在一個指令碼中,這種情況是不會發生的,因為在指令碼中,Bash歷史記錄被關閉了.

               下邊是一些關於"/"一些不協調的行為.
               bash$ echo hello/!
               hello!

               bash$ echo "hello/!"
               hello/!

               bash$ echo -e x/ty
               xty

               bash$ echo -e "x/ty"
               x       y

        [4]        "單詞分隔",在這個上下文中意味著,將一個字串分隔為一些分離的引數.

        5.2 轉義(/)
        ———–
        轉義是一種引用單個字元的方法.一個具有特殊含義的字元前邊放上一個轉義符(/)就告訴shell
        這個字元失去了特殊的含義.
           值得注意的是,在某些特定的命令和工具中,比如echo和sed,轉義符往往會起到相反的效果,
           它反倒有可能引發出這個字元特殊的含義.

        對於特定的轉義符的特殊的含義
        在echo和sed中所使用的
        /n        意味著新的一行
        /r        回車
        /t        tab鍵
        /v        vertical tab(垂直tab),查前邊的Ctl-K
        /b        backspace,查前邊的Ctl-H
        /a        "alert"(如beep或flash)
        /0xx    轉換成8進位制ASCII解碼,等價於oxx

        Example 5-2 轉義符
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # escaped.sh: 轉義符
        3
        4 echo; echo
        5
        6 echo "/v/v/v/v"      # 逐字的列印/v/v/v/v .
        7 # 使用-e選項的echo命令來列印轉義符
        8 echo "============="
        9 echo "VERTICAL TABS"
        10 echo -e "/v/v/v/v"   # Prints 4 vertical tabs.
        11 echo "=============="
        12
        13 echo "QUOTATION MARK"
        14 echo -e "/042"       # 列印" (引號, 8進位制的ASCII 碼就是42).
        15 echo "=============="
        16
        17 # The $’/X’ construct makes the -e option unnecessary.
        17 # 如果使用$’/X’結構,那-e選項就不必要了
        18 echo; echo "NEWLINE AND BEEP"
        19 echo $’/n’           # 新行.
        20 echo $’/a’           # Alert (beep).
        21
        22 echo "==============="
        23 echo "QUOTATION MARKS"
        24 # 版本2以後Bash允許使用$’/nnn’結構
        25 # 注意這種情況,’/nnn/是8進位制
        26 echo $’/t /042 /t’   # Quote (") framed by tabs.
        27
        28 # 當然,也可以使用16進位制的值,使用$’/xhhh’ 結構
        29 echo $’/t /x22 /t’  # Quote (") framed by tabs.
        30
        31 # 早一點的Bash版本允許’/x022’這種形式
        32 echo "==============="
        33 echo
        34
        35
        36 # 分配ASCII字元到變數中
        37 # ———————
        38 quote=$’/042′        # /042是",分配到變數中
        39 echo "$quote This is a quoted string, $quote and this lies outside the quotes."
        40
        41 echo
        42
        43 # Concatenating ASCII chars in a variable.
        43 # 變數中的連續的ASCII char.
        44 triple_underline=$’/137/137/137′  # 137 是8進位制的ASCII 碼’_’.
        45 echo "$triple_underline UNDERLINE $triple_underline"
        46
        47 echo
        48
        49 ABC=$’/101/102/103/010′           # 101, 102, 103 是8進位制的碼A, B, C.
        50 echo $ABC
        51
        52 echo; echo
        53
        54 escape=$’/033′                    # 033 是8進位制碼for escape.
        55 echo "/"escape/" echoes as $escape"
        56 #"escape" echoes as                  沒有變數被輸出
        57
        58 echo; echo
        59
        60 exit 0
        ################################End Script#########################################
           另一個關於$”字串擴充套件結果的例子見Example 34-1

        /"        表達引號本身
               1 echo "Hello"                  # Hello
               2 echo "/"Hello/", he said."    # "Hello", he said.

        /$        $號本身,跟在/$後的變數名,將不能擴充套件
               1 echo "/$variable01"  # 結果是$variable01

        //        /號本身.
               1 echo "//"  # 結果是/
               2
               3 # 相反的 . . .
               4
               5 echo "/"   # 這會出現第2個命令提示符,說白了就是提示你命令不全,你再補個"就
               6             # 好了.如果是在指令碼里,就會給出一個錯誤.

           注意:/的行為依賴於它是否被轉義,被"",或者是否在"命令替換"和"here document"中.
        ################################Start Script#######################################
        1                       #  簡單的轉義和""
        2 echo /z               #  z
        3 echo //z              # /z
        4 echo ‘/z’             # /z
        5 echo ‘//z’            # //z
        6 echo "/z"             # /z
        7 echo "//z"            # /z
        8
        9                       #  命令替換
        10 echo `echo /z`        #  z
        11 echo `echo //z`       #  z
        12 echo `echo ///z`      # /z
        13 echo `echo ////z`     # /z
        14 echo `echo //////z`   # /z
        15 echo `echo ///////z`  # //z
        16 echo `echo "/z"`      # /z
        17 echo `echo "//z"`     # /z
        18
        19                       # Here document
        20 cat <21 /z                      
        22 EOF                   # /z
        23
        24 cat <25 //z                    
        26 EOF                   # /z
        ################################End Script#########################################

           分配給變數的字串的元素也會被轉義,但是隻把一個轉義符分配給變數將會報錯.
        ################################Start Script#######################################
        1 variable=/
        2 echo "$variable"
        3 # Will not work – gives an error message:
        3 # 將不能正常工作- 將給出一個錯誤訊息:
        4 # test.sh: : command not found
        5 # 一個"裸體的" 轉義符將不能夠安全的分配給變數.
        6 #
        7 #  What actually happens here is that the "/" escapes the newline and
        7 #  這裡其實真正發生的是variable=/,這句被shell認為是沒有完成,/被認為是一個續行符
        8 # 這樣,下邊的這句echo,也被認為是上一行的補充.所以,總的來說就是一個非法變數分配
        9
        10 variable=/
        11 23skidoo
        12 echo "$variable"        #  23skidoo
        13                         #  這句就可以使用,因為這是一個合法的變數分配
        14
        15 variable=/
        16 #        /^   轉義一個空格
        17 echo "$variable"        # 顯示空格
        18
        19 variable=//
        20 echo "$variable"        # /
        21
        22 variable=///
        23 echo "$variable"
        24 # 不能正常工作,給出一個錯誤
        25 # test.sh: /: command not found
        26 #
        27 #  第一個轉義符把第2個/轉義了,但是第3個又變成"裸體的"了,
        28 # 與上邊的例子的原因相同
        29
        30 variable=////
        31 echo "$variable"        # //
        32                         # 轉了兩個/
        33                         # 沒問題
        ################################End Script#########################################

        轉義一個空格,在命令列引數列表中將會阻止單詞分隔問題.
        ################################Start Script#######################################
        1 file_list="/bin/cat /bin/gzip /bin/more /usr/bin/less /usr/bin/emacs-20.7"
        2 # 列出的檔案都作為命令的引數.
        3
        4 # Add two files to the list, and list all.
        4 # 加2個檔案到list中,並且列出全部.
        5 ls -l /usr/X11R6/bin/xsetroot /sbin/dump $file_list
        6
        7 echo "————————————————————————-"
        8
        9 # 如果我們轉義2個空格,會發生什麼?
        10 ls -l /usr/X11R6/bin/xsetroot/ /sbin/dump/ $file_list
        11 # 錯誤: 因為前3個路徑名被合併成一個引數傳給了’ls -l’
        12 #        因為2個轉義符阻止了引數(單詞)分離
        ################################End Script#########################################

        轉義符也提供續行功能.一般,每一行都包含一個不同的命令,但如果在行尾加上/,那就會接受
        新行的輸入,作為這一行的補充.
        1 (cd /source/directory && tar cf – . ) | /
        2 (cd /dest/directory && tar xpvf -)
        3 # 重複了 Alan Cox的目錄樹拷貝命令
        4 # 為了增加可讀性分成2行.
        5
        6 # 也可以使用如下方式:
        7 tar cf – -C /source/directory . |
        8 tar xpvf – -C /dest/directory
        9 # 察看下邊的注意事項

        注意:如果一個指令碼以|(管道字元)結束.那麼一個/(轉義符),就不用非加上不可了.
           但是一個好的shell指令碼編寫風格,還是應該在行尾加上/,以增加可讀性.
        ################################Start Script#######################################
        1 echo "foo
        2 bar"
        3 #foo
        4 #bar
        5
        6 echo
        7
        8 echo ‘foo
        9 bar’    # 沒區別
        10 #foo
        11 #bar
        12
        13 echo
        14
        15 echo foo/
        16 bar     # 續行
        17 #foobar
        18
        19 echo
        20
        21 echo "foo/
        22 bar"     # 與上邊一樣,/還是作為續行符
        23 #foobar
        24
        25 echo
        26
        27 echo ‘foo/
        28 bar’     # 由於是強引用,所以/沒被解釋成續行符
        29 #foo/
        30 #bar
        ################################End Script#########################################

        第6章 退出和退出狀態
        ====================
        exit命令被用來結束指令碼,就像C語言一樣.他也會返回一個值來傳給父程序,父程序會判斷是否
        可用.

        每個命令都會返回一個exit狀態(有時候也叫return狀態).成功返回0,如果返回一個非0值,通
        常情況下都會被認為是一個錯誤碼.一個編寫良好的UNIX命令,程式,和工具都會返回一個0作為
        退出碼來表示成功,雖然偶爾也會有例外.

        同樣的,指令碼中的函式和指令碼本身都會返回退出狀態.在指令碼或者是指令碼函式中執行的最後的命
        令會決定退出狀態.在指令碼中,exit nnn命令將會把nnn退出碼傳遞給shell(nnn必須是10進位制數
        0-255).

        當一個指令碼以不帶引數exit來結束時,指令碼的退出狀態就由指令碼中最後執行命令來決定.
        1 #!/bin/bash
        2
        3 COMMAND_1
        4
        5 . . .
        6
        7 # 將以最後的命令來決定退出狀態
        8 COMMAND_LAST
        9
        10 exit $?

        1 #!/bin/bash
        2
        3 COMMAND1
        4
        5 . . .
        6
        7 # 將以最後的命令來決定退出狀態
        8 COMMAND_LAST

        $?讀取最後執行命令的退出碼.函式返回後,$?給出函式最後執行的那條命令的退出碼.這種給
        函式返回值的方法是Bash的方法.對於指令碼來說也一樣.總之,一般情況下,0為成功,非0失敗W.
        Example 6-1 exit/exit狀態
        ################################Start Script#######################################
        1 #!/bin/bash
        2
        3 echo hello
        4 echo $?    # 返回0,因為執行成功
        5
        6 lskdf      # 不認識的命令.
        7 echo $?    # 返回非0值,因為失敗了.
        8
        9 echo
        10
        11 exit 113   # 將返回113給shell.
        12            # To verify this, type "echo $?" after script terminates.
        12            # 為了驗證這個,在指令碼結束的地方使用"echo $?"
        ################################End Script#########################################

        $?對於測試指令碼中的命令的結果特別有用(見Example 12-32和Example 12-17).
        注意: !邏輯非操作,將會反轉test命令的結果,並且這會影響exit狀態.
        Example 6-2 否定一個條件使用!
        ################################Start Script#######################################
        1 true  # true是shell內建命令,什麼事都不做,就是shell返回0
        2 echo "exit status of /"true/" = $?"     # 0
        3
        4 ! true
        5 echo "exit status of /"! true/" = $?"   # 1
        6 # 注意:"!"需要一個空格
        7 #    !true   將導致一個"command not found"錯誤
        8 #
        9 # 如果一個命令以’!’開頭,那麼將使用Bash的歷史機制.就是顯示這個命令被使用的歷史.
        10
        11 true
        12 !true
        13 # 這次就沒有錯誤了.
        14 # 他不過是重複了之前的命令(true).
        ################################End Script#########################################

        注意事項:
           特定的退出碼都有預定的含義(見附錄D),使用者不應該在自己的指令碼中指定他.

        第7章 Tests
        ===========
        每個完整的合理的程式語言都具有條件判斷的功能.Bash具有test命令,不同的[]和()操作,和
        if/then結構.

        7.1 Test結構
        ————
        一個if/then結構可以測試命令的返回值是否為0(因為0表示成功),如果是的話,執行更多命令.

        有一個專用命令"["(左中括號,特殊字元).這個命令與test命令等價,但是出於效率上的考慮,
        它是一個內建命令.這個命令把它的引數作為比較表示式或是檔案測試,並且根據比較的結果,
        返回一個退出碼.

        在版本2.02的Bash中,推出了一個新的[[…]]擴充套件test命令.因為這種表現形式可能對某些語
        言的程式設計師來說更加熟悉.注意"[["是一個關鍵字,並不是一個命令.

        Bash把[[ $a -lt $b ]]看作一個單獨的元素,並且返回一個退出碼.

        ((…))和let…結果也能夠返回一個退出碼,當它們所測試的算術表示式的結果為非0的時候,
        他們的退出碼將返回0.這些算術擴充套件(見第15章)結構被用來做算術比較.
        1 let "1<2" returns 0 (as "1<2" expands to "1")
        2 (( 0 && 1 )) returns 1 (as "0 && 1" expands to "0")

        if命令可以測試任何命令,不僅僅是括號中的條件.
        1 if cmp a b &> /dev/null  # 阻止輸出.
        2 then echo "Files a and b are identical."
        3 else echo "Files a and b differ."
        4 fi
        5
        6 # 非常有用的"if-grep" 結構:
        7 # ————————
        8 if grep -q Bash file
        9 then echo "File contains at least one occurrence of Bash."
        10 fi
        11
        12 word=Linux
        13 letter_sequence=inu
        14 if echo "$word" | grep -q "$letter_sequence"
        15 # "-q"選項是用來阻止輸出
        16 then
        17   echo "$letter_sequence found in $word"
        18 else
        19   echo "$letter_sequence not found in $word"
        20 fi
        21
        22
        23 if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
        24 then echo "Command succeeded."
        25 else echo "Command failed."
        26 fi

        一個if/then結構可以包含多級比較和tests.

        1 if echo "Next *if* is part of the comparison for the first *if*."
        2
        3   if [[ $comparison = "integer" ]]
        4     then (( a < b ))
        5   else
        6     [[ $a < $b ]]
        7   fi
        8
        9 then
        10   echo ‘$a is less than $b’
        11 fi

        Example 7-1 什麼情況下為真?
        ################################Start Script#######################################
         1 #!/bin/bash
         2
         3 #  技巧:
         4 #  如果你不確定一個特定的條件如何判斷.
         5 # 在一個if-test結構中測試它.
         6
         7 echo
         8
         9 echo "Testing /"0/""
        10 if [ 0 ]      # zero
        11 then
        12   echo "0 is true."
        13 else
        14   echo "0 is false."
        15 fi            # 0 is true.
        16
        17 echo
        18
        19 echo "Testing /"1/""
        20 if [ 1 ]      # one
        21 then
        22   echo "1 is true."
        23 else
        24   echo "1 is false."
        25 fi            # 1 is true.
        26
        27 echo
        28
        29 echo "Testing /"-1/""
        30 if [ -1 ]     # -1
        31 then
        32   echo "-1 is true."
        33 else
        34   echo "-1 is false."
        35 fi            # -1 is true.
        36
        37 echo
        38
        39 echo "Testing /"NULL/""
        40 if [ ]        # NULL (控狀態)
        41 then
        42   echo "NULL is true."
        43 else
        44   echo "NULL is false."
        45 fi            # NULL is false.
        46
        47 echo
        48
        49 echo "Testing /"xyz/""
        50 if [ xyz ]    # 字串
        51 then
        52   echo "Random string is true."
        53 else
        54   echo "Random string is false."
        55 fi            # Random string is true.
        56
        57 echo
        58
        59 echo "Testing /"/$xyz/""
        60 if [ $xyz ]   # 測試$xyz是否為null,但是…(明顯沒人定義麼!)
        61               # 只不過是一個未定義的變數
        62 then
        63   echo "Uninitialized variable is true."
        64 else
        65   echo "Uninitialized variable is false."
        66 fi            # Uninitialized variable is false.
        67
        68 echo
        69
        70 echo "Testing /"-n /$xyz/""
        71 if [ -n "$xyz" ]            # 更學究的的檢查
        72 then
        73   echo "Uninitialized variable is true."
        74 else
        75   echo "Uninitialized variable is false."
        76 fi            # Uninitialized variable is false.
        77
        78 echo
        79
        80
        81 xyz=          # 初始化了,但是將其設為空值
        82
        83 echo "Testing /"-n /$xyz/""
        84 if [ -n "$xyz" ]
        85 then
        86   echo "Null variable is true."
        87 else
        88   echo "Null variable is false."
        89 fi            # Null variable is false.
        90
        91
        92 echo
        93
        94
        95 # 什麼時候"flase"為true?
        96
        97 echo "Testing /"false/""
        98 if [ "false" ]              #  看起來"false"只不過是個字串而已.
        99 then
        100   echo "/"false/" is true." # 並且它test的結果就是true.
        101 else
        102   echo "/"false/" is false."
        103 fi            # "false" is true.
        104
        105 echo
        106
        107 echo "Testing /"/$false/""  # 再來一個,未宣告的變數
        108 if [ "$false" ]
        109 then
        110   echo "/"/$false/" is true."
        111 else
        112   echo "/"/$false/" is false."
        113 fi            # "$false" is false.
        114               # 現在我們終於得到了期望的結果
        115
        116 #  如果我們test這個變數"$true"會發生什麼結果?答案是和"$flase"一樣,都為空,因為我
        117 # 們並沒有定義它.
        118 echo
        119
        120 exit 0
        ################################End Script#########################################
        練習.解釋上邊例子的行為(我想我解釋的已經夠清楚了)

        1 if [ condition-true ]
        2 then
        3    command 1
        4    command 2
        5    …
        6 else
        7    # 可選的(如果不需要可以省去)
        8    # 如果原始的條件測試結果是false,那麼新增預設的程式碼來執行.
        9    command 3
        10    command 4
        11    …
        12 fi

        注意:當if和then在一個條件測試的同一行中的話,必須使用";"來終止if表示式.if和then都是
           關鍵字.關鍵字(或者命令)作為一個表示式的開頭,並且在一個新的表示式開始之前,必須
           結束上一個表示式.
           1 if [ -x "$filename" ]; then

        Else if和elif

        elif
           elif是else if的縮減形式.
                1 if [ condition1 ]
                2 then
                3    command1
                4    command2
                5    command3
                6 elif [ condition2 ]
                7 # Same as else if
                8 then
                9    command4
               10    command5
               11 else
               12    default-command
               13 fi

        使用if test condition-true這種形式和if[condition-true]這種形式是等價的.向我們前邊
        所說的"["是test的標記.並且以"]"結束.在if/test中並不應該這麼嚴厲,但是新版本的Bash
        需要它.

        注意:test命令是Bash的內建命令,用來測試檔案型別和比較字串.因此,在Bash指令碼中,test
        並不呼叫/usr/bin/test的二進位制版本(這是sh-utils工具包的一部分).同樣的,[並不呼叫
        /usr/bin/[,被連線到/usr/bin/test.
               bash$ type test
               test is a shell builtin
               bash$ type ‘[‘
               [ is a shell builtin
               bash$ type ‘[[‘
               [[ is a shell keyword
               bash$ type ‘]]’
               ]] is a shell keyword
               bash$ type ‘]’
               bash: type: ]: not found

        Example 7-2 幾個等效命令test,/usr/bin/test,[],和/usr/bin/[
        ################################Start Script#######################################
         1 #!/bin/bash
         2
         3 echo
         4
         5 if test -z "$1"
         6 then
         7   echo "No command-line arguments."
         8 else
         9   echo "First command-line argument is $1."
        10 fi
        11
        12 echo
        13
        14 if /usr/bin/test -z "$1"      # 與內建的test結果相同
        15 then
        16   echo "No command-line arguments."
        17 else
        18   echo "First command-line argument is $1."
        19 fi
        20
        21 echo
        22
        23 if [ -z "$1" ]                # 與上邊程式碼的作用相同
        24 #   if [ -z "$1"                應該工作,但是…
        25 #  Bash相應一個缺少關閉中括號的錯誤訊息.
        26 then
        27   echo "No command-line arguments."
        28 else
        29   echo "First command-line argument is $1."
        30 fi
        31
        32 echo
        33
        34
        35 if /usr/bin/[ -z "$1" ]       # 再來一個,與上邊程式碼的作用相同
        36 # if /usr/bin/[ -z "$1"       # 工作,但是給個錯誤訊息
        37 #                             # 注意:
        38 #                               This has been fixed in Bash, version 3.x.
        38 #                               在ver 3.x上,這個bug已經被Bash修正了.
        39 then
        40   echo "No command-line arguments."
        41 else
        42   echo "First command-line argument is $1."
        43 fi
        44
        45 echo
        46
        47 exit 0
        ###############################End Script#########################################

        [[]]結構比Bash的[]更加靈活,這是一個擴充套件的test命令,從ksh88繼承過來的.
        注意:在[[]]結構中,將沒有檔案擴充套件或者是單詞分離,但是會發生引數擴充套件和命令替換.
           1 file=/etc/passwd
           2
           3 if [[ -e $file ]]
           4 then
           5   echo "Password file exists."
           6 fi
        注意:使用[[]],而不是[],能夠阻止指令碼中的許多邏輯錯誤.比如,儘管在[]中將給出一個錯誤,
           但是&&,||,<>操作還是能夠工作在一個[[]]test之中.
        注意:在if後邊,test命令和[]或[[]]都不是必須的.如下:
           1 dir=/home/bozo
           2
           3 if cd "$dir" 2>/dev/null; then   # "2>/dev/null" hides error message.
           4   echo "Now in $dir."
           5 else
           6   echo "Can’t change to $dir."
           7 fi
        if命令將返回if後邊的命令的退出碼.

        與此相似,當在一個在使用與或列表結構的時候,test或中括號的使用,也並不一定非的有if不可
           1 var1=20
           2 var2=22
           3 [ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"
           4
           5 home=/home/bozo
           6 [ -d "$home" ] || echo "$home directory does not exist."

        (())結構擴充套件並計算一個算術表示式的結果.如果表示式的結果為0,它將返回1作為退出碼,或
        者是"false".而一個非0表示式的結果將返回0作為退出碼,或者是"true".

        Example 7-3 算數測試使用(( ))
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # 算數測試
        3
        4 # The (( … )) construct evaluates and tests numerical expressions.
        4 # (( … ))結構計算並測試算數表示式的結果.
        5 # 退出碼將與[ … ]結構相反!
        6
        7 (( 0 ))
        8 echo "Exit status of /"(( 0 ))/" is $?."         # 1
        9
        10 (( 1 ))
        11 echo "Exit status of /"(( 1 ))/" is $?."         # 0
        12
        13 (( 5 > 4 ))                                      # true
        14 echo "Exit status of /"(( 5 > 4 ))/" is $?."     # 0
        15
        16 (( 5 > 9 ))                                      # false
        17 echo "Exit status of /"(( 5 > 9 ))/" is $?."     # 1
        18
        19 (( 5 – 5 ))                                      # 0
        20 echo "Exit status of /"(( 5 – 5 ))/" is $?."     # 1
        21
        22 (( 5 / 4 ))                                      # 除法也行
        23 echo "Exit status of /"(( 5 / 4 ))/" is $?."     # 0
        24
        25 (( 1 / 2 ))                                      # 出發結果<1
        26 echo "Exit status of /"(( 1 / 2 ))/" is $?."     # 結果將為0
        27                                                  # 1
        28
        29 (( 1 / 0 )) 2>/dev/null                          # 除數為0的錯誤
        30 #           ^^^^^^^^^^^
        31 echo "Exit status of /"(( 1 / 0 ))/" is $?."     # 1
        32
        33 # What effect does the "2>/dev/null" have?
        33 # "2>/dev/null"的作用是什麼?
        34 # 如果刪除"2>dev/null"將會發生什麼?
        35 # Try removing it, then rerunning the script.
        35 # 嘗試刪除它,然後再執行指令碼.
        36
        37 exit 0
        ################################End Script#########################################

        7.2 檔案測試操作
        —————-
        返回true如果…

        -e        檔案存在
        -a        檔案存在
               這個選項的效果與-e相同.但是它已經被棄用了,並且不鼓勵使用
        -f        file是一個regular檔案(不是目錄或者裝置檔案)
        -s        檔案長度不為0
        -d        檔案是個目錄
        -b        檔案是個塊裝置(軟盤,cdrom等等)
        -c        檔案是個字元裝置(鍵盤,modem,音效卡等等)
        -p        檔案是個管道
        -h        檔案是個符號連結
        -L        檔案是個符號連結
        -S        檔案是個socket
        -t        關聯到一個終端裝置的檔案描述符
               這個選項一般都用來檢測是否在一個給定指令碼中的stdin[-t0]或[-t1]是一個終端
        -r        檔案具有讀許可權(對於使用者執行這個test)
        -w        檔案具有寫許可權(對於使用者執行這個test)
        -x        檔案具有執行許可權(對於使用者執行這個test)
        -g        set-group-id(sgid)標誌到檔案或目錄上
               如果一個目錄具有sgid標誌,那麼一個被建立在這個目錄裡的檔案,這個目錄屬於建立
               這個目錄的使用者組,並不一定與建立這個檔案的使用者的組相同.對於workgroup的目錄
               共享來說,這非常有用.見<>第58頁.
        -u        set-user-id(suid)標誌到檔案上
               如果執行一個具有root許可權的檔案,那麼執行程序將取得root許可權,即使你是一個普通
               使用者.[1]這對於需要存取系統硬體的執行操作(比如pppd和cdrecord)非常有用.如果
               沒有suid標誌的話,那麼普通使用者(沒有root許可權)將無法執行這種程式.
               見<>第58頁.
                  -rwsr-xr-t    1 root       178236 Oct  2  2000 /usr/sbin/pppd
               對於設定了suid的檔案,在它的許可權標誌中有"s".
        -k        設定貼上位,見<>第65頁.
               對於"sticky bit",save-text-mode標誌是一個檔案許可權的特殊型別.如果設定了這
               個標誌,那麼這個檔案將被儲存在交換區,為了達到快速存取的目的.如果設定在目錄
               中,它將限制寫許可權.對於設定了sticky bit位的檔案或目錄,許可權標誌中有"t".
                  drwxrwxrwt    7 root         1024 May 19 21:26 tmp/
               如果一個使用者並不時具有stick bit位的目錄的擁有者,但是具有寫許可權,那麼使用者只
               能在這個目錄下刪除自己所擁有的檔案.這將防止使用者在一個公開的目錄中不慎覆蓋
               或者刪除別人的檔案,比如/tmp(當然root或者是目錄的所有者可以隨便刪除或重新命名
               其中的檔案).
        -O        你是檔案的所有者.
        -G        檔案的group-id和你的相同.
        -N        從檔案最後被閱讀到現在,是否被修改.

        f1 -nt f2
               檔案f1比f2新
        f1 -ot f2
               f1比f2老
        f1 -ef f2
               f1和f2都硬連線到同一個檔案.

        !        非–反轉上邊測試的結果(如果條件缺席,將返回true)

        Example 7-4 test死的連結檔案
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # broken-link.sh
        3 # Written by Lee bigelow
        4 # Used with permission.
        5
        6 #一個真正有用的shell指令碼來找出死連結檔案並且輸出它們的引用
        7 #以便於它們可以被輸入到xargs命令中進行處理 🙂
        8 #比如: broken-link.sh /somedir /someotherdir|xargs rm
        9 #
        10 #這裡,不管怎麼說,是一種更好的方法
        11 #
        12 #find "somedir" -type l -print0|/
        13 #xargs -r0 file|/
        14 #grep "broken symbolic"|
        15 #sed -e ‘s/^/|: *broken symbolic.*$/"/g’
        16 #
        17 #但這不是一個純粹的bash,最起碼現在不是.
        18 #小心:小心/proc檔案系統和任何的迴圈連結檔案.
        19 ##############################################################
        20
        21
        22 #如果沒對這個指令碼傳遞引數,那麼就使用當前目錄.
        23 #否則就使用傳遞進來的引數作為目錄來搜尋.
        24 #
        25 ####################
        26 [ $# -eq 0 ] && directorys=`pwd` || [email protected]
        27
        28 #建立函式linkchk來檢查傳進來的目錄或檔案是否是連結和是否存在,
        29 #並且列印出它們的引用
        30 #如果傳進來的目錄有子目錄,
        31 #那麼把子目錄也傳送到linkchk函式中處理,就是遞迴目錄.
        32 ##########
        33 linkchk () {
        34     for element in $1/*; do
        35     [ -h "$element" -a ! -e "$element" ] && echo /"$element/"
        36     [ -d "$element" ] && linkchk $element
        37     # Of course, ‘-h’ tests for symbolic link, ‘-d’ for directory.
        37     # 當然’-h’是測試連結,’-d’是測試目錄.
        38     done
        39 }
        40
        41 #如果是個可用目錄,那就把每個從指令碼傳遞進來的引數都送到linkche函式中.
        42 #如果不是,那就列印出錯誤訊息和使用資訊.
        43 #
        44 ################
        45 for directory in $directorys; do
        46     if [ -d $directory ]
        47     then linkchk $directory
        48     else
        49         echo "$directory is not a directory"
        50         echo "Usage: $0 dir1 dir2 …"
        51     fi
        52 done
        53
        54 exit 0
        ################################End Script#########################################
        Example 28-1, Example 10-7, Example 10-3, Example 28-3, 和Example A-1 也會說明檔案
        測試操作的使用過程.

        注意事項:
        [1]        小心suid,可能引起安全漏洞,但是不會影響shell指令碼.
        [2]        在當代UNIX系統中,已經不使用sticky bit了,只在目錄中使用.

        7.3 其他比較操作
        —————-
        二元比較操作符,比較變數或者比較數字.注意數字與字串的區別.

        整數比較

        -eq        等於,如:if [ "$a" -eq "$b" ]
        -ne        不等於,如:if [ "$a" -ne "$b" ]
        -gt        大於,如:if [ "$a" -gt "$b" ]
        -ge        大於等於,如:if [ "$a" -ge "$b" ]
        -lt        小於,如:if [ "$a" -lt "$b" ]
        -le        小於等於,如:if [ "$a" -le "$b" ]
        <        小於(需要雙括號),如:(("$a" < "$b"))
        <=        小於等於(需要雙括號),如:(("$a" <= "$b"))
        >        大於(需要雙括號),如:(("$a" > "$b"))
        >=        大於等於(需要雙括號),如:(("$a" >= "$b"))

        字串比較
        =        等於,如:if [ "$a" = "$b" ]
        ==        等於,如:if [ "$a" == "$b" ],與=等價
               注意:==的功能在[[]]和[]中的行為是不同的,如下:
               1 [[ $a == z* ]]    # 如果$a以"z"開頭(模式匹配)那麼將為true
               2 [[ $a == "z*" ]]  # 如果$a等於z*(字元匹配),那麼結果為true
               3
               4 [ $a == z* ]      # File globbing 和word splitting將會發生
               5 [ "$a" == "z*" ]  # 如果$a等於z*(字元匹配),那麼結果為true
               一點解釋,關於File globbing是一種關於檔案的速記法,比如"*.c"就是,再如~也是.
               但是file globbing並不是嚴格的正規表示式,雖然絕大多數情況下結構比較像.
        !=        不等於,如:if [ "$a" != "$b" ]
               這個操作符將在[[]]結構中使用模式匹配.
        <        小於,在ASCII字母順序下.如:
               if [[ "$a" < "$b" ]]
               if [ "$a" /< "$b" ]
               注意:在[]結構中"<"需要被轉義.
        >        大於,在ASCII字母順序下.如:
               if [[ "$a" > "$b" ]]
               if [ "$a" /> "$b" ]
               注意:在[]結構中">"需要被轉義.
               具體參考Example 26-11來檢視這個操作符應用的例子.
        -z        字串為"null".就是長度為0.
        -n        字串不為"null"
               注意:
               使用-n在[]結構中測試必須要用""把變數引起來.使用一個未被""的字串來使用! -z
               或者就是未用""引用的字串本身,放到[]結構中(見Example 7-6)雖然一般情況下可
               以工作,但這是不安全的.習慣於使用""來測試字串是一種好習慣.[1]

        Example 7-5 數字和字串比較
        ################################Start Script#######################################
        1 #!/bin/bash
        2
        3 a=4
        4 b=5
        5
        6 #  這裡的變數a和b既可以當作整型也可以當作是字串.
        7 #  這裡在算術比較和字串比較之間有些混淆,
        8 # 因為Bash變數並不是強型別的.
        9
        10 #  Bash允許對整型變數操作和比較
        11 # 當然變數中只包含數字字元.
        12 #  但是還是要考慮清楚再做.
        13
        14 echo
        15
        16 if [ "$a" -ne "$b" ]
        17 then
        18   echo "$a is not equal to $b"
        19   echo "(arithmetic comparison)"
        20 fi
        21
        22 echo
        23
        24 if [ "$a" != "$b" ]
        25 then
        26   echo "$a is not equal to $b."
        27   echo "(string comparison)"
        28   #     "4"  != "5"
        29   # ASCII 52 != ASCII 53
        30 fi
        31
        32 # 在這個特定的例子中,"-ne"和"!="都可以.
        33
        34 echo
        35
        36 exit 0
        ################################End Script#########################################

        Example 7-6 測試字串是否為null
        ################################Start Script#######################################
        1 #!/bin/bash
        2 #  str-test.sh: 測試null字串和非引用字串,
        3 # but not strings and sealing wax, not to mention cabbages and kings . . .
        4 # 上邊這句沒看懂
        5 # Using   if [ … ]
        6
        7
        8 # 如果一個字串沒被初始化,那麼它就沒有定義的值(像這種話,總感覺像屁話)
        9 # 這種狀態叫做"null"(與zero不同)
        10
        11 if [ -n $string1 ]    # $string1 沒被宣告和初始化
        12 then
        13   echo "String /"string1/" is not null."
        14 else  
        15   echo "String /"string1/" is null."
        16 fi  
        17 # 錯誤的結果.
        18 # 顯示$string1為非空,雖然他沒被初始化.
        19
        20
        21 echo
        22
        23
        24 # 讓我們再試一下.
        25
        26 if [ -n "$string1" ]  # 這次$string1被引用了.
        27 then
        28   echo "String /"string1/" is not null."
        29 else  
        30   echo "String /"string1/" is null."
        31 fi                    # ""的字串在[]結構中
        32
        33
        34 echo
        35
        36
        37 if [ $string1 ]       # 這次$string1變成"裸體"的了
        38 then
        39   echo "String /"string1/" is not null."
        40 else  
        41   echo "String /"string1/" is null."
        42 fi  
        43 # 這工作得很好.
        44 # 這個[]test操作檢測string是否為null.
        45 # 然而,使用("$string1")是一種很好的習慣
        46 #
        47 # As Stephane Chazelas points out,
        48 #    if [ $string1 ]    有1個引數 "]"
        49 #    if [ "$string1" ]  有2個引數,空的"$string1"和"]"
        50
        51
        52
        53 echo
        54
        55
        56
        57 string1=initialized
        58
        59 if [ $string1 ]       # 再來,$string1"裸體了"
        60 then
        61   echo "String /"string1/" is not null."
        62 else  
        63   echo "String /"string1/" is null."
        64 fi  
        65 # 再來,給出了正確的結果.
        66 # 不過怎麼說("$string1")還是好很多,因為. . .
        67
        68
        69 string1="a = b"
        70
        71 if [ $string1 ]       # 再來,$string1 再次裸體了.
        72 then
        73   echo "String /"string1/" is not null."
        74 else  
        75   echo "String /"string1/" is null."
        76 fi  
        77 # 非引用的"$string1"現在給出了一個錯誤的結果!
        78
        79 exit 0
        80 # Thank you, also, Florian Wisser, for the "heads-up".
        ################################End Script#########################################

        Example 7-7 zmore
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # zmore
        3
        4 #使用’more’來檢視gzip檔案
        5
        6 NOARGS=65
        7 NOTFOUND=66
        8 NOTGZIP=67
        9
        10 if [ $# -eq 0 ] # 與 if [ -z "$1" ]同樣的效果
        11 # 應該是說前邊的那句註釋有問題,$1是可以存在的,比如:zmore "" arg2 arg3
        12 then
        13   echo "Usage: `basename $0` filename" >&2
        14   # 錯誤訊息到stderr
        15   exit $NOARGS
        16   # 指令碼返回65作為退出碼.
        17 fi  
        18
        19 filename=$1
        20
        21 if [ ! -f "$filename" ]   # 將$filename ""起來,來允許可能的空白
        22 then
        23   echo "File $filename not found!" >&2
        24    # 錯誤訊息到stderr
        25   exit $NOTFOUND
        26 fi  
        27
        28 if [ ${filename##*.} != "gz" ]
        29 # 在變數替換中使用中括號
        30 then
        31   echo "File $1 is not a gzipped file!"
        32   exit $NOTGZIP
        33 fi  
        34
        35 zcat $1 | more
        36
        37 # 使用過濾命令’more’
        38 # 如果你想的話也可使用’less’
        39
        40
        41 exit $?   # 指令碼將返回pipe的結果作為退出碼
        42 # 事實上,不用非的有"exit $?",但是不管怎麼說,有了這句,能正規一些
        43 # 將最後一句命令的執行狀態作為退出碼返回
        ################################End Script#########################################

        混合比較

        -a        邏輯與
               exp1 -a exp2    如果exp1和exp2都為true的話,這個表示式將返回true

        -o        邏輯或
               exp1 -o exp2    如果exp1和exp2中有一個為true的話,那麼這個表示式就返回true

        這與Bash的比較操作符&&和||很相像.在[[]]中使用它.
               1 [[ condition1 && condition2 ]]
        -o和-a一般都是和test命令或者是[]一起工作.
               1 if [ "$exp1" -a "$exp2" ]

        請參考Example 8-3,Example 26-16和Example A-28來檢視混合比較操作的行為.

        注意事項:
        [1]        S.C.(這傢伙是個人名)指出,在使用混合比較的時候即使"$var"也可能會產生問題.
               如果$string為空的話,[ -n "$string" -o "$a" = "$b" ]可能在某些版本的Bash中
               會有問題.為了附加一個額外的字元到可能的空變數中的一種安全的辦法是,
               [ "x$string" != x -o "x$a" = "x$b" ](the "x’s" cancel out)(沒看懂).
               cancel out是抵消的意思.

        7.4 巢狀的if/then條件test
        ————————-
        可以使用if/then來進行巢狀的條件test.最終的結果和上邊的使用&&混合比較操作是相同的.
           1 if [ condition1 ]
           2 then
           3   if [ condition2 ]
           4   then
           5     do-something  # 這裡只有在condition1和condition2都可用的時候才行.
           6   fi  
           7 fi
        具體請檢視Example 34-4.

        7.5 檢查你的test知識
        ——————–
        系統範圍的xinitrc檔案可以用來啟動X server.這個檔案中包含了相當多的if/then test,
        就像下邊的節選一樣:
        1 if [ -f $HOME/.Xclients ]; then
        2   exec $HOME/.Xclients
        3 elif [ -f /etc/X11/xinit/Xclients ]; then
        4   exec /etc/X11/xinit/Xclients
        5 else
        6      # 故障保險設定,雖然我們永遠都不會走到這來.
        7      # (我們在Xclients中也提供了相同的機制)它不會受傷的.
        8      xclock -geometry 100×100-5 5 &
        9      xterm -geometry 80×50-50 150 &
        10      if [ -f /usr/bin/netscape -a -f /usr/share/doc/HTML/index.html ]; then
        11              netscape /usr/share/doc/HTML/index.html &
        12      fi
        13 fi

        對上邊的"test"結構進行解釋,然後檢查整個檔案,/etc/X11/xinit/xinitrc,並分析if/then
        test結構.你可能需要檢視一下後邊才能講解到的grep,sed和正規表示式的知識.

        第8章 操作符和相關的主題
        ========================

        8.1 操作符
        ———-

        等號操作符

        變數賦值
           初始化或者修改變數的值
        =
           無論在算術運算還是字串運算中,都是賦值語句.
           1 var=27
           2 category=minerals  # No spaces allowed after the "=".

           注意:不要和"="test操作符混淆.
               1 #    = as a test operator
               2
               3 if [ "$string1" = "$string2" ]
               4 # if [ "X$string1" = "X$string2" ] is safer,
               5 # to prevent an error message should one of the variables be empty.
               6 # (The prepended "X" characters cancel out.)
               7 then
               8    command
               9 fi

        算術操作符

               加法
        –        減法
        *        乘法
        /        除法
        **        冪運算
               1 # Bash, version 2.02, introduced the "**" exponentiation operator.
               2
               3 let "z=5**3"
               4 echo "z = $z"   # z = 125
        %        取模
               bash$ expr 5 % 3
               2

               5/3=1餘2
               模運算經常用在其它的事情中,比如產生特定的範圍的數字(Example 9-24,
               Example 9-27)和格式化程式的輸出(Example 26-15,Example A-6).它甚至可以用來
               產生質數,(Example A-16).事實上取模運算在算術運算中使用的頻率驚人的高.

        Example 8-1 最大公約數
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # xxx.sh: 最大公約數
        3 #         使用Euclid’s 演算法
        4
        5 #  最大公約數,就是2個數能夠同時整除的最大的數.
        6 #
        7
        8 #  Euclid’s演算法採用連續除法.
        9 #  在每個迴圈中
        10 # 被除數 <—  除數
        11 # 除數 <—  餘數
        12 # 直到餘數= 0.
        13 # 在最後的迴圈中The xxx = 被除數
        14 #
        15 #  關於這個演算法更精彩的討論
        16 #  見Jim Loy’s site, http://www.jimloy.com/numb…
        17
        18
        19 # ——————————————————
        20 # 引數檢查
        21 ARGS=2
        22 E_BADARGS=65
        23
        24 if [ $# -ne "$ARGS" ]
        25 then
        26   echo "Usage: `basename $0` first-number second-number"
        27   exit $E_BADARGS
        28 fi
        29 # ——————————————————
        30
        31
        32 xxx ()
        33 {
        34
        35   dividend=$1                    #  隨便給值
        36   divisor=$2                     # 即使$2大,也沒關係.
        37                                  #  Why not?
        38
        39   remainder=1                    #  如果再迴圈中使用為初始化的變數.
        40                                  # 那將在第一次迴圈中產生一個錯誤訊息.
        41                                  
        42
        43   until [ "$remainder" -eq 0 ]
        44   do
        45     let "remainder = $dividend % $divisor"
        46     dividend=$divisor            # 現在使用2個最小的數重複.
        47     divisor=$remainder
        48   done                           # Euclid’s algorithm
        49
        50 }                                # Last $dividend is the xxx.
        50 }                                # 最後的$dividend就是xxx.
        51
        52
        53 xxx$1 $2
        54
        55 echo; echo "XXX of $1 and $2 = $dividend"; echo
        56
        57
        58 # 練習:
        59 # ——–
        60 #  檢查命令列引數來確定它們都是整數,
        61 # and exit the script with an appropriate error message if not.
        61 # 否則就選擇合適的錯誤訊息退出.
        62
        63 exit 0
        ################################End Script#########################################

        =        加等於(通過常量增加變數)
               let "var = 5"        #var將在本身值的基礎上增加5
        -=        減等於
        *=        乘等於
               let "var *= 4"
        /=        除等於
        %=        取模賦值,算術操作經常使用expr或者let表示式.

        Example 8-2 使用算術操作符
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # Counting to 11 in 10 different ways.
        3
        4 n=1; echo -n "$n "
        5
        6 let "n = $n 1"   # let "n = n 1"  這麼寫也行
        7 echo -n "$n "
        8
        9
        10 : $((n = $n 1))
        11 #  ":" 是必須的,這是因為,如果沒有":"的話,Bash將
        12 # 嘗試把"$((n = $n 1))"解釋成一個命令
        13 echo -n "$n "
        14
        15 (( n = n 1 ))
        16 #  對於上邊的方法的一個更簡單的選則.
        17 #  Thanks, David Lombard, for pointing this out.
        18 echo -n "$n "
        19
        20 n=$(($n 1))
        21 echo -n "$n "
        22
        23 : $[ n = $n 1 ]
        24 #  ":" 是必須的,這是因為,如果沒有":"的話,Bash將
        25 # 嘗試把"$[ n = $n 1 ]" 解釋成一個命令
        26 #  即使"n"被初始化成為一個字串,這句也能工作.
        27 echo -n "$n "
        28
        29 n=$[ $n 1 ]
        30 #  即使"n"被初始化成為一個字串,這句也能工作.
        31 #* Avoid this type of construct, since it is obsolete and nonportable.
        31 #* 儘量避免這種型別的結果,因為這已經被廢棄了,並且不具可移植性.
        32 #  Thanks, Stephane Chazelas.
        33 echo -n "$n "
        34
        35 # 現在來個C風格的增量操作.
        36 # Thanks, Frank Wang, for pointing this out.
        37
        38 let "n "          # let " n"  also works.
        39 echo -n "$n "
        40
        41 (( n ))          # (( n )  also works.
        42 echo -n "$n "
        43
        44 : $(( n ))       # : $(( n )) also works.
        45 echo -n "$n "
        46
        47 : $[ n ]         # : $[ n ]] also works
        48 echo -n "$n "
        49
        50 echo
        51
        52 exit 0
        ################################End Script#########################################

        注意:在Bash中的整型變數事實上是32位的,範圍是 -2147483648 到2147483647.如果超過這個
        範圍進行算術操作,將不會得到你期望的結果(就是溢位麼).
           1 a=2147483646
           2 echo "a = $a"      # a = 2147483646
           3 let "a =1"         # 加1 "a".
           4 echo "a = $a"      # a = 2147483647
           5 let "a =1"         # 再加1 "a" ,將超過上限了.
           6 echo "a = $a"      # a = -2147483648
           7                    #      錯誤 (溢位了)
           在Bash 2.05b版本中,Bash支援64位整型了.

        注意:Bash並不能理解浮點運算.它把包含的小數點看作字串.
           1 a=1.5
           2
           3 let "b = $a 1.3"  # 錯誤.
           4 # t2.sh: let: b = 1.5 1.3: 表示式的語義錯誤(錯誤標誌為".5 1.3")
           5
           6 echo "b = $b"       # b=1
           如果真想做浮點運算的話,使用bc(見12.8節),bc可以進行浮點運算或呼叫數學庫函式.

        位操作符.
           (暈,有點強大過分了吧,位級操作都支援.)
           位操作符在shell指令碼中極少使用.它們最主要的用途看起來就是操作和test從sockets中
           讀出的變數."Bit flipping"與編譯語言的聯絡很緊密,比如c/c ,在這種語言中它可以
           執行得足夠快.(原文有處on the fly,我查了一下,好像是沒事幹的意思,沒理解)

        <<        左移1位(每次左移都將乘2)

        <<=        左移幾位,=號後邊將給出左移幾位
               let "var <<= 2"就是左移2位(就是乘4)

        >>        右移1位(每次右移都將除2)

        >>=        右移幾位

        &        按位與

        &=        按位與賦值

        |        按位或

        |=        按位或賦值

        ~        按位非

        !        按位否?(沒理解和上邊的~有什麼區別?),感覺是應該放到下邊的邏輯操作中

        ^        按位異或XOR

        ^=        異或賦值

        邏輯操作:

        &&        邏輯與
               1 if [ $condition1 ] && [ $condition2 ]
               2 # 與:  if [ $condition1 -a $condition2 ] 相同
               3 # 如果condition1和condition2都為true,那結果就為true.
               4
               5 if [[ $condition1 && $condition2 ]]    # 也可以.
               6 # 注意&&不允許出現在[ … ]中.
               注意:&&也可以用在and list中(見25章),但是使用的時候需要依賴上下文.

        ||        邏輯或
               1 if [ $condition1 ] || [ $condition2 ]
               2 # 與:  if [ $condition1 -o $condition2 ] 相同
               3 # 如果condition1或condition2為true,那結果就為true.
               4
               5 if [[ $condition1 || $condition2 ]]    # 也可以
               6 # 注意||不允許出現在[ … ]中.
               注意:Bash將test每個連線到邏輯操作的狀態的退出狀態(見第6章).

        Example 8-3 使用&&和||進行混合狀態的test
        ################################Start Script#######################################
        1 #!/bin/bash
        2
        3 a=24
        4 b=47
        5
        6 if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]
        7 then
        8   echo "Test #1 succeeds."
        9 else
        10   echo "Test #1 fails."
        11 fi
        12
        13 # 錯誤:   if [ "$a" -eq 24 && "$b" -eq 47 ]
        14 #         嘗試執行’ [ "$a" -eq 24 ‘
        15 #         因為沒找到’]’所以失敗了.
        16 #
        17 #  注意:  如果 [[ $a -eq 24 && $b -eq 24 ]]  能夠工作.
        18 #  那這個[[]]的test結構就比[]結構更靈活了.
        19 #
        20 #    (在17行的"&&"與第6行的"&&"意義不同)
        21 #    Thanks, Stephane Chazelas, for pointing this out.
        22
        23
        24 if [ "$a" -eq 98 ] || [ "$b" -eq 47 ]
        25 then
        26   echo "Test #2 succeeds."
        27 else
        28   echo "Test #2 fails."
        29 fi
        30
        31
        32 #  -a和-o選項提供了
        33 # 一種可選的混合test方法.
        34 #  Thanks to Patrick Callahan for pointing this out.
        35
        36
        37 if [ "$a" -eq 24 -a "$b" -eq 47 ]
        38 then
        39   echo "Test #3 succeeds."
        40 else
        41   echo "Test #3 fails."
        42 fi
        43
        44
        45 if [ "$a" -eq 98 -o "$b" -eq 47 ]
        46 then
        47   echo "Test #4 succeeds."
        48 else
        49   echo "Test #4 fails."
        50 fi
        51
        52
        53 a=rhino
        54 b=crocodile
        55 if [ "$a" = rhino ] && [ "$b" = crocodile ]
        56 then
        57   echo "Test #5 succeeds."
        58 else
        59   echo "Test #5 fails."
        60 fi
        61
        62 exit 0
        ################################End Script#########################################
           &&和||操作也能在算術運算的上下文中找到.
               bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0))
               1 0 1 0

        混雜操作:
        ,        逗號操作符
               逗號操作符可以連線2個或多個算術運算.所有的操作都會被執行,但是隻有最後一個
               操作作為結果.
               1 let "t1 = ((5 3, 7 – 1, 15 – 4))"
               2 echo "t1 = $t1"               # t1 = 11
               3
               4 let "t2 = ((a = 9, 15 / 3))"  # Set "a" and calculate "t2".
               5 echo "t2 = $t2    a = $a"     # t2 = 5    a = 9
               ","主要用在for迴圈中,具體見Example 10-12.

        8.2 數字常量
        ————
        shell指令碼預設都是將數字作為10進位制數處理,除非這個數字某種特殊的標記法或字首開頭.
        以0開頭就是8進位制.以0x開頭就是16進位制數.使用BASE#NUMBER這種形式可以表示其它進位制
        表示法

        Example 8-4 數字常量的處理
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # numbers.sh: 數字常量的幾種不同的表示法
        3
        4 # 10進位制: 預設
        5 let "dec = 32"
        6 echo "decimal number = $dec"             # 32
        7 # 一切都很正常
        8
        9
        10 # 8進位制: 以’0′(零)開頭
        11 let "oct = 032"
        12 echo "octal number = $oct"               # 26
        13 # 表示式的結果用10進製表示.
        14 #
        15
        16 # 16進製表示:數字以’0x’或者’0X’開頭
        17 let "hex = 0x32"
        18 echo "hexadecimal number = $hex"         # 50
        19 # 表示式的結果用10進製表示.
        20
        21 # 其它進位制: BASE#NUMBER
        22 # BASE between 2 and 64.
        22 # 2到64進位制都可以.
        23 # NUMBER必須在BASE的範圍內,具體見下邊.
        24
        25
        26 let "bin = 2#111100111001101"
        27 echo "binary number = $bin"              # 31181
        28
        29 let "b32 = 32#77"
        30 echo "base-32 number = $b32"             # 231
        31
        32 let "b64 = 64#@_"
        33 echo "base-64 number = $b64"             # 4031
        34 # 這種64進位制的表示法中的每位數字都必須在64進製表示法的限制字元內.
        35 # 10 個數字 26 個小寫字母 26 個大寫字母 @ _
        36
        37
        38 echo
        39
        40 echo $((36#zz)) $((2#10101010)) $((16#AF16)) $((53#1aA))
        41                                          # 1295 170 44822 3375
        42
        43
        44 #  重要的注意事項:
        45 #  —————
        46 #  如果使用的每位數字超出了這個進製表示法規定字元的範圍的話,
        47 # 將給出一個錯誤訊息.
        48
        49 let "bad_oct = 081"
        50 # (部分的) 錯誤訊息輸出:
        51 #  bad_oct = 081: too great for base (error token is "081")
        52 #              Octal numbers use only digits in the range 0 – 7.
        53
        54 exit 0       # Thanks, Rich Bartell and Stephane Chazelas, for clarification.
        ################################End Script#########################################

        第三部分    超越基本

        第9章    變數重遊
        ================
        如果變數使用恰當,將會增加指令碼的能量和靈活性.但是前提是這需要仔細學習變數的細節知識.

        9.1 內部變數
        ————
        Builtin variable
           這些內建的變數,將影響bash指令碼的行為.    

           $BASH
               這個變數將指向Bash的二進位制執行檔案的位置.
               bash$ echo $BASH
               /bin/bash

           $BASH_ENV
               這個環境變數將指向一個Bash啟動檔案,這個啟動檔案將在呼叫一個指令碼時被讀取.

           $BASH_SUBSHELL
               這個變數將提醒subshell的層次,這是一個在version3才被新增到Bash中的新特性.
               見Example 20-1.

           $BASH_VERSINFO[n]
               記錄Bash安裝資訊的一個6元素的陣列.與下邊的$BASH_VERSION很像,但這個更加詳細.
                1 # Bash version info:
                2
                3 for n in 0 1 2 3 4 5
                4 do
                5   echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
                6 done  
                7
                8 # BASH_VERSINFO[0] = 3                      # 主版本號
                9 # BASH_VERSINFO[1] = 00                     # 次版本號
               10 # BASH_VERSINFO[2] = 14                     # Patch 次數.
               11 # BASH_VERSINFO[3] = 1                      # Build version.
               12 # BASH_VERSINFO[4] = release                # Release status.
               13 # BASH_VERSINFO[5] = i386-redhat-linux-gnu  # Architecture

           $BASH_VERSION
               安裝在系統上的Bash的版本號.
               bash$ echo $BASH_VERSION
               3.00.14(1)-release
               tcsh% echo $BASH_VERSION
               BASH_VERSION: Undefined variable.
               使用這個變數對於判斷系統上到底執行的是那個shll來說是一種非常好的辦法.$SHELL
               有時將不能給出正確的答案.

           $DIRSTACK
               在目錄棧中最上邊的值(將受到pushd和popd的影響).
               這個內建的變數與dirs命令是保持一致的,但是dirs命令將顯示目錄棧的整個內容.

           $EDITOR
               指令碼呼叫的預設編輯器,一般是vi或者是emacs.

           $EUID
               "effective"使用者ID號.
               當前使用者被假定的任何id號.可能在su命令中使用.
               注意:$EUID並不一定與$UID相同.

           $FUNCNAME
               當前函式的名字.
               1 xyz23 ()
               2 {
               3   echo "$FUNCNAME now executing."  # xyz23 現在正在被執行.
               4 }
               5
               6 xyz23
               7
               8 echo "FUNCNAME = $FUNCNAME"        # FUNCNAME =
               9                                    # 出了函式就變為Null值了.

           $GLOBIGNORE
               一個檔名的模式匹配列表,如果在file globbing中匹配到的檔案包含這個列表中的
               某個檔案,那麼這個檔案將被從匹配到的檔案中去掉.

           $GROUPS
               當前使用者屬於的組.
               這是一個當前使用者的組id列表(陣列),就像在/etc/passwd中記錄的一樣.
               root# echo $GROUPS
               0

               root# echo ${GROUPS[1]}
               1

               root# echo ${GROUPS[5]}
               6

           $HOME
               使用者的home目錄,一般都是/home/username(見Example 9-14)

           $HOSTNAME
               hostname命令將在一個init指令碼中,在啟動的時候分配一個系統名字.
               gethostname()函式將用來設定這個$HOSTNAME內部變數.(見Example 9-14)

           $HOSTTYPE
               主機型別
               就像$MACHTYPE,識別系統的硬體.
               bash$ echo $HOSTTYPE
               i686

           $IFS
               內部域分隔符.
               這個變數用來決定Bash在解釋字串時如何識別域,或者單詞邊界.
               $IFS預設為空白(空格,tab,和新行),但可以修改,比如在分析逗號分隔的資料檔案時.
               注意:$*使用$IFS中的第一個字元,具體見Example 5-1.
               bash$ echo $IFS | cat -vte
               $

               bash$ bash -c ‘set w x y z; IFS=":-;"; echo "$*"’
               w:x:y:o
               
               注意:$IFS並不像它處理其它字元一樣處理空白.

        Example 9-1 $IFS和空白
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # $IFS 處理空白的方法,與處理其它字元不同.
        3
        4 output_args_one_per_line()
        5 {
        6   for arg
        7   do echo "[$arg]"
        8   done
        9 }
        10
        11 echo; echo "IFS=/" /""
        12 echo "——-"
        13
        14 IFS=" "
        15 var=" a  b c   "
        16 output_args_one_per_line $var  # output_args_one_per_line `echo " a  b c   "`
        17 #
        18 # [a]
        19 # [b]
        20 # [c]
        21
        22
        23 echo; echo "IFS=:"
        24 echo "—–"
        25
        26 IFS=:
        27 var=":a::b:c:::"               # 與上邊的一樣,但是用" "替換了":"
        28 output_args_one_per_line $var
        29 #
        30 # []
        31 # [a]
        32 # []
        33 # [b]
        34 # [c]
        35 # []
        36 # []
        37 # []
        38
        39 # 同樣的事情也會發生在awk中的"FS"域分隔符.
        40
        41 # Thank you, Stephane Chazelas.
        42
        43 echo
        44
        45 exit 0
        ################################End Script#########################################
               Example 12-37也是使用$IFS的另一個啟發性的例子.

           $IGNOREEOF
               忽略EOF: 告訴shell在log out之前要忽略多少檔案結束符(control-D).

           $LC_COLLATE
               常在.bashrc或/etc/profile中設定,這個變數用來在檔名擴充套件和模式匹配校對順序.
               如果$LC_COLLATE被錯誤的設定,那麼將會在filename globbing中引起錯誤的結果.
               
               注意:在2.05以後的Bash版本中,filename globbing將不在對[]中的字元區分大小寫.
                   比如:ls [A-M]* 將即匹配File1.txt也會匹配file1.txt.為了恢復[]的習慣用法,
                   設定$LC_COLLATE的值為c,使用export LC_COLLATE=c 在/etc/profile或者是
                   ~/.bashrc中.

           $LC_CTYPE
               這個內部變數用來控制globbing和模式匹配的字串解釋.

           $LINENO
               這個變數記錄它所在的shell指令碼中它所在行的行號.這個變數一般用於除錯目的.
               1 # *** BEGIN DEBUG BLOCK ***
               2 last_cmd_arg=$_  # Save it.
               3
               4 echo "At line number $LINENO, variable /"v1/" = $v1"
               5 echo "Last command argument processed = $last_cmd_arg"
               6 # *** END DEBUG BLOCK ***

           $MACHTYPE
               系統型別
               提示系統硬體
               bash$ echo $MACHTYPE
               i686

           $OLDPWD
               老的工作目錄("OLD-print-working-directory",你所在的之前的目錄)

           $OSTYPE
               作業系統型別.
               bash$ echo $OSTYPE
               linux

           $PATH
               指向Bash外部命令所在的位置,一般為/usr/bin,/usr/X11R6/bin,/usr/local/bin等.
               當給出一個命令時,Bash將自動對$PATH中的目錄做一張hash表.$PATH中以":"分隔的
               目錄列表將被儲存在環境變數中.一般的,系統儲存的$PATH定義在/ect/processed或
               ~/.bashrc中(見Appendix G).

               bash$ echo $PATH
               /bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin

               PATH=${PATH}:/opt/bin將把/opt/bin目錄附加到$PATH變數中.在指令碼中,這是一個
               新增目錄到$PATH中的便捷方法.這樣在這個指令碼退出的時候,$PATH將會恢復(因為這個
               shell是個子程序,像這樣的一個指令碼是不會將它的父程序的環境變數修改的)

               注意:當前的工作目錄"./"一般都在$PATH中被省去.

           $PIPESTATUS
               陣列變數將儲存最後一個執行的前臺管道的退出碼.有趣的是,這個退出碼和最後一個命令
               執行的退出碼並不一定相同.
               bash$ echo $PIPESTATUS
               0

               bash$ ls -al | bogus_command
               bash: bogus_command: command not found
               bash$ echo $PIPESTATUS
               141

               bash$ ls -al | bogus_command
               bash: bogus_command: command not found
               bash$ echo $?
               127

               $PIPESTATUS陣列的每個成員都會儲存一個管道命令的退出碼,$PIPESTATUS[0]儲存第
               一個管道命令的退出碼,$PIPESTATUS[1]儲存第2個,以此類推.

               注意:$PIPESTATUS變數在一個login shell中可能會包含一個錯誤的0值(3.0以下版本)
               tcsh% bash

               bash$ who | grep nobody | sort
               bash$ echo ${PIPESTATUS[*]}
               0
               包含在指令碼中的上邊這行將會產生一個期望的輸出0 1 0.

               注意:在某些上下文$PIPESTATUS可能不會給出正確的結果.
               bash$ echo $BASH_VERSION
               3.00.14(1)-release

               bash$ $ ls | bogus_command | wc
               bash: bogus_command: command not found
               0       0       0

               bash$ echo ${PIPESTATUS[@]}
               141 127 0
               
               Chet Ramey把上邊輸出不成確原因歸咎於ls的行為.因為如果把ls的結果放到管道上,
               並且這個輸出沒被讀取,那麼SIGPIPE將會kill掉它,並且退出碼變為141,而不是我們期
               望的0.這種情況也會發生在tr命令中.

               注意:$PIPESTATUS是一個"volatile"變數.在任何命令插入之前,並且在pipe詢問之後,
               這個變數需要立即被捕捉.
               bash$ $ ls | bogus_command | wc
               bash: bogus_command: command not found
               0       0       0

               bash$ echo ${PIPESTATUS[@]}
               0 127 0

               bash$ echo ${PIPESTATUS[@]}
               0

           $PPID
               一個程序的$PPID就是它的父程序的程序id(pid).[1]
               使用pidof命令對比一下.

           $PROMPT_COMMAND
               這個變數儲存一個在主提示符($PS1)顯示之前需要執行的命令.

           $PS1
               主提示符,具體見命令列上的顯示.

           $PS2
               第2提示符,當你需要額外的輸入的時候將會顯示,預設為">".

           $PS3
               第3提示符,在一個select迴圈中顯示(見Example 10-29).

           $PS4
               第4提示符,當使用-x選項呼叫指令碼時,這個提示符將出現在每行的輸出前邊.
               預設為" ".

           $PWD
               工作目錄(你當前所在的目錄).
               與pwd內建命令作用相同.
        ################################Start Script#######################################
        1 #!/bin/bash
        2
        3 E_WRONG_DIRECTORY=73
        4
        5 clear # 清屏.
        6
        7 TargetDirectory=/home/bozo/projects/GreatAmericanNovel
        8
        9 cd $TargetDirectory
        10 echo "Deleting stale files in $TargetDirectory."
        11
        12 if [ "$PWD" != "$TargetDirectory" ]
        13 then    # 防止偶然刪除錯誤的目錄
        14   echo "Wrong directory!"
        15   echo "In $PWD, rather than $TargetDirectory!"
        16   echo "Bailing out!"
        17   exit $E_WRONG_DIRECTORY
        18 fi  
        19
        20 rm -rf *
        21 rm .[A-Za-z0-9]*    # Delete dotfiles.
        21 rm .[A-Za-z0-9]*    # 刪除"."檔案(隱含檔案).
        22 # rm -f .[^.]* ..?*   為了刪除以多個"."開頭的檔案.
        23 # (shopt -s dotglob; rm -f *)   也行.
        24 # Thanks, S.C. for pointing this out.
        25
        26 # 檔名能夠包含0-255範圍的所有字元,除了"/".
        27 # 刪除以各種詭異字元開頭的檔案將作為一個練習留給大家.
        28
        29 # 這裡預留給其他的必要操作.
        30
        31 echo
        32 echo "Done."
        33 echo "Old files deleted in $TargetDirectory."
        34 echo
        35
        36
        37 exit 0
        ################################End Script#########################################

           $REPLY
               read命令如果沒有給變數,那麼輸入將儲存在$REPLY中.在select選單中也可用,但是隻
               提供選擇的變數的項數,而不是變數本身的值.
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # reply.sh
        3
        4 # REPLY是’read’命令結果儲存的預設變數.
        5
        6 echo
        7 echo -n "What is your favorite vegetable? "
        8 read
        9
        10 echo "Your favorite vegetable is $REPLY."
        11 #  當且僅當在沒有變數提供給"read"命令時,
        12 # REPLY才儲存最後一個"read"命令讀入的值.
        13
        14 echo
        15 echo -n "What is your favorite fruit? "
        16 read fruit
        17 echo "Your favorite fruit is $fruit."
        18 echo "but…"
        19 echo "Value of /$REPLY is still $REPLY."
        20 #  $REPLY還是儲存著上一個read命令的值,
        21 # 因為變數$fruit被傳入到了這個新的"read"命令中.
        22
        23 echo
        24
        25 exit 0
        ################################End Script#########################################

           $SECONDS
               這個指令碼已經執行的時間(單位為秒).
        ################################Start Script#######################################
        1 #!/bin/bash
        2
        3 TIME_LIMIT=10
        4 INTERVAL=1
        5
        6 echo
        7 echo "Hit Control-C to exit before $TIME_LIMIT seconds."
        8 echo
        9
        10 while [ "$SECONDS" -le "$TIME_LIMIT" ]
        11 do
        12   if [ "$SECONDS" -eq 1 ]
        13   then
        14     units=second
        15   else  
        16     units=seconds
        17   fi
        18
        19   echo "This script has been running $SECONDS $units."
        20   #  在一臺比較慢的或者是負載很大的機器上,這個指令碼可能會跳過幾次迴圈
        21   # 在一個while迴圈中.
        22   sleep $INTERVAL
        23 done
        24
        25 echo -e "/a"  # Beep!
        26
        27 exit 0
        ################################End Script#########################################

           $SHELLOPTS
               這個變數裡儲存shell允許的選項,這個變數是隻讀的.
               bash$ echo $SHELLOPTS
               braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs

           $SHLVL
               Shell層次,就是shell層疊的層次,如果是命令列那$SHLVL就是1,如果命令列執行的腳
               本中,$SHLVL就是2,以此類推.

           $TMOUT
               如果$TMOUT環境變數被設定為一個非零的時間值,那麼在過了這個指定的時間之後,
               shell提示符將會超時,這會引起一個logout.

               在2.05b版本的Bash中,已經支援在一個帶有read命令的指令碼中使用$TMOUT變數.
                1 # 需要使用Bash v2.05b或者以後的版本上
                2
                3 TMOUT=3    # Prompt times out at three seconds.
                3 TMOUT=3    # 設定超時的時間為3秒
                4
                5 echo "What is your favorite song?"
                6 echo "Quickly now, you only have $TMOUT seconds to answer!"
                7 read song
                8
                9 if [ -z "$song" ]
               10 then
               11   song="(no answer)"
               12   # 預設響應.
               13 fi
               14
               15 echo "Your favorite song is $song."

               這裡有一個更復雜的方法來在一個指令碼中實現超時功能.一種辦法就是建立一個時間循
               環,在超時的時候通知指令碼.不過,這也需要一個訊號處理機制,在超時的時候來產生中
               斷.
               (參見Example 29-5)

        Example 9-2 時間輸入
        ################################Start Script#######################################
          1 #!/bin/bash
          2 # timed-input.sh
          3
          4 # TMOUT=3    在新版本的Bash上也能工作.
          5
          6
          7 TIMELIMIT=3  # 在這個例子上是3秒,也可以設其他的值.
          8
          9 PrintAnswer()
         10 {
         11   if [ "$answer" = TIMEOUT ]
         12   then
         13     echo $answer
         14   else       # 別想混合著兩個例子.
         15     echo "Your favorite veggie is $answer"
         16     kill $!  # kill將不再需要TimerOn函式執行在後臺.
         17              # $! 是執行在後臺的最後一個工作的PID.
         18   fi
         19
         20 }  
         21
         22
         23
         24 TimerOn()
         25 {
         26   sleep $TIMELIMIT && kill -s 14 $$ &
         27   # 等待3秒,然後傳送一個訊號給指令碼.
         28 }  
         29
         30 Int14Vector()
         31 {
         32   answer="TIMEOUT"
         33   PrintAnswer
         34   exit 14
         35 }  
         36
         37 trap Int14Vector 14   # 為了我們的目的,時間中斷(14)被破壞了.
         38
         39 echo "What is your favorite vegetable "
         40 TimerOn
         41 read answer
         42 PrintAnswer
         43
         44
         45 #  很明顯的,這是一個拼湊的實現.
         46 # 然而使用"-t"選項來"read"的話,將會簡化這個任務.
         47 #  見"t-out.sh",在下邊.
         48
         49 #  如果你需要一個真正的幽雅的寫法…
         50 # 建議你使用c/c 來寫這個應用,
         51 # 使用合適的庫來完成這個任務,比如’alarm’和’setitimer’.
         52
         53 exit 0
        ################################End Script#########################################
               使用stty也是一種選擇.

        Example 9-3 再來一個時間輸入
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # timeout.sh
        3
        4 #  Stephane Chazelas編寫,
        5 # 本書作者進行了一些修改.
        6
        7 INTERVAL=5                # timeout間隔
        8
        9 timedout_read() {
        10   timeout=$1
        11   varname=$2
        12   old_tty_settings=`stty -g`
        13   stty -icanon min 0 time ${timeout}0
        14   eval read $varname      # 或者就是 read $varname
        15   stty "$old_tty_settings"
        16   # 察看"stty"的man頁.
        17 }
        18
        19 echo; echo -n "What’s your name? Quick! "
        20 timedout_read $INTERVAL your_name
        21
        22 #  這種方法可能不是每個終端型別都可以正常使用的.
        23 #  最大的timeout依賴於具體的終端.
        24 # (一般都是25.5秒).
        25
        26 echo
        27
        28 if [ ! -z "$your_name" ]  # If name input before timeout…
        29 then
        30   echo "Your name is $your_name."
        31 else
        32   echo "Timed out."
        33 fi
        34
        35 echo
        36
        37 # 這個指令碼的行為可能與"timed-input.sh"有點不同.
        38 # 在每次按鍵的時候,計數器都會重置.
        39
        40 exit 0
        ################################End Script#########################################
               或許,最簡單的辦法就是使用-t選項來read了.

        Example 9-4 Timed read
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # t-out.sh
        3 # "syngin seven"的一個很好的提議 (thanks).
        4
        5
        6 TIMELIMIT=4         # 4 seconds
        7
        8 read -t $TIMELIMIT variable <&1
        9 #                           ^^^
        10 #  在這個例子中,對於Bash 1.x和2.x就需要使用"<&1"
        11 #  但對於Bash 3.x就不需要.
        12
        13 echo
        14
        15 if [ -z "$variable" ]  # Is null?
        16 then
        17   echo "Timed out, variable still unset."
        18 else  
        19   echo "variable = $variable"
        20 fi  
        21
        22 exit 0
        ################################End Script#########################################

           $UID
               使用者ID號.
               當前使用者的id號,在/etc/passwd中記錄.
               這個值不會因為使用者使用了su命令而改變.$UID是隻讀變數,不容易在命令列或者是腳
               本中被修改,並且和內建的id命令很相像.
        Example 9-5 我是root?
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # am-i-root.sh:   我是不是root使用者?
        3
        4 ROOT_UID=0   # Root的$UID是0.
        5
        6 if [ "$UID" -eq "$ROOT_UID" ]  # 是否是root使用者,請站出來.
        7 then
        8   echo "You are root."
        9 else
        10   echo "You are just an ordinary user (but mom loves you just the same)."
        11 fi
        12
        13 exit 0
        14
        15
        16 # ============================================================= #
        17 # 下邊的程式碼將不被執行,因為指令碼已經退出了.
        18
        19 # 檢驗是root使用者的一種可選方法:
        20
        21 ROOTUSER_NAME=root
        22
        23 username=`id -nu`              # Or…   username=`whoami`
        24 if [ "$username" = "$ROOTUSER_NAME" ]
        25 then
        26   echo "Rooty, toot, toot. You are root."
        27 else
        28   echo "You are just a regular fella."
        29 fi
        ################################End Script#########################################
               見例子Example 2-3
               注意:變數$ENV,$LOGNAME,$MAIL,$TERM,$USER,和$USERNAME並不是Bash的內建變數.它
               們經常被設定成環境變數,它們一般都放在Bash的安裝檔案中.$SHELL,使用者登入的
               shell的名字,可能是從/etc/passwd設定的,也可能是在一個"init"指令碼中設定的,同樣
               的,它也不是Bash的內建變數.
               tcsh% echo $LOGNAME
               bozo
               tcsh% echo $SHELL
               /bin/tcsh
               tcsh% echo $TERM
               rxvt

               bash$ echo $LOGNAME
               bozo
               bash$ echo $SHELL
               /bin/tcsh
               bash$ echo $TERM
               rxvt

        位置引數
           $0, $1, $2,等等…
               位置引數,從命令列傳遞給指令碼,或者是傳遞給函式.或者賦職給一個變數.
               (具體見Example 4-5和Example 11-15)

           $#
               命令列或者是位置引數的個數.(見Example 33-2)

           $*
               所有的位置引數,被作為一個單詞.
               注意:"$*"必須被""引用.
           
           [email protected]
               與$*同義,但是每個引數都是一個獨立的""引用字串,這就意味著引數被完整地傳遞,
               並沒有被解釋和擴充套件.這也意味著,每個引數列表中的每個引數都被當成一個獨立的
               單詞.
               注意:"[email protected]"必須被引用.

        Example 9-6 arglist:通過$*和[email protected]列出所有的引數
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # arglist.sh
        3 # 多使用幾個引數來呼叫這個指令碼,比如"one tow three".
        4
        5 E_BADARGS=65
        6
        7 if [ ! -n "$1" ]
        8 then
        9   echo "Usage: `basename $0` argument1 argument2 etc."
        10   exit $E_BADARGS
        11 fi  
        12
        13 echo
        14
        15 index=1          # 初始化數量.
        16
        17 echo "Listing args with /"/$*/":"
        18 for arg in "$*"  # 如果"$*"不被""引用,那麼將不能正常地工作
        19 do
        20   echo "Arg #$index = $arg"
        21   let "index =1"
        22 done             # $* sees all arguments as single word.
        22 done             # $* 認為所有的引數為一個單詞
        23 echo "Entire arg list seen as single word."
        24
        25 echo
        26
        27 index=1          # 重置數量.
        28                  # 如果你忘了這句會發生什麼?
        29
        30 echo "Listing args with /"/[email protected]/":"
        31 for arg in "[email protected]"
        32 do
        33   echo "Arg #$index = $arg"
        34   let "index =1"
        35 done             # [email protected] 認為每個引數都一個單獨的單詞.
        36 echo "Arg list seen as separate words."
        37
        38 echo
        39
        40 index=1          # 重置數量.
        41
        42 echo "Listing args with /$* (unquoted):"
        43 for arg in $*
        44 do
        45   echo "Arg #$index = $arg"
        46   let "index =1"
        47 done             # 未""引用的$*把引數作為獨立的單詞.
        48 echo "Arg list seen as separate words."
        49
        50 exit 0
        ################################End Script#########################################

               在shift命令後邊,[email protected]將儲存命令列中剩餘的引數,而$1被丟掉了.
                1 #!/bin/bash
                2 # 使用 ./scriptname 1 2 3 4 5 來呼叫這個指令碼
                3
                4 echo "[email protected]"    # 1 2 3 4 5
                5 shift
                6 echo "[email protected]"    # 2 3 4 5
                7 shift
                8 echo "[email protected]"    # 3 4 5
                9
               10 # 每個"shift"都丟棄$1.
               11 # "[email protected]" 將包含剩下的引數.
               [email protected]也作為為工具使用,用來過濾傳給指令碼的輸入.
               cat "[email protected]"結構接受從stdin傳來的輸入,也接受從引數中指定的檔案傳來的輸入.
               具體見Example 12-21和Example 12-22.

               注意:$*和[email protected]的引數有時會不一致,發生令人迷惑的行為,這依賴於$IFS的設定.

        Example 9-7 不一致的$*和[email protected]行為
        ################################Start Script#######################################
         1 #!/bin/bash
         2
         3 #  "$*"和"[email protected]"的古怪行為,
         4 # 依賴於它們是否被""引用.
         5 #  單詞拆分和換行的不一致處理.
         6
         7
         8 set — "First one" "second" "third:one" "" "Fifth: :one"
         9 # 設定這個指令碼引數,$1,$2,等等.
        10
        11 echo
        12
        13 echo ‘IFS unchanged, using "$*"’
        14 c=0
        15 for i in "$*"               # 引用
        16 do echo "$((c =1)): [$i]"   # 這行在下邊的每個例子中都一樣.
        17                             # Echo引數.
        18 done
        19 echo —
        20
        21 echo ‘IFS unchanged, using $*’
        22 c=0
        23 for i in $*                 # 未引用
        24 do echo "$((c =1)): [$i]"
        25 done
        26 echo —
        27
        28 echo ‘IFS unchanged, using "[email protected]"’
        29 c=0
        30 for i in "[email protected]"
        31 do echo "$((c =1)): [$i]"
        32 done
        33 echo —
        34
        35 echo ‘IFS unchanged, using [email protected]
        36 c=0
        37 for i in [email protected]
        38 do echo "$((c =1)): [$i]"
        39 done
        40 echo —
        41
        42 IFS=:
        43 echo ‘IFS=":", using "$*"’
        44 c=0
        45 for i in "$*"
        46 do echo "$((c =1)): [$i]"
        47 done
        48 echo —
        49
        50 echo ‘IFS=":", using $*’
        51 c=0
        52 for i in $*
        53 do echo "$((c =1)): [$i]"
        54 done
        55 echo —
        56
        57 var=$*
        58 echo ‘IFS=":", using "$var" (var=$*)’
        59 c=0
        60 for i in "$var"
        61 do echo "$((c =1)): [$i]"
        62 done
        63 echo —
        64
        65 echo ‘IFS=":", using $var (var=$*)’
        66 c=0
        67 for i in $var
        68 do echo "$((c =1)): [$i]"
        69 done
        70 echo —
        71
        72 var="$*"
        73 echo ‘IFS=":", using $var (var="$*")’
        74 c=0
        75 for i in $var
        76 do echo "$((c =1)): [$i]"
        77 done
        78 echo —
        79
        80 echo ‘IFS=":", using "$var" (var="$*")’
        81 c=0
        82 for i in "$var"
        83 do echo "$((c =1)): [$i]"
        84 done
        85 echo —
        86
        87 echo ‘IFS=":", using "[email protected]"’
        88 c=0
        89 for i in "[email protected]"
        90 do echo "$((c =1)): [$i]"
        91 done
        92 echo —
        93
        94 echo ‘IFS=":", using [email protected]
        95 c=0
        96 for i in [email protected]
        97 do echo "$((c =1)): [$i]"
        98 done
        99 echo —
        100
        101 [email protected]
        102 echo ‘IFS=":", using $var ([email protected])’
        103 c=0
        104 for i in $var
        105 do echo "$((c =1)): [$i]"
        106 done
        107 echo —
        108
        109 echo ‘IFS=":", using "$var" ([email protected])’
        110 c=0
        111 for i in "$var"
        112 do echo "$((c =1)): [$i]"
        113 done
        114 echo —
        115
        116 var="[email protected]"
        117 echo ‘IFS=":", using "$var" (var="[email protected]")’
        118 c=0
        119 for i in "$var"
        120 do echo "$((c =1)): [$i]"
        121 done
        122 echo —
        123
        124 echo ‘IFS=":", using $var (var="[email protected]")’
        125 c=0
        126 for i in $var
        127 do echo "$((c =1)): [$i]"
        128 done
        129
        130 echo
        131
        132 # 用ksh或者zsh -y來試試這個指令碼.
        133
        134 exit 0
        135
        136 # This example script by Stephane Chazelas,
        137 # and slightly modified by the document author.
        ################################End Script#########################################
               注意:[email protected]和$*中的引數只有在""中才會不同.

        Example 9-8 當$IFS為空時的$*和[email protected]
        ################################Start Script#######################################
        1 #!/bin/bash
        2
        3 #  如果$IFS被設定為空時,
        4 # 那麼"$*" 和"[email protected]" 將不會象期望那樣echo出位置引數.
        5
        6 mecho ()       # Echo 位置引數.
        7 {
        8 echo "$1,$2,$3";
        9 }
        10
        11
        12 IFS=""         # 設定為空.
        13 set a b c      # 位置引數.
        14
        15 mecho "$*"     # abc,,
        16 mecho $*       # a,b,c
        17
        18 mecho [email protected]       # a,b,c
        19 mecho "[email protected]"     # a,b,c
        20
        21 #  當$IFS設定為空時,$* 和[email protected] 的行為依賴於
        22 # 正在執行的Bash或者sh的版本.
        23 #  所以在指令碼中使用這種"feature"不是明智的行為.
        24
        25
        26 # Thanks, Stephane Chazelas.
        27
        28 exit 0
        ################################End Script#########################################

        其他的特殊引數

           $-
               傳遞給指令碼的falg(使用set命令).參考Example 11-15.

               注意:這起初是ksh的特徵,後來被引進到Bash中,但不幸的是,在Bash中它看上去也不
               能可靠的工作.使用它的一個可能的方法就是讓這個指令碼進行自我測試(檢視是否是交
               互的).

           $!
               在後臺執行的最後的工作的PID(程序ID).
                1 LOG=$0.log
                2
                3 COMMAND1="sleep 100"
                4
                5 echo "Logging PIDs background commands for script: $0" >> "$LOG"
                6 # 所以它們可以被監控,並且在必要的時候kill掉.
                7 echo >> "$LOG"
                8
                9 # Logging 命令.
               10
               11 echo -n "PID of /"$COMMAND1/":  " >> "$LOG"
               12 ${COMMAND1} &
               13 echo $! >> "$LOG"
               14 # PID of "sleep 100":  1506
               15
               16 # Thank you, Jacques Lederer, for suggesting this.

               1 possibly_hanging_job & { sleep ${TIMEOUT}; eval ‘kill -9 $!’ &> /dev/null; }
               2 # 強制結束一個品行不良的程式.
               3 # 很有用,比如在init指令碼中.
               4
               5 # Thank you,Sylvain Fourmanoit,for this creative use of the "!" variable.

           $_
               儲存之前執行的命令的最後一個引數.

        Example 9-9 下劃線變數
        ################################Start Script#######################################
        1 #!/bin/bash
        2
        3 echo $_              # /bin/bash
        4                      # 只是呼叫/bin/bash來執行這個指令碼.
        5
        6 du >/dev/null        # 將沒有命令的輸出
        7 echo $_              # du
        8
        9 ls -al >/dev/null    # 沒有命令輸出
        10 echo $_              # -al  (最後的引數)
        11
        12 :
        13 echo $_              # :
        ################################End Script#########################################

           $?
               命令,函式或者指令碼本身的退出狀態(見Example 23-7)

           $$
               指令碼自身的程序ID.這個變數經常用來構造一個"unique"的臨時檔名.
               (參考Example A-13,Example 29-6,Example 12-28和Example 11-25).
               這通常比呼叫mktemp來得簡單.

        注意事項:
        [1]        當前執行的指令碼的PID為$$.
        [2]        "argument"和"parameter"這兩個單詞經常不加區分的使用.在這整本書中,這兩個
               單詞的意思完全相同.(在翻譯的時候就未加區分,統統翻譯成引數)

        9.2 操作字串
        ————–
        Bash支援超多的字串操作,操作的種類和數量令人驚異.但不幸的是,這些工具缺乏集中性.
        一些是引數替換的子集,但是另一些則屬於UNIX的expr命令.這就導致了命令語法的不一致和
        功能的重疊,當然也會引起混亂.

        字串長度

           ${#string}
           expr length $string
           expr "$string" : ‘.*’

           1 stringZ=abcABC123ABCabc
           2
           3 echo ${#stringZ}                 # 15
           4 echo `expr length $stringZ`      # 15
           5 echo `expr "$stringZ" : ‘.*’`    # 15

        Example 9-10 在一個文字檔案的段間插入空行
        ################################Start Script#######################################
        1 #!/bin/bash
        2 # paragraph-space.sh
        3
        4 # 在一個不空行的文字檔案的段間插入空行.
        5 # Usage: $0 6
        7 MINLEN=45        # 可能需要修改這個值.
        8 #  假定行的長度小於$MINLEN指定的長度
        9 # $MINLEN中的值用來描述多少個字元結束一個段.
        10
        11 while read line  # 對於需要多行輸入的檔案基本都是這個樣子
        12 do
        13   echo "$line"   # 輸出line.
        14
        15   len=${#line}
        16   if [ "$len" -lt "$MINLEN" ]
        17     then echo    # 在短行後邊新增一個空行
        18   fi  
        19 done
        20
        21 exit 0
        ################################End Script#########################################

        從字串開始的位置匹配子串的長度

           expr match "$string" ‘$substring’
               $substring是一個正規表示式
           
           expr "$string" : ‘$substring’
               $substring是一個正規表示式

           1 stringZ=abcABC123ABCabc
           2 #       |——|
           3
           4 echo `expr match "$stringZ" ‘abc[A-Z]*.2’`   # 8
           5 echo `expr "$stringZ" : ‘abc[A-Z]*.2’`       # 8

        索引

           expr index $string $substring
               匹配到子串的第一個字元的位置.

           1 stringZ=abcABC123ABCabc
           2 echo `expr index "$stringZ" C12`             # 6
           3                                              # C position.
           4
           5 echo `expr index "$stringZ" 1c`              # 3
           6 # ‘c’ (in #3 position) matches before ‘1’.

           在C語言中最近的等價函式為strchr().

        提取子串
           
           ${string:position}
               在string中從位置$position開始提取子串.
               如果$string為"*"或"@",那麼將提取從位置$position開始的位置引數,[1]

           ${string:position:length}
               在string中從位置$position開始提取$length長度的子串.

        ################################Start Script#######################################
        1 stringZ=abcABC123ABCabc
        2 #       0123456789…..
        3 #       0-based indexing.
        4
        5 echo ${stringZ:0}                            # abcABC123ABCabc
        6 echo ${stringZ:1}                            # bcABC123ABCabc
        7 echo ${stringZ:7}                            # 23ABCabc
        8
        9 echo ${stringZ:7:3}                          # 23A
        10                                              # 3個字元長度的子串.
        11
        12
        13
        14 # 有沒有可能從字元結尾開始,反向提取子串?
        15    
        16 echo ${stringZ:-4}                           # abcABC123ABCabc
        17 # 以${parameter:-default}方式,預設是提取完整地字串.
        18 # 然而 . . .
        19
        20 echo ${stringZ:(-4)}                         # Cabc
        21 echo ${stringZ: -4}                          # Cabc
        22 # 現在,它可以工作了.
        23 # 使用圓括號或者新增一個空格來轉義這個位置引數.
        24
        25 # Thank you, Dan Jacobson, for pointing this out.
        ################################End Script#########################################
               如果$string引數為"*"或"@",那將最大的提取從$position開始的$length個位置引數.
           1 echo ${*:2}          # Echo出第2個和後邊所有的位置引數.
           2 echo ${@:2}          # 與前邊相同.
           3
           4 echo ${*:2:3}        # 從第2個開始,Echo出後邊3個位置引數.

           expr substr $string $position $length
               在string中從位置$position開始提取$length長度的子串.
           1 stringZ=abcABC123ABCabc
           2 #       123456789……
           3 #       1-based indexing.
           4
           5 echo `expr substr $stringZ 1 2`              # ab
           6 echo `expr substr $stringZ 4 3`              # ABC

           expr match "$string" ‘/($substring/)’
               從$string的開始位置提取$substring,$substring是一個正規表示式.

           expr "$string" : ‘/($substring/)’
               從$string的開始位置提取$substring,$substring是一個正規表示式.
           1 stringZ=abcABC123ABCabc
           2 #       =======        
           3
           4 echo `expr match "$stringZ" ‘/(.[b-c]*[A-Z]..[0-9]/)’`   # abcABC1
           5 echo `expr "$stringZ" : ‘/(.[b-c]*[A-Z]..[0-9]/)’`       # abcABC1
           6 echo `expr "$stringZ" : ‘/(……./)’`                   # abcABC1
           7 # All of the above forms give an identical result.

        子串削除

           ${string#substring}
                從$string的左邊截掉第一個匹配的$substring
           ${string##substring}
                從$string的左邊截掉最後一個個匹配的$substring

           1 stringZ=abcABC123ABCabc
           2 #       |—-|
           3 #       |———-|
           4
           5 echo ${stringZ#a*C}      # 123ABCabc
           6 # 截掉’a’和’C’之間最近的匹配.
           7
           8 echo ${stringZ##a*C}     # abc
           9 # 截掉’a’和’C’之間最遠的匹配.
               
           
           ${string%substring}
                從$string的右邊截掉第一個匹配的$substring
           ${string%%substring}
                從$string的右邊截掉最後一個匹配的$substring

           1 stringZ=abcABC123ABCabc
           2 #                    ||
           3 #        |————|
           4
           5 echo ${stringZ%b*c}      # abcABC123ABCa
           6 # 從$stringZ的後邊開始截掉’b’和’c’之間的最近的匹配
           7
           8 echo ${stringZ%%b*c}     # a
           9 # 從$stringZ的後邊開始截掉’b’和’c’之間的最遠的匹配

        Example 9-11 利用修改檔名,來轉換圖片格式
        ################################Start Script#######################################

        <script type="text/javascript">

        google_ad_client = "pub-2416224910262877";
        google_ad_width = 728;
        google_ad_height = 90;
        google_ad_format = "728x90_as";
        google_ad_channel = "";
        google_color_border = "E1771E";
        google_color_bg = "FFFFFF";
        google_color_link = "0000FF";
        google_color_text = "000000";
        google_color_url = "008000";

        </script><script type="text/javascript"
        src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
        </script>