Bash指令碼15分鐘進階指導
這裡的技術技巧最初是來自谷歌的“Testing on the Toilet” (TOTT)。這裡是一個修訂和擴增版本。
指令碼安全
我的所有bash指令碼都以下面幾句為開場白:
#!/bin/bash set -o nounset set -o errexit
這樣做會避免兩種常見的問題:
- 引用未定義的變數(預設值為“”)
- 執行失敗的命令被忽略
需要注意的是,有些Linux命令的某些引數可以強制忽略發生的錯誤,例如“mkdir -p” 和 “rm -f”。
還要注意的是,在“errexit”模式下,雖然能有效的捕捉錯誤,但並不能捕捉全部失敗的命令,在某些情況下,一些失敗的命令是無法檢測到的。(更多細節請參考這個帖子。)
指令碼函式
在bash裡你可以定義函式,它們就跟其它命令一樣,可以隨意的使用;它們能讓你的指令碼更具可讀性:
ExtractBashComments() { egrep "^#" } cat myscript.sh | ExtractBashComments | wc comments=$(ExtractBashComments < myscript.sh)
還有一些例子:
SumLines() { # iterating over stdin - similar to awk local sum=0 local line=”” while read line ; do sum=$((${sum} + ${line})) done echo ${sum} } SumLines < data_one_number_per_line.txt log() { # classic logger local prefix="[$(date +%Y/%m/%d\ %H:%M:%S)]: " echo "${prefix} $@" >&2 } log "INFO" "a message"
儘可能的把你的bash程式碼移入到函式裡,僅把全域性變數、常量和對“main”呼叫的語句放在最外層。
變數註解
Bash裡可以對變數進行有限的註解。最重要的兩個註解是:
- local(函式內部變數)
- readonly(只讀變數)
# a useful idiom: DEFAULT_VAL can be overwritten # with an environment variable of the same name readonly DEFAULT_VAL=${DEFAULT_VAL:-7} myfunc() { # initialize a local variable with the global default local some_var=${DEFAULT_VAL} ... }
這樣,你可以將一個以前不是隻讀變數的變數宣告成只讀變數:
x=5 x=6 readonly x x=7 # failure
儘量對你bash指令碼里的所有變數使用local或readonly進行註解。
用$()代替反單引號(`)
反單引號很難看,在有些字型裡跟正單引號很相似。$()能夠內嵌使用,而且避免了轉義符的麻煩。
# both commands below print out: A-B-C-D echo "A-`echo B-\`echo C-\\\`echo D\\\`\``" echo "A-$(echo B-$(echo C-$(echo D)))"
用[[]](雙層中括號)替代[]
使用[[]]能避免像異常的副檔名之類的問題,而且能帶來很多語法上的改進,而且還增加了很多新功能:
操作符 | 功能說明 |
---|---|
|| | 邏輯or(僅雙中括號裡使用) |
&& | 邏輯and(僅雙中括號裡使用) |
< | 字串比較(雙中括號裡不需要轉移) |
-lt | 數字比較 |
= | 字串相等 |
== | 以Globbing方式進行字串比較(僅雙中括號裡使用,參考下文) |
=~ | 用正規表示式進行字串比較(僅雙中括號裡使用,參考下文) |
-n | 非空字串 |
-z | 空字串 |
-eq | 數字相等 |
-ne | 數字不等 |
單中括號:
[ "${name}" \> "a" -o ${name} \< "m" ]
雙中括號
[[ "${name}" > "a" && "${name}" < "m" ]]
正規表示式/Globbing
使用雙中括號帶來的好處用下面幾個例子最能表現:
t="abc123" [[ "$t" == abc* ]] # true (globbing比較) [[ "$t" == "abc*" ]] # false (字面比較) [[ "$t" =~ [abc]+[123]+ ]] # true (正規表示式比較) [[ "$t" =~ "abc*" ]] # false (字面比較)
注意,從bash 3.2版開始,正規表示式和globbing表示式都不能用引號包裹。如果你的表示式裡有空格,你可以把它儲存到一個變數裡:
r="a b+" [[ "a bbb" =~ $r ]] # true
按Globbing方式的字串比較也可以用到case語句中:
case $t in abc*) <action> ;; esac
字串操作
Bash裡有各種各樣操作字串的方式,很多都是不可取的。
基本使用者
f="path1/path2/file.ext" len="${#f}" # = 20 (字串長度) # 切片操作: ${<var>:<start>} or ${<var>:<start>:<length>} slice1="${f:6}" # = "path2/file.ext" slice2="${f:6:5}" # = "path2" slice3="${f: -8}" # = "file.ext"(注意:"-"前有空格) pos=6 len=5 slice4="${f:${pos}:${len}}" # = "path2"
替換操作(使用globbing)
f="path1/path2/file.ext" single_subst="${f/path?/x}" # = "x/path2/file.ext" global_subst="${f//path?/x}" # = "x/x/file.ext" # 字串拆分 readonly DIR_SEP="/" array=(${f//${DIR_SEP}/ }) second_dir="${arrray[1]}" # = path2
刪除頭部或尾部(使用globbing)
f="path1/path2/file.ext" # 刪除字串頭部 extension="${f#*.}" # = "ext" # 以貪婪匹配方式刪除字串頭部 filename="${f##*/}" # = "file.ext" # 刪除字串尾部 dirname="${f%/*}" # = "path1/path2" # 以貪婪匹配方式刪除字串尾部 root="${f%%/*}" # = "path1"
避免使用臨時檔案
有些命令需要以檔名為引數,這樣一來就不能使用管道。這個時候 <() 就顯出用處了,它可以接受一個命令,並把它轉換成可以當成檔名之類的什麼東西:
# 下載並比較兩個網頁 diff <(wget -O - url1) <(wget -O - url2)
還有一個非常有用處的是”here documents”,它能讓你在標準輸入上輸入多行字串。下面的’MARKER’可以替換成任何字詞。
# 任何字詞都可以當作分界符 command << MARKER ... ${var} $(cmd) ... MARKER
如果文字里沒有內嵌變數替換操作,你可以把第一個MARKER用單引號包起來:
command << 'MARKER' ... no substitution is happening here. $ (dollar sign) is passed through verbatim. ... MARKER
內建變數
變數 | 說明 |
---|---|
$0 | 指令碼名稱 |
$n | 傳給指令碼/函式的第n個引數 |
$$ | 指令碼的PID |
$! | 上一個被執行的命令的PID(後臺執行的程式) |
$? | 上一個命令的退出狀態(管道命令使用${PIPESTATUS}) |
$# | 傳遞給指令碼/函式的引數個數 |
$@ | 傳遞給指令碼/函式的所有引數(識別每個引數) |
$* | 傳遞給指令碼/函式的所有引數(把所有引數當成一個字串) |
提示
使用$*很少是正確的選擇。
$@能夠處理空格引數,而且引數間的空格也能正確的處理。
使用$@時應該用雙引號括起來,像”$@”這樣。
除錯
對指令碼進行語法檢查:
bash -n myscript.sh
跟蹤指令碼里每個命令的執行:
bash -v myscripts.sh
跟蹤指令碼里每個命令的執行並附加擴充資訊:
bash -x myscript.sh
你可以在指令碼頭部使用set -o verbose和set -o xtrace來永久指定-v和-o。當在遠端機器上執行指令碼時,這樣做非常有用,用它來輸出遠端資訊。
什麼時候不應該使用bash指令碼
- 你的指令碼太長,多達幾百行
- 你需要比陣列更復雜的資料結構
- 出現了複雜的轉義問題
- 有太多的字串操作
- 不太需要呼叫其它程式和跟其它程式管道互動
- 擔心效能
這個時候,你應該考慮一種指令碼語言,比如Python或Ruby。
參考
- Advanced Bash-Scripting Guide: http://tldp.org/LDP/abs/html/
- Bash Reference Manual
相關文章
- 幫助Linux運維十分鐘完成 Bash 指令碼進階!Bash經典用法及其案例詳解列舉!Linux運維指令碼
- Bash指令碼指令碼
- Bash 常用指令碼片段指令碼
- Bash指令碼debug攻略指令碼
- Bash 指令碼簡介指令碼
- Bash 指令碼程式設計的一些高階用法指令碼程式設計
- 指令碼前面的/bin/bash指令碼
- 《Bash 指令碼教程》釋出了指令碼
- bashdb除錯bash指令碼除錯指令碼
- 批量修改檔名的bash指令碼指令碼
- 世界上最短的bash指令碼指令碼
- shell和bash指令碼命令學習指令碼
- 關於 Bash 指令碼中 Shebang 的趣事指令碼
- bash shell指令碼接受多個引數指令碼
- Bash 指令碼中的錯誤處理指令碼
- 執行shell指令碼報錯:-bash: ./test1.sh: /bin/bash^M: ...指令碼
- Linux編寫Bash指令碼的10個技巧Linux指令碼
- 如何編寫冪等的 Bash 指令碼?- Arslan指令碼
- [20210330]bash使用source or ..呼叫shell指令碼注意txt指令碼
- (20201026已解決)bash指令碼中給rsync指定密碼指令碼密碼
- [20231023]生成bbed的執行指令碼(bash shell).txt指令碼
- Bash 指令碼安裝 MySQL-8.0.20 資料庫指令碼MySql資料庫
- 一個能夠生成 Markdown 表格的 Bash 指令碼指令碼
- Bash Shell指令碼中的陣列使用例項指令碼陣列
- 使用Git Bash進行程式碼管理Git行程
- JavaScript進階之(一) this指標JavaScript指標
- c-指標進階篇指標
- C++進階(智慧指標)C++指標
- Java小白進階筆記(5)-進階物件導向Java筆記物件
- TWI工作指導的四階段法
- 跟我一起寫shell補全指令碼(Bash篇)指令碼
- shell指令碼頭,#!/bin/sh與#!/bin/bash的區別.指令碼
- [20231102]除錯bash shell指令碼遇到的問題.txt除錯指令碼
- 生成 Linux 執行時間報告的 Bash 指令碼Linux指令碼
- 在 Linux 上用 Bash 指令碼監控 messages 日誌Linux指令碼
- Bash 指令碼:`(反引號)運算子和 $()的使用方式指令碼
- [20210107]編寫bash shell指令碼遇到的問題.txt指令碼
- 10分鐘快速進階rollup.jsJS
- Python進階之物件導向Python物件