官方資料:Shell Functions (Bash Reference Manual)
簡介
正如我們在《Bash腳本編程學習筆記06:條件結構體》中最后所說的,我們應該把一些可能反復執行的代碼塊整合起來,避免反復撰寫使得代碼過于臃腫,
函式正是為了解決這個問題而存在的,函式在定義時,可以將常用的代碼整合為一個整體,當我們需要執行的時候,只需要呼叫這個函式即可,
Bash是程序式編程語言,從上至下順序執行代碼,因此函式定義必須在函式呼叫之前完成,
函式屬于shell的基礎特性,即不僅僅是針對于bash,包括csh、sh、ksh和zsh等都具有這里說明的函式特性,
函式會在當前的shell環境下執行,不會創建新的行程(子shell)來解釋函式,
函式可以通過“unset -f”命令洗掉,
函式的定義和呼叫
函式的定義有兩種方式,
方式一,
FUNC_NAME () {
BODY
}
方式二,
function FUNC_NAME [()] { BODY }
[()]:表示小括號可以省略,
函式的呼叫只需要鍵入函式名即可,就像鍵入命令名一樣,
[root@c7-server ~]# cat function.sh #!/bin/bash
# 函式定義 test_func() { echo "This is just for test." }
# 函式呼叫 test_func [root@c7-server ~]# bash function.sh This is just for test.
示例:撰寫一個腳本,接收一個用戶名引數,輸出用戶名、UID和默認shell,(要求以函式的形式)
#!/bin/bash userinfo() { if ! id $username &> /dev/null; then echo "The user $username is not exists." else grep "^$username\>" /etc/passwd | cut -d : -f 1,3,7 fi } username=$1 userinfo
示例:將《Bash腳本編程學習筆記06:條件結構體》中最后的服務腳本中冗余的代碼改寫為函式,即函式式編程,
#!/bin/bash # # chkconfig: - 50 50 # Description: test service script # prog=$(basename $0) lockfile="/var/lock/subsys/$prog" start() { if [ -e $lockfile ]; then echo "The service $prog has already started." else touch $lockfile echo "The service $prog starts finished." fi } stop() { if [ ! -e $lockfile ]; then echo "The service $prog has already stopped." else rm -f $lockfile echo "The service $prog stops finished." fi } restart() { if [ -e $lockfile ]; then rm -f $lockfile touch $lockfile echo "The service $prog restart finished." else touch $lockfile echo "The service $prog starts finished." fi } status() { if [ -e $lockfile ]; then echo "The service $prog is running." else echo "The service $prog is not running." fi } case $1 in start) start ;; stop) stop ;; restart) restart ;; status) status ;; *) echo "Usage: $prog {start|stop|restart|status}" exit 1 ;; esac
函式的輸出和退出狀態碼
函式的輸出指的是函式體中執行的命令的輸出,STDOUT和STDERR都會輸出,其中也包括了我們使用echo或者printf的顯式的STDOUT,
如果函式中沒有return陳述句的話,那么函式的退出狀態碼是函式體中最后一條命令的退出狀態碼,
如果函式中有return陳述句的話,那么當函式體自上而下執行到return時,函式會立即停止并回傳,函式的退出狀態碼就是return指定的退出狀態碼,
return [n]
如果return沒有指定退出狀態碼的話,那么就是return的上一條命令的退出狀態碼,
注意不要和exit陳述句混淆,return用戶終止函式的執行并回傳,而exit是終止了整個腳本的執行并退出,后者的作用域更廣,
函式的位置引數
函式和腳本一樣,都可以接收引數作為位置引數,然后在函式中參考這些引數,也支持參考與位置引數有關的特殊變數($#, $*, $@),
向函式傳遞位置引數和向腳本傳遞位置引數的方式是一樣的,
my_func() { echo "$1 $2" } my_func arg1 arg2
記住,傳參時,不要寫成類似C語言的風格,會報錯的,
my_func(arg1, arg2)
練習
1、撰寫一個腳本,批量添加用戶,用戶傳參給腳本,引數是欲添加的用戶名前綴,用戶名其余部分使用數字補全,
#!/bin/bash userAdd() { if id $1 &> /dev/null; then return 1 else useradd $1 return 0 fi } for i in {01..03}; do username="$1$i" userAdd $username retval=$? if [ $retval -eq 0 ]; then echo "The user $username has been added." elif [ $retval -eq 1 ]; then echo "The user $username was existed." fi done
這里有一點需要注意,在for回圈體中,一定要在函式呼叫后立即將函式的退出狀態碼(return)獲取并存入變數中(如該示例中的retval),而后在對該狀態碼做判斷,
如果直接多次判斷$?的值的話,那么第一次的$?的值是函式的退出狀態碼,到了第二次,就會變成了上一句echo陳述句了,
2、撰寫一個腳本,通過函式檢測(ping)某一主機的在線狀態,需檢測192.168.152.1~192.168.152.254這個范圍,
#!/bin/bash ping_test() { ip=$1 if ping -c 1 -q $ip &> /dev/null; then echo "The host $ip is online." return 0 else return 1 fi } for i in 192.168.152.{1..254}; do ping_test $i done
Linux中的ping命令,默認是發送無限的請求資料包持續ping的,需要使用-c指定包的數量,這個腳本不太好,因為ping遇到不通的情況會等待一段時間,導致腳本比較耗時,
3、撰寫一個腳本,通過函式實作乘法口訣表,注意,不是九九乘法口訣表,而是NN乘法口訣表,N作為用戶引數傳入腳本,
#!/bin/bash multi() { N=$1 for ((i=1;i<=N;i++)); do for ((j=1;j<=i;j++)); do echo -ne "$j*$i=$((i*j))\t" done echo done } multi $1
注意:這個腳本有瑕疵,在輸出時,當N數值過大的時候,使用“\t”制表符會使顯示較不人性化,

函式中的變數作用域
在《Bash腳本編程學習筆記01:變數與多命令執行》中我們說變數有三種型別并說明了其作用域,
- 本地變數:僅當前shell有效(即當前bash行程),
- 環境變數:當前shell及其子shell(即當前bash行程即其子bash行程),
- 區域變數:在某部分代碼片段中有效(例如函式),
結合作用域的概念,我們來看下面這個示例,
#!/bin/bash name=tom set_name() { name=jerry echo $name } set_name echo $name
我們應該會認為這樣吧?
set_name --輸出--> jerry echo $name --輸出--> tom
其實,真實的結果是:
[root@c7-server ~]# bash func_scope.sh jerry jerry
造成這種結果的原因是,區域變數是需要手動定義的,而不是其出現在函式體中就是區域變數了,
可以通過內置命令local來定義區域變數,local僅可以在函式內部使用!
local [option] name[=value] …
因此我們對腳本進行改寫,函式體中的變數明確使用local命令定義,就可以驗證我們此前說的變數作用域的理論了,
[root@c7-server ~]# cat func_scope.sh #!/bin/bash name=tom set_name() { local name=jerry echo $name } set_name echo $name [root@c7-server ~]# bash func_scope.sh jerry tom
函式中的變數作用域,是一個難點,我其實沒搞太明白,上面的示例其實是一個非常簡單的示例,在復雜的情況下,例如函式A呼叫函式B時,local會變得很有幫助,具體遇到的時候,建議大家再翻閱一下官方的手冊,
函式的遞回
一個函式可以呼叫另一個函式,而當一個函式呼叫自身時,就叫做函式的遞回,
遞回不會無限遞回,一般會有一個邊界,在邊界處會回傳,而后根據遞回的順序逐一按照相反的順序回傳,
函式的遞回很好地解決了計算階乘(factorial)和斐波那契(fibonacci)數列,
階乘
階乘是由基斯頓·卡曼發明的數學運算,一個正整數的階乘是所有小于等于該數的正整數的積,記作“n!”,0和1的階乘都是1,
n!=1*2*3*4*...*n
階乘存在一個規律,
5!=1*2*3*4*5 4!=1*2*3*4 3!=1*2*3 2!=1*2 1!=1
因此,
5!=4!*5 4!=3!*5 3!=2!*3 ... n!=(n-1)!*n=n*(n-1)!
我們就可以把階乘定義為一個函式并通過遞回的方式來實作階乘的計算,
#!/bin/bash fact() { if [ $1 -eq 1 -o $1 -eq 0 ]; then echo 1 else echo $(($1*$(fact $(($1-1))))) fi } fact $1
斐波那契數列
斐波那契數列是數學家萊昂納多·斐波那契以兔子繁殖為例引入的,又作兔子數列,
該數列F,第一項和第二項都為1,從第二項開始,每一項的值都是前兩項之和,
數列F F(1)=1 F(2)=1 F(3)=F(1)+F(2) ... F(n)=F(n-2)+F(n-1)
1 1 2 3 5 8 13 21 34 ...
因此我們可以把求斐波那契數列的第N項的值寫為一個函式,
[root@c7-server ~]# cat func_factorial.sh #!/bin/bash fact() { if [ $1 -eq 1 -o $1 -eq 0 ]; then echo 1 else echo $(($1*$(fact $(($1-1))))) fi } fact $1 [root@c7-server ~]# vim func_fibo.sh [root@c7-server ~]# bash func_fibo.sh 1 1 [root@c7-server ~]# bash func_fibo.sh 2 1 [root@c7-server ~]# bash func_fibo.sh 3 2 [root@c7-server ~]# bash func_fibo.sh 4 3 [root@c7-server ~]# bash func_fibo.sh 5 5 [root@c7-server ~]# bash func_fibo.sh 6 8 [root@c7-server ~]# bash func_fibo.sh 7 13 [root@c7-server ~]# bash func_fibo.sh 8 21
不過這種方式僅僅是求得第N項的值,我們可以改為列印這個斐波那契數列,
[root@c7-server ~]# cat func_fibo.sh #!/bin/bash fibo() { if [ $1 -eq 1 -o $1 -eq 2 ]; then echo -n "1 " else echo -n "$(($(fibo $(($1-2)))+$(fibo $(($1-1))))) " fi } for i in $(seq $1); do fibo $i done echo [root@c7-server ~]# bash func_fibo.sh 10 1 1 2 3 5 8 13 21 34 55
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/141374.html
標籤:Linux
下一篇:Bringing up interface eth0: Device eth0 does not seem to be presen
