在上一章當中咱們講述了bash循環,其中咱們講述了for
循環的特殊用法,以及while
循環的特殊用法,而在此前咱們講述了循環的控制語句,一個是break
,另外一個是continue
,對於continue來講,它是結束本輪循環然後進入下一輪循環,而break是提早結束其循環自己。但若是是循環嵌套的話,break只能退出當前那一層的循環,若是想退出全部的循環,就要使用break後面加上一個數字用來跳出循環的層數。然後while
循環的特殊用法能夠遍歷文件的每一行,for以及while循環能夠寫成:shell
for (()); do 循環體 done while read VARIABLE; do 循環體 done < /PATH/TO/SOMEFILE
咱們此前講到過,任何一個程序控制語言,在執行該程序時,都由順序、選擇及循環這三種組成,而對於判斷條件的語句來講,主要有if
和case
,咱們回顧一下多分支的if語句組成格式爲:編程
多分支if語句: if CONDITION1; then 分支1 elif CONDINTION2; then 分支2 ... else 分支n fi
示例1:顯示一個菜單給用戶:bash
cpu) display cpu information mem) display mem information disk) display disks information quit) quit 要求:(1) 提示用戶給出本身的選擇; (2) 正確的選擇則給出相應的信息;不然,從新提示讓其用戶選擇正確的選項
那麼以上咱們使用的是if控制語句寫出的bash程序,接下來咱們瞭解一下case語句的使用,其語法格式爲:框架
case語句的語法格式: case $VARIABLE in PAT1) 分支1 ;; PAT2) 分支2 ;; ... *) 分支n ;; esac
每個分支都要使用雙分號結尾,這也是固定的語法格式,而這個雙分號能夠單獨成行,也能夠加載分支語句的後面,可是不能省略,由於省略的話,即使模式匹配,也會判斷下一個模式。不過雖然支持通配,但僅能支持glob
的通配符。ide
case支持glob風格的通配符: *:任意長度的任意字符; ?:任意單個字符; []:範圍內任意單個字符; a|b:a或b;
那麼如今咱們更改一下上面的示例,更改成case語句使得來回做爲比較。模塊化
#!/bin/bash # cat << EOF cpu) display cpu information. mem) display memory information. disk) display disk information. quit) quit ============================================== EOF read -p "Please input option: " option while [ "$option" != "cpu" -a "$option" != "mem" -a "$option" != "disk" -a "$option" != "quit" ]; do echo "Error! Please input information." read -p "Enter your option: " option done case $option in cpu) lscpu ;; mem) free -m ;; disk) fdisk -l /dev/[hs]d[a-z] ;; quit) echo "quit!" exit 0 ;; esac
與多分支的if語句相比簡單不少,case語句在不少的場景當中,可以替換多分支的if,寫爲最簡潔的格式,但只能用於一個變量對於多個值作模式匹配纔可使用。函數
示例:寫出一個服務框架腳本;測試
$lockfile, 值/var/lock/subsys/SCRIPT_NAME (1) 此腳本可接受start, stop, restart, status四個參數之一; (2) 若是參數非此四者,則提示幫助後退出; (3) start, 則建立lockfile,並顯示啓動,stop, 則刪除lockfile,並顯示中止;restart, 則先刪除文件在建立此文件,然後顯示重啓完成;status, 若是lockfile存在,則顯示running,不然,則顯示爲stopped。
以上就是case語句的示例,看上去比多分支的語句要簡便了許多,可是有些代碼仍是重複了使用,而重複使用的後果使得腳本會變得臃腫,因此咱們必須使用代碼某種手段可以將代碼重用,而函數就是將代碼可以實現重用及模塊和結構化的編程。ui
那麼函數對於過程式編程來講,實現的是代碼重用的一個功能組件,它能實現模塊和結構化編程,那麼對於函數來說,咱們能夠理解爲:spa
把一段獨立功能的代碼看成一個總體(用大括號圈起來),併爲之起一個名字;而命名的代碼片斷,此即爲函數;
咱們須要注意的是:
注意:定義函數的代碼段不會自動執行,在調用時才能執行;所謂調用函數,是指在執行的代碼段中給定函數名便可; 函數名出現的任何位置,在代碼執行時,都會被自動替換爲函數代碼;
因爲函數是一個獨立而又完整的功能,所以就會引進新的上下文,那麼函數就是將一段擁有獨立功能的代碼或命令使用{}
進行封裝,等待被執行調用,而函數名即爲就是調用該函數代碼時的名稱,將函數名引入到執行代碼時則會被調用,而在執行時則會自動替換爲函數代碼執行,因此它是可以完成模塊化與結構化編程的一個重要組件,而主要的實現就是代碼重用,將其一段代碼可調用n屢次。
那麼如何定義其函數語法,其共有兩種方式:
語法一: function f_name { ...函數體... } 語法二: f_name() { ...函數體... }
那麼再次強調的是,函數只能被調用,纔會執行,而調用的方式就是給定其函數名便可,函數名出現的地方則會自動替換其函數代碼。
函數是有其生命週期的,就是每一次被調用時纔會建立該函數,而執行完成返回給執行代碼的時候則會終止;
其實在咱們執行bash腳本時,都會有一個命令的狀態值或者爲返回值,而函數也有返回值,共有兩個,一種是使用echo
或者是print
f的結果返回,還有一種就是狀態返回值,其狀態返回的就是函數體中的最後一條命令運行的狀態結果。
固然,對咱們來說其實並不理想,因此咱們可能須要自定義狀態返回值。
函數的生命週期:每次被調用時建立,返回時終止; 其狀態返回值爲函數體中運行的最後一條命令的狀態結果; 自定義狀態返回值,須要使用:return 0: 成功; 1-255: 失敗;
不過須要注意的是,在函數中的任何位置但凡碰見return,則函數生命週期就執行結束,即便該函數代碼並非最後一個語句,就像腳本語句,一旦碰見exit
,則後續的腳本程序也沒法運行。
示例:給定一個用戶名;取得用戶的id號和默認的shell。
#!/bin/bash #
可是這段代碼仍是不夠靈活,由於腳本的交互式限制了多個用戶查詢,即便能夠容許多個用戶查詢,每次在read命令中還得須要添加變量,這是很是麻煩的,因此咱們儘量的避開,那麼另外一個示例代碼爲:
#!/bin/bash #
須要注意的是,在函數中$1變量有特殊的意義。
以上咱們瞭解到函數的做用及用法以後,咱們將以前的服務框架腳本使用函數的方式來進行重寫。
示例:使用函數來重寫服務框架腳本:
#!/bin/bash
#
# chkconfig: - 44 55
#
prog=$(basename $0)
lockfile=/var/lock/subsys/$prog
start (){
if [ -f $lockfile ]; then
echo "$prog started..."
else
touch $lockfile
[ $? -eq 0 ] && echo "$prog start..."
fi
}
stop(){
if [ -f $lockfile ]; then
rm -rf $lockfile
[ $? -eq 0 ] && echo "$prog stop..."
else
echo "$prog stop yet..."
fi
}
status() {
if [ -f $lockfile ]; then
echo "$prog is running..."
else
echo "$prog is stopped..."
fi
}
usage(){
echo "Usage: { start | stop | restart | status }"
exit 3
}
case $1 in
"start")
start
;;
"stop")
stop
;;
"restart")
stop
start
;;
"status")
status
;;
*)
usage
;;
esac
接下來講一下函數返回值的問題,函數的返回值共有兩種,一種是執行結果,另外一種是狀態結果,也就是結果狀態碼,那麼若是想要使用函數返回值給予調用者,在調用者內部則能夠經過某些功能用來取得,所謂函數出現並替換的地方,可以將其函數的代碼執行結果放在主程序中,那麼函數如何有執行結果共有如下幾種方式:
函數返回值: 函數執行結果返回值: (1) 使用echo或printf命令進行輸出; (2) 函數體中調用的命令的執行結果函數退出狀態碼;
還有就是函數的退出狀態碼,咱們以前說過:
函數的退出狀態碼: (1) 默認取決於函數體中執行的最後一條命令的退出狀態碼; (2) 自定義:return
在之後寫函數時,有必要顯示給出return
用來自定義其退出狀態碼。
在函數中,$1
和$2
有特殊意義,由於函數也能夠接受參數,並且傳遞參數給函數時,在函數內部中,$1
和$2
調用的是函數內部的參數,而不是腳本的參數。那麼如何給函數傳遞參數是在函數名後面給定函數列表便可,例如:testfun arg1 arg2 arg3 ...
。
咱們在函數體當中,若是使用的是$1
,就表示調用的是arg1
。一樣,使用的是$2
,則調用的是arg2
,以此類推。同時還能夠在函數中使用$*或$@引用全部參數,而$#引用傳遞參數的個數。
函數能夠接受參數: 傳遞參數給函數: 在函數體當中,可使用$1, $2, ...引用傳遞給函數的參數;還能夠在函數中使用$*或$@引用全部參數,$#引用傳遞參數的個數; 在調用函數時,在函數名後面以空白給定參數列表便可,例如:testfun arg1 arg2 ...
因此說函數是能夠很靈活的,在代碼段當中可以實現某一完整的功能,可是到底執行什麼樣的操做,施加在那個對象身上,可取決於傳遞函數的參數來實現。對於函數可以施加參數而言,咱們如下有個示例,以便於可以進行理解。
示例:添加10個用戶,添加用戶的功能使用函數實現,用戶名作爲參數傳遞給函數;
#!/bin/bash
#
# 5 user exists
#
adduser() {
if id $1 &> /dev/null; then
return 5
else
useradd $1
retval=$?
return $retval
fi
}
for i in {1..10}; do
adduser ${1}${i}
retval=$?
if [ $retval -eq 0 ]; then
echo "add user ${1}${i} finish."
elif [ $retval -eq 5 ]; then
echo "user ${1}${i} exists."
else
echo "Unknown Error."
fi
done
當$?
屢次調用時,最好將其$?
的數值保存下來。否則的話,第一個條件測試完成以後,返回的是當時條件的測試值。
練習:
一、使用函數實現ping主機時測試主機的在線狀態;主機地址經過參數傳遞給函數; 主程序:測試192.168.1.1-192.168.10.1範圍內各個主機的在線狀態; 二、打印NN乘法表;
咱們此前說過變量共有三種類型,分別爲環境變量、本地變量和局部變量,在這裏咱們說的是局部變量
,局部變量就是在函數內部所存在,其有效範圍就是函數內部,函數建立並調用時則局部變量建立便引用,當函數生命週期結束時,則局部變量也會其自動銷燬,須要注意的是,無需等待腳本運行結束,而是函數調用返回時,則變量就結束,而這種變量就叫作局部變量。
而定義局部變量的方法爲:local VARIABLE=VALUE
。
變量做用域: 局部變量:做用域是函數的生命週期;在函數結束時被自動銷燬; 定義局部變量的方法:local VARIABLE=VALUE
咱們回顧一下本地變量的做用域:
本地變量:做用域是運行腳本的shell進程的生命週期;所以,其做用範圍爲當前shell腳本程序文件;
舉個栗子:
#!/bin/bash
#
name=tom
setname() {
name=jerry
echo "Function: $name"
}
setname
echo "Shell: $name"
運行以後會發現:
# bash local.sh
Function: jerry
Shell: jerry
原本Shell應該是tom,結果爲jerry,由於咱們在函數中所調用的name,其實就是主程序的name,這表示當函數開始調用時,直接將主程序的變量的值改成了函數中變量的值,所以函數內部爲jerry,在函數外部依然是jerry,第一行name變量爲本地變量,而在函數中是能夠調用本地變量的。
若是兩者不互相受影響,在函數內部的name
變量以前加上local
。
#!/bin/bash
#
name=tom
setname() {
local name=jerry
echo "Function: $name"
}
setname
echo "Shell: $name"
加上以後其運行結果爲:
# bash local.sh Function: jerry Shell: tom
須要注意的是,若是主程序變量和函數變量相同且互不影響時,在函數里加local
對變量進行修飾。因此在寫代碼時,在函數中使用局部變量,否則會有一些故障須要手動排除,這是一個很麻煩的過程,有時候你也不知道那個bug
是出自在那裏。除非和主程序交互,交換值,不然在函數中必定要用local
方式來使用局部變量。
所謂遞歸就是本身不斷的調用本身,而函數遞歸就是函數直接或間接調用自身,那麼何時被用到,好比作階乘的時候,以及作斐波那契數列的時候等等,可能會用到,好比說10的階乘。
函數遞歸: 函數直接或間接調用自身; 10!=10*9!=10*9*8!=10*9*8*7!=...
那麼若是用函數來進行表示的話,如何實現其遞歸的功能,假設這個數字爲n,這個n可以返回n*(n-1)
,然後可以咱們可以讓其階乘爲n*(n-1)!=n*(n-1)*(n-2)!=
,然後再一次調用函數爲n=1
終止,則意味這一層一層的作出計算,稱之爲遞歸返回,可是遞歸太多也是有很大的問題,數值太大的話,須要大量的內存空間來保留其中間值。
還有一種是斐波那契數列,其特性爲第一個數爲1,第二個數也爲1,隨後每個值都是前兩個值的和。
1, 1, 2, 3, 5, 8, 13, 21, ...
如下分別爲兩個示例,階乘和斐波那契數列,在這裏咱們很少作闡述。
#!/bin/bash
#
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ]; then
echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact 9
#!/bin/bash#fab() { if [ $1 -eq 1 ]; then echo -n "1 " elif [ $1 -eq 2 ]; then echo -n "1 " else echo -n "$[$(fab $[$1-1])+$(fab $[$1-2])] " fi}for i in $(seq 1 $1); do fab "$i "doneecho