天天看點

Bash腳本15分鐘進階教程

這裡的技術技巧最初是來自谷歌的“testing on the toilet” (tott)。這裡是一個修訂和擴增版本。

Bash腳本15分鐘進階教程

<a target="_blank"></a>

我的所有bash腳本都以下面幾句為開場白:

#!/bin/bash

set -o nounset

set -o errexit

這樣做會避免兩種常見的問題:

引用未定義的變量(預設值為“”)

執行失敗的指令被忽略

需要注意的是,有些linux指令的某些參數可以強制忽略發生的錯誤,例如“mkdir -p” 和 “rm -f”。

在bash裡你可以定義函數,它們就跟其它指令一樣,可以随意的使用;它們能讓你的腳本更具可讀性:

extractbashcomments() {

egrep "^#"

}

cat myscript.sh | extractbashcomments | wc

comments=$(extractbashcomments &lt; 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 &lt; data_one_number_per_line.txt

log() { # classic logger

local prefix="[$(date +%y/%m/%d\ %h:%m:%s)]: "

echo "${prefix} $@" &gt;&amp;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(僅雙中括号裡使用)

&amp;&amp;

邏輯and(僅雙中括号裡使用)

&lt;

字元串比較(雙中括号裡不需要轉移)

-lt

數字比較

=

字元串相等

==

以globbing方式進行字元串比較(僅雙中括号裡使用,參考下文)

=~

用正規表達式進行字元串比較(僅雙中括号裡使用,參考下文)

-n

非空字元串

-z

空字元串

-eq

數字相等

-ne

數字不等

單中括号:

[ "${name}" \&gt; "a" -o ${name} \&lt; "m" ]

雙中括号

[[ "${name}" &gt; "a" &amp;&amp; "${name}" &lt; "m" ]]

使用雙中括号帶來的好處用下面幾個例子最能表現:

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*) &lt;action&gt; ;;

esac

bash裡有各種各樣操作字元串的方式,很多都是不可取的。

基本使用者

f="path1/path2/file.ext"

len="${#f}" # = 20 (字元串長度)

# 切片操作: ${&lt;var&gt;:&lt;start&gt;} or ${&lt;var&gt;:&lt;start&gt;:&lt;length&gt;}

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)

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)

# 删除字元串頭部

extension="${f#*.}" # = "ext"

# 以貪婪比對方式删除字元串頭部

filename="${f##*/}" # = "file.ext"

# 删除字元串尾部

dirname="${f%/*}" # = "path1/path2"

# 以貪婪比對方式删除字元串尾部

root="${f%%/*}" # = "path1"

有些指令需要以檔案名為參數,這樣一來就不能使用管道。這個時候 &lt;() 就顯出用處了,它可以接受一個指令,并把它轉換成可以當成檔案名之類的什麼東西:

# 下載下傳并比較兩個網頁

diff &lt;(wget -o - url1) &lt;(wget -o - url2)

還有一個非常有用處的是”here documents”,它能讓你在标準輸入上輸入多行字元串。下面的’marker’可以替換成任何字詞。

# 任何字詞都可以當作分界符

command &lt;&lt; marker

${var}

$(cmd)

marker

如果文本裡沒有内嵌變量替換操作,你可以把第一個marker用單引号包起來:

command &lt;&lt; 'marker'

no substitution is happening here.

$ (dollar sign) is passed through verbatim.

變量

說明

$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。當在遠端機器上執行腳本時,這樣做非常有用,用它來輸出遠端資訊。

你的腳本太長,多達幾百行

你需要比數組更複雜的資料結構

出現了複雜的轉義問題

有太多的字元串操作

不太需要調用其它程式和跟其它程式管道互動

擔心性能

這個時候,你應該考慮一種腳本語言,比如python或ruby。

原文釋出時間為:2014-04-25

本文來自雲栖社群合作夥伴“linux中國”