寫在前面(最重要)
本文部分資料和示例援引自以下書籍。在此,感謝原作者的創作,以及所有譯者的付出,向他們致敬。
其中 《高級Bash腳本編程指南》Revision 10中文版 是 《Advanced Bash-Scripting Guide》 的中文翻譯版,文檔翻譯正在進行中,再次感謝譯者付出。
前言
在之前的文章 Linux 基礎命令(十)—— SHELL腳本編程進階(一) 中,我們已經詳細的介紹了SHELL腳本編程的循環與分支的相關內容,這些循環與分支在bash編程中有着至關重要的地位。今天我們將要介紹bash編程進階的第二部分,也是實際使用中很重要的一部分,函數和數組。
函數和數組
第一部分 函數
1. 函數定義
2. 函數使用
3. 函數返回值
4. 函數參數
5. 函數變量
6. 函數的遞歸調用
第二部分 數組
1. 數組聲明與賦值
2. 數組引用
3. 數組的數據處理
第一部分 函數
函數定義
函數,任何一門高級編程語言中都具備的一種代碼結構。其實是借用了工程上模塊化的思想。函數的作用能夠將簡化代碼的編寫,使得程序結構更加的清晰。同時函數能夠複用重複的代碼,實現代碼重用和模塊化。 bash中函數是由若干條shell命令組成的語句塊,與shell程序形式上是相似的,不同的是它不是一個單獨的進程,不能獨立運行,而是shell程序的一部分。 函數由兩部分組成:函數名和函數體 ,bash中函數的定義如下所示。
#語法一: function fun_name{ ...函數體... } #語法二: function fun_name (){ ...函數體... } #語法三:fun_name (){ ...函數體... }
實際使用過程中, 三種方式沒有什麼區別,根據自己的喜好去使用就可以。
函數使用
函數使用場景一般是在bash腳本中,定義函數,然後進行調用。
#!/bin/bash # #函數定義 hello (){ echo "Hello World!" } #函數調用 hello
同時也推薦,bash 腳本中需要使用的函數全部定義到一個單獨的文件中,然後在bash腳本中進行引用。這樣做的好處就是不光這一個bash腳本可以引用該文件裏面的函數,其他的bash腳本也可以引用裏面的某些函數。CentOS典型的例子有很多,例如/etc/init.d/functions文件。
#單獨定義一個函數文件 #執行vim /app/funcs #函數定義hello (){ echo "Hello World!" } #函數調用 hello
然後在bash腳本中進行調用
#!/bin/bash #引用函數文件 . /app/funcs #調用函數名稱 hello
函數返回值
bash中有兩種返回值
函數執行結果返回值,使用echo命令返回,相當於Java 中的return關鍵字。
函數的退出狀態碼,默認是函數體最後一條命令的退出狀態碼。當然也可以自定義,使用return關鍵字。自定義退出狀態碼,其格式爲:
return 從函數中返回,用最後狀態命令決定返回值。
return 0 無錯誤返回
return 1-255 有錯誤返回
#!/bin/bash # #定義一個能夠返回字符串的函數 hello_echo (){ echo "Hello" } #定義一個返回命令執行結果的函數 hello_return (){ ls /etc/* &> /dev/null return } #定義一個自定義狀態結果的函數 hello_return_error (){ echo "test" &> /dev/null return 1 } #調用函數的返回結果 echo "result 1 is $(hello_echo)" #調用函數的結果狀態值 hello_return echo "result 2 is $?" #調用函數的結果狀態值 hello_return_error echo "result 3 is $?"
函數參數
bash 中函數支持參數的傳遞。但是與其他高級編程語言不同的是,bash中並不會顯示的指定參數的類型和個數,而是直接在函數的最後的傳入參數。與bash腳本的參數傳遞是一致的。
在函數體中當中,可使用$1, $2, …調用這些參數;還可以使用$@, $*, $#等特殊變量。這一點與bash腳本幾乎一致。
#!/bin/bash#hello (){ echo "參數的個數是$#" echo "輸出所有的參數$@" echo "輸出所有的參數$*" echo "第一個參數是$1" for arg in $@;do echo "$arg" done } #調用函數,並在調用的時候傳入參數。 hello python java c#
因爲bash的寫法太過於靈活,以至於讓人感覺bash並不是很嚴謹,在使用的時候可能要拋棄以往那些高級編程語言的思維。上面示例的結果如下圖所示。
[root@localhost function]#./f4.sh 參數的個數是3 輸出所有的參數python java c# 輸出所有的參數python java c# 第一個參數是python python java c#
函數變量
bash中的變量的作用域有三種類型:
環境變量:在當前shell和子shell中有效。
本地變量:只在當前shell進程中有效。
局部變量:只在函數的生命週期中有效。函數運行結束,變量失效。
在函數中定義局部變量的的方式是
local VARIABLE_NAME=VALUE
#!/bin/bash # hello (){ #定義一個函數體內的局部變量 local name="HelloBash" echo $name } echo $name #沒有值 因爲腳本中並沒有定義這樣一個本地變量 hello #會輸出HelloBash
函數的遞歸調用
函數的遞歸調用指的是,函數間接地或者直接地調用自身。但是在遞歸地同時一定要注意什麼時候結束遞歸,避免死循環。 這是編程地一種基本能力。
實驗一 實現斐波那契數列
斐波那契數列又稱黃金分割數列,因數學家列昂納多·斐波那契以兔子繁殖爲例子而引入,故又稱爲“兔子數列”,指的是這樣一個數列:0、1、1、2、3、5、8、13、21、34、……,斐波納契數列以如下被以遞歸的方法定義:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2)利用函數,求n階斐波那契數列
#!/bin/bash##實現了斐波那契數列read -p "請輸入階數" N fibonacci (){ if (( $1==0 ));then echo 0 elif (( $1==1 ));then echo 1 else #echo $(($[fibonacci $[$1-1]]+$[fibonacci $[$1-2]])) let num=$(fibonacci $[$1-1])+$(fibonacci $[$1-2]) echo $num fi } if [ $N -ge 0 ] &> /dev/null ;then for I in `seq 0 $N`;do fibonacci $I done else echo "$N 不是數字或者$N 小於0" exit fi
實驗二 實現漢諾塔
漢諾塔(又稱河內塔)問題是源於印度一個古老傳說。大梵天創造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞着64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。並且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤,利用函數,實現N片盤的漢諾塔的移動步驟。
#!/bin/bash read -p "please input the number of plates:" NUM #執行的步驟數 STEPS=0 move () { let STEPS++ echo "$STEPS: move plate $1 $2---------->$3" } #傳入的參數 NUM A B C hanoi () { if [ $1 -eq 1 ];then move 1 $2 $4 else hanoi $[$1-1] $2 $4 $3 move $1 $2 $4 hanoi $[$1-1] $3 $2 $4 fi } hanoi $NUM A B C
實驗三 fork×××
fork ×××是一種惡意地程序。實質上是一個簡單地遞歸程序,由於程序是遞歸的,如果沒有任何限制,這會導致這個簡單的程序迅速耗盡系統裏面的所有資源。
#在bash命令提示符後輸入以下內容,系統很快就會崩潰。 :(){ :|:& };: #下面這段代碼是fork×××地形象展示。 bomb() { bomb | bomb & }; bomb
順便附上前幾年比較火的讓瀏覽器瞬間崩潰的12行代碼,其實原理與fork×××是類似的。
<html> <body> <script> var total=""; for (var i=0;i<1000000;i++) { total= total+i.toString(); history.pushState(0,0,total); } </script> </body> </html>
第二部分 數組
bash 中沒有像其他高級編程語言那麼多的基本數據類型(int double)和引用數據類型(List ArrayList)。bash是一種弱類型的編程語言。 數組是存儲多個元素的連續的內存空間,相當於多個變量的集合。
數組聲明與賦值
數組的聲明有兩種方式
顯示聲明:
declare -a ARRAY_NAME
直接賦值:
ARRAY_NAME[INDEX]=VALUE
,如果使用這種方式,Bash會自動創建數組。
數組的賦值有多種方式
一次只賦值一個元素
ARRAY_NAME[INDEX]=VALUE
一次賦值全部元素
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
只賦值特定元素
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
在介紹了數組的引用之後,一併來舉例說明數組的使用。
注意:索引可支持使用自定義的格式,而不僅是數值格式,即爲關聯索引,bash4.0版本之後開始支持,這就是關聯數組,有點類似於高級編程語言中的字典
數組引用
數組的引用有下面幾種方式
${ARRAY_NAME[INDEX]}
省略[INDEX]表示引用下標爲0的元素引用所有的元素
${ARRAY_NAME[*]}
,${ARRAY_NAME[@]}
數組的長度
${#ARRAY_NAME[*]}
,${#ARRAY_NAME[@]}
刪除數組中的某個元素:導致稀疏格式 ,
unset ARRAY[INDEX]
刪除整個數組:
unset ARRAY
數組的數據處理
數組數據的處理涉及到了數組數據的讀取和追加。
數組切片
${ARRAY[@]:offset:number}
offset :要跳過的元素個數
number:要取出的元素個數
區偏移量之後的所有元素
${ARRAY[@]:offset}
向數組中追加元素
ARRAY[${#ARRAY[*]}]=value
實驗一 實現矩陣轉置
# 編寫腳本實現矩陣轉置 matrix.sh # 1 2 3 1 4 7 # 4 5 6 ==>> 2 5 8 # 7 8 9 3 6 9 #!/bin/bash declare -A matrix num_rows=3 num_columns=3 matrix=([1,1]="1" [1,2]="2" [1,3]="3" [2,1]="4" [2,2]="5" [2,3]="6" [3,1]="7" [3,2]="8" [3,3]="9" ) f1="%9s" for ((i=1;i<=num_rows;i++)) do for ((j=1;j<=num_columns;j++)) do printf "$f1" ${matrix[$i,$j]} done echo done echo echo for ((j=1;j<=num_columns;j++)) do for ((i=1;i<=num_rows;i++)) do printf "$f1" ${matrix[$i,$j]} done echo done
這裏我們需要注意的是,在bash中並沒有二維數組的這個概念。所以我們只能夠使用自定義下標的方式來模擬二維數組,原理上是相似的。 實現效果如下圖所示。
實驗二 編寫腳本,定義一個數組,數組中的元素是/var/log目錄下所有以.log結尾的文件;要統計其下標爲偶數的文件中的行數之和
#!/bin/bash # #bash中數組支持直接使用通配符來進行賦值 declare -a files=(/var/log/*.log) declare -i lines=0 for i in $(seq 0 $[${#files[*]}-1]);do #判斷下標是不是偶數 if [ $[$i%2] -eq 0 ];then let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1) fi done echo "Lines:$lines"
迄今爲止,bash編程的大部分知識都已經介紹的差不多了,但是介紹的內容比較淺顯,並沒有深入的介紹。同時bash編程由於具有巨大的靈活性,導致使用方式多種多樣,實際生產中應該根據自己的實際情況來靈活使用。
個人博客地址:http://www.pojun.tech/ 歡迎訪問