高级Bash脚本编程指南
生活随笔
收集整理的這篇文章主要介紹了
高级Bash脚本编程指南
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
http://www.linuxdiyf.com/viewarticle.php?id=18812
毫無疑問,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:thegrendel@theriver.com
這本書假定你沒有任何腳本或一般程序的編程知識,但是如果你有相關的知識,那么你將很容易
達到中高級的水平...all the while sneaking in little snippets of UNIX? wisdom and
lore(這句不知道怎么譯).你可以把本書作為教材,自學手冊,或者你獲得shell腳本技術的文檔.
書中的練習和例子腳本中的注釋將會與讀者有更好的互動,但是最關鍵的前提是:
想真正學習腳本編程的唯一途徑就是編寫腳本.
這本書也可作為教材來講解一般的編程概念.
下載本書最新版本, http://personal.riverusers.com/~thegrendel/abs-guide-3.7.tar.bz2,
這是一個以tar和bzip2進行打包的,并且是以HTML來發行的.當然,你也可以獲得本書的pdf版本
在 http://www.tldp.org/LDP/abs/abs-guide.pdf.可以在
http://personal.riverusers.com/~thegrendel/Change.log中查看修訂歷史.
譯者:楊春敏,黃毅
mail:chunmin.yang@gmail.com
一直想好好學習一下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:通過$*和$@列出所有的參數
9-7. 不一致的$*和$@行為
9-8. 當$IFS為空時的$*和$@
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/Quartz/computer/unix/csh.harmful.gz中查看具體內容.)
接下來是腳本的一些說明.在展示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 <SCRIPTNAME,因為這禁用了腳本從STDIN中讀數據的功能.)
更方便的方法是讓腳本本身就具有可執行權限,通過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/onlinepubs/007904975/toc.htm中查閱.
[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節.
$*,$@ 位置參數
$? 退出狀態變量.$?保存一個命令/一個函數或者腳本本身的退出狀態.
$$ 進程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 <a.cox@swansea.ac.uk>, 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}...
兩個值得注意的變量$*和$@(第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 $1@whois.ripe.net;;
26 "wh-ripe") whois $1@whois.ripe.net;;
27 "wh-radb") whois $1@whois.radb.net;;
28 "wh-cw" ) whois $1@whois.cw.net;;
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 <<EOF
21 \z
22 EOF # \z
23
24 cat <<EOF
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 <ligelowbee@yahoo.com>
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` || directorys=$@
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 100x100-5+5 &
9 xterm -geometry 80x50-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 # gcd.sh: 最大公約數
3 # 使用Euclid's 算法
4
5 # 最大公約數,就是2個數能夠同時整除的最大的數.
6 #
7
8 # Euclid's算法采用連續除法.
9 # 在每個循環中
10 #+ 被除數 <--- 除數
11 #+ 除數 <--- 余數
12 #+ 直到余數= 0.
13 #+ 在最后的循環中The gcd = 被除數
14 #
15 # 關于這個算法更精彩的討論
16 # 見Jim Loy's site, http://www.jimloy.com/number/euclids.htm.
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 gcd ()
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 gcd.
50 } # 最后的$dividend就是gcd.
51
52
53 gcd $1 $2
54
55 echo; echo "GCD 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 #
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 #
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)
$*
所有的位置參數,被作為一個單詞.
注意:"$*"必須被""引用.
$@
與$*同義,但是每個參數都是一個獨立的""引用字串,這就意味著參數被完整地傳遞,
并沒有被解釋和擴展.這也意味著,每個參數列表中的每個參數都被當成一個獨立的
單詞.
注意:"$@"必須被引用.
Example 9-6 arglist:通過$*和$@列出所有的參數
################################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 \"\$@\":"
31 for arg in "$@"
32 do
33 echo "Arg #$index = $arg"
34 let "index+=1"
35 done # $@ 認為每個參數都一個單獨的單詞.
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命令后邊,$@將保存命令行中剩余的參數,而$1被丟掉了.
1 #!/bin/bash
2 # 使用 ./scriptname 1 2 3 4 5 來調用這個腳本
3
4 echo "$@" # 1 2 3 4 5
5 shift
6 echo "$@" # 2 3 4 5
7 shift
8 echo "$@" # 3 4 5
9
10 # 每個"shift"都丟棄$1.
11 # "$@" 將包含剩下的參數.
$@也作為為工具使用,用來過濾傳給腳本的輸入.
cat "$@"結構接受從stdin傳來的輸入,也接受從參數中指定的文件傳來的輸入.
具體見Example 12-21和Example 12-22.
注意:$*和$@的參數有時會不一致,發生令人迷惑的行為,這依賴于$IFS的設置.
Example 9-7 不一致的$*和$@行為
################################Start Script#######################################
1 #!/bin/bash
2
3 # "$*"和"$@"的古怪行為,
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 "$@"'
29 c=0
30 for i in "$@"
31 do echo "$((c+=1)): [$i]"
32 done
33 echo ---
34
35 echo 'IFS unchanged, using $@'
36 c=0
37 for i in $@
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 "$@"'
88 c=0
89 for i in "$@"
90 do echo "$((c+=1)): [$i]"
91 done
92 echo ---
93
94 echo 'IFS=":", using $@'
95 c=0
96 for i in $@
97 do echo "$((c+=1)): [$i]"
98 done
99 echo ---
100
101 var=$@
102 echo 'IFS=":", using $var (var=$@)'
103 c=0
104 for i in $var
105 do echo "$((c+=1)): [$i]"
106 done
107 echo ---
108
109 echo 'IFS=":", using "$var" (var=$@)'
110 c=0
111 for i in "$var"
112 do echo "$((c+=1)): [$i]"
113 done
114 echo ---
115
116 var="$@"
117 echo 'IFS=":", using "$var" (var="$@")'
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="$@")'
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#########################################
注意:$@和$*中的參數只有在""中才會不同.
Example 9-8 當$IFS為空時的$*和$@
################################Start Script#######################################
1 #!/bin/bash
2
3 # 如果$IFS被設置為空時,
4 #+ 那么"$*" 和"$@" 將不會象期望那樣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 $@ # a,b,c
19 mecho "$@" # a,b,c
20
21 # 當$IFS設置為空時,$* 和$@ 的行為依賴于
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 <FILENAME
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#######################################
1 #!/bin/bash
2 # cvt.sh:
3 # 把一個目錄下的所有MacPaint格式的圖片文件都轉換為"pbm"格式的圖片文件.
4
5 # 使用來自"netpbm"包的"macptopbm"程序,
6 #+ 這個程序主要是由Brian Henderson(bryanh@giraffe-data.com)來維護的.
7 # Netpbm是大多數Linux發行版的標準部分.
8
9 OPERATION=macptopbm
10 SUFFIX=pbm # 新的文件名后綴
11
12 if [ -n "$1" ]
13 then
14 directory=$1 # 如果目錄名作為第1個參數給出...
15 else
16 directory=
總結
以上是生活随笔為你收集整理的高级Bash脚本编程指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python实现 ---【简易】1230
- 下一篇: Eclipse 优化