1. 程式人生 > 實用技巧 >shell中的函式

shell中的函式

#什麼是函式
	盛放某一功能的容器
#為什麼要用函式
	沒有引入函式前,遇到重複使用某一個功能的地方,只能複製黏貼實現該功能的程式碼,這會導致:
	1.減少程式碼冗餘,解決指令碼重複使用某一功能,結構不清晰,可讀性差
	2.可擴充套件性差,如果要修改功能,需要找到該指令碼內所有的該功能才能修改
#怎麼呼叫函式
	先定義,後呼叫
	把程式碼從磁碟取出來,放到記憶體(函式呼叫),在記憶體申請一個記憶體空間,把函式體程式碼放進去,通過函式名呼叫

#函式就是命令,指令碼也是命令
	命令可以傳參,指令碼可以傳參,函式可以傳參
	函式的執行相當於開了一個子shell,所以可以使用指令碼的位置引數
	使用$$可以看到,指令碼和函式屬於一個程序

一、函式定義的格式

function name() {
    statements
    [return value]
}

#省略空格
function name(){
    statements
    [return value]
}

#省略function
name() {
    statements
    [return value]
}

#省略小括號
function name {
    statements
    [return value]
}

二、函式的執行

一:不帶引數的執行
1.function和後面的小括號都不帶嗎,僅僅時'函式名'就可以執行
2.函式的定義必須在執行之前定義或載入(即先定義後執行)
3.函式執行時,會和指令碼公用變數,也可以為函式設定區域性變數及特殊位置引數 #函式變數的作用域
4.函式中的return和指令碼中的exit + break功能類似,與break的功能相同,return用來退出函式,exit是用來退出指令碼,break用來退出迴圈,continel用來退出本次迴圈
5.return的返回值('只能是0-255的整型')會給呼叫函式的程式,而exit的返回值給執行程式的shell #指令碼程序。$?可以檢視
	return的返回值使用$?檢視
	return的返回值可以是變數
	return後面的同級別程式碼將不會執行
	一個函式可以寫多個return,同級別值執行一個,不同級別配合if使用
	return沒有echo好用
6.如果函式獨立與指令碼之外,被指令碼載入使用時,需要使用source或者"."來提前載入使用
  例如:
        . /etc/init.d/functions  
        #載入系統函式中的命令或者引數變數,以供後面的程式使用
  	    #指令碼內定義函式的話,可以直接呼叫系統函式
7.local定義函式內部的區域性變數,變數在離開函式後會消失 #函式變數的作用域

二:帶引數的執行:
  函式名 引數1 引數2
1.shell的位置引數$1、$2、..$# 、$*、$?及$@都可以作為函式的引數使用,此時,父指令碼的引數臨時被隱藏,而$0仍然是父指令碼的名稱
2.當函式執行完成時,原來的命令列指令碼的引數恢復
3.'函式的引數變數'是在函式體裡面定義的,	#先新增後呼叫
#測試位置引數的隱藏,函式體中的位置引數表示傳入的引數
#!/bin/bash
echo $0
echo $1
echo $2
echo $3
function name(){
        echo $0
        echo $1
        echo $2
        echo $3
}
name

#檔案返回值測試
[root@hass-11 script]# /bin/true;echo $?
0
[root@hass-11 script]# /bin/false;echo $?
1

#action格式
[root@hass-11 script]# action "註釋" /bin/true
註釋                                                       [  OK  ]
[root@hass-11 script]# action "註釋" /bin/false
註釋                                                       [FAILED]

#return與exit,函式體沒有return,exit檢視的返回值預設是最後一串程式碼執行之後的返回值
#!/bin/sh
function name(){
        echo 111
        xxxxxx
        echo 333
}
name
echo $?

小插曲

1.使用cat命令追加多行,如'列印選項選單'
cat <<END
    1.FIRST
    2.SECOND
    3.THIRD 
END

2.給輸出的字型加顏色
echo -e 識別轉義字元,這裡識別字符的特殊含義,加顏色

 #顏色的開始
 RED_COLOR='\E[1;31m'
 GREEN_COLOR='\E[1;32m'
 YELLOW_COLOR='\E[1;33m'
 BLUE_COLOR='\E[1;34m'

 #顏色的結束
 RES='\E[0m'
  
 echo -e ${RED_COLOR} OLD ${RES}  #OLD是紅色
 echo -e ${GREEN_COLOR} OLD ${RES} #OLD是綠色
 echo -e ${YELLOW_COLOR} OLD ${RES} #OLD是黃色
 echo -e ${BLUE_COLOR} OLD ${RES} #OLD是藍色

定義一個函式,計算所有引數之和

#!/bin/bash
function usage(){
    if [ $# -lt 2 ];then
        echo "usage: $0 num1 num2 ..."
    fi
}

function zhengshu(){
    for i in $*
    do
	((i++))
	if [ $? -ne 0 ];then
	    usage
	fi
    done
}

function getsum(){
    local sum=0
    for n in $*
    do
         ((sum+=n))
    done
    return $sum
}

function main(){
    usage $*
    zhengshu $*
    getsum $*
    echo $?
}

main $*

作用域

#什麼是作用域
	變數的作用範圍
	也就是定義一個變數,在哪可以訪問到
#為什麼要使用作用域
	區分變數查詢的優先順序,避免衝突
#區域性作用域
	函式內部定義
	使用local關鍵字宣告
	只能在"該函式內"使用
	程序級別的,不同程序之間,區域性變數不通用
	
子函式不是子程序,是同一個程序,屬於一個函式
#!/bin/sh
unset x
x=000
function syy(){
        echo $x
}
function name(){
        local x=111
        syy
}
function syy2(){
        echo $x
}
echo $x
name
syy2

#全域性作用域
	在當前程序內宣告
	不使用關鍵字
	可以在"當前程序的任意位置"訪問到
	全域性變數是程序級別的

當前shell中執行
. ./test.sh
#!/bin/sh
unset x
x=222
function name(){
        local x=111
}
echo $x

#只要沒有被local宣告的變數,都是全域性變數
#shell的變數是程序級別的,python的變數是檔案級別的
	程序級別的變數'在不同的程序'是之間'不能訪問'的
	檔案級別的變數在不同的程序之間是可以訪問的
	shell的變數可以做成'偽檔案級別'的(使用 export 關鍵字定義,然後放到全域性環境變數檔案中)
	
#環境變數,檔案級別的
變數的程序隔離
[root@hass-11 ~]# unset x
[root@hass-11 ~]# x=1
[root@hass-11 ~]# echo $x
1
[root@hass-11 ~]# bash
[root@hass-11 ~]# echo $x

環境變數的"傳子不傳爹"
[root@hass-11 ~]# export x=2
[root@hass-11 ~]# exit
[root@hass-11 ~]# echo $x
1
傳子,使用export變數定義,臨時的,所有子程序中都可以使用
[root@hass-11 ~]# unset x
[root@hass-11 ~]# export x=2
[root@hass-11 ~]# bash
[root@hass-11 ~]# echo $x
2

#環境變數檔案
/etc/bashrc   		#推薦,永久生效
/etc/proifile
/etc/proifile.d/*	#推薦
~/.bashrc
~/.bash_profile

#登入式shell與非登入式shell
	登陸式:su - syy
	非登陸式:su syy		#不會載入所有的環境變數檔案,涉及到指令碼能否正常執行
	
#記憶體佔用與優化
	佔用
		每次定義變數都要使用記憶體空間
	優化
		減少bash的個數
		減少變數的定義

實戰題目一:

用shell指令碼檢查某網站是否存在異常

方式一(普通):
#!/bin/bash
 if [ $# -ne 1 ];then
        usage $"usage $0 url"
 fi
wget --spider -q -o /dev/null --tries=1 -T 5 $1 
#--spider用於測試,不下載,-q不在命令中顯示 -o 輸入到後面的檔案中 ,--tries=number為嘗試次數和-t一樣  -T超時時間和--timeout一樣 -S顯示響應頭
if [ $? -eq 0 ];then
        echo "$1,up"
else
        echo "$1,down"
fi
方式二(函式封裝):
#!/bin/bash
function Usage(){
        echo $"Usage:$0 url"
        exit 1
}
function check_url(){
        wget --spider -q -o /dev/null -t 1 -T 5 $1 
        if [ $? -eq 0 ];then
                echo "ok"
        else
                echo "error"
        fi 
}
function main(){
        if [ $# -ne 1 ];then
                Usage
        else
                check_url $1
        fi
}
main $*  #將指令碼傳入的引數全部都傳到主函式中

實戰題目二:

引數傳入指令碼、檢查某網站是否存在異常,以更專業的方式輸出

#!/bin/bash
. /etc/init.d/functions  #呼叫(載入)系統函式,因為下面要用action函式
function Usage(){
        echo $"usage:$0 url"
        exit 1
}
function check_url(){
        wget --spider -q -o /dev/null -t 1 -T 5 $1
        if [ $? -eq 0 ];then
                action "test $1" /bin/true
        else
                action "test $1" /bin/false
        fi
}
function main (){
        if [ $# -ne 1 ];then
                Usage
        else
                check_url $1
        fi
}
main $*

效果如下:
[root@mycentos shell]# sh 3.sh www.baidu.com   
test www.baidu.com                                         [  OK  ]
[root@mycentos shell]# sh 3.sh www.baidu.c
test www.baidu.c                                           [FAILED]

實戰題目三:

用shell開發模組化rsync服務啟動指令碼

#!/bin/bash
#chkconfig:2345 21 81
#description
#上面2行是將rsync加入開機自啟動服務

#呼叫系統函式
. /etc/init.d/functions

#輸入錯誤提示
function Usage(){
        echo "usage: $0 {start|stop|restart}"
        exit 1
}

#啟動服務
function Start(){
        rsync --daemon  #啟動服務
        sleep 2    #啟動服務2秒後再做判斷
        if [ $(netstat -pantu | grep rsync | wc -l) -ne 0 ];then
                action "rsyncd is started" /bin/true
        else
        		
                action "rsyncd is started" /bin/false
        fi
}
#停止服務
function Stop(){
        killall rsync &>/dev/null
        sleep 2
        if [ $(netstat -apntu| grep rsync | wc -l) -eq 0 ];then
                action "rsyncd is stopped" /bin/true
        else
                action "rsyncd is stopped" /bin/false
        fi
}

case "$1" in
 "start")
        Start
        ;;
 "stop")
        Stop
        ;;
 "restart")
        Stop
        sleep 1
        Start
        ;;
  *)
        Usage
esac

結果如下:
[root@mycentos init.d]# /etc/init.d/rsyncd start
rsyncd is started                                          [  OK  ]
[root@hass-11 script]# yum install -y lsof
[root@mycentos init.d]# lsof -i:873
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
rsync   1478 root    4u  IPv4  13067      0t0  TCP *:rsync (LISTEN)
rsync   1478 root    5u  IPv6  13068      0t0  TCP *:rsync (LISTEN)
[root@mycentos init.d]# /etc/init.d/rsyncd stop
rsyncd is stopped                                          [  OK  ]
[root@mycentos init.d]# lsof -i:873
[root@mycentos init.d]# /etc/init.d/rsyncd restart
rsyncd is stopped                                          [  OK  ]
rsyncd is started                                          [  OK  ]
[root@mycentos init.d]# lsof -i:873               
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
rsync   2379 root    4u  IPv4  19734      0t0  TCP *:rsync (LISTEN)
rsync   2379 root    5u  IPv6  19735      0t0  TCP *:rsync (LISTEN)

注:
1.在安裝或者啟動時,如果遇到找不到“/etc/rsync.conf”檔案時,要用touch建立一個,實際工作中要寫入東西,此處為了方便就不寫,為了啟動rsync服務即可
2.rsync具體的脫離xinetd啟動的方式見"shell基礎二"
3.放在/etc/init.d目錄下就可以用service命令啟動,啟動前需要是指令碼有執行許可權,否則無法執行,也無法自動補全。
4.要加入開機自啟動,則需要加入指令碼中直譯器下面2行,然後“chkconfig --add rsyncd”加入自啟動 "rsyncd"是/etc/init.d目錄下的服務名稱

實戰四:

執行shell指令碼,列印如下選單,柑橘選擇,給選擇的水果加一種顏色。

1.紅色的蘋果
2.綠色的蘋果
3.黃色的蘋果
4.藍色的蘋果

方法一(普通版)
#!/bin/sh

#定義好顏色,方便後面使用
RED_COLOR='\E[1;31m'
GREEN_COLOR='\E[1;32m'
YELLOW_COLOR='\E[1;33m'
BLUE_COLOR='\E[1;34m'
RES='\E[0m'

cat <<END
====================
        1.紅色的蘋果
        2.綠色的蘋果
        3.黃色的蘋果
        4.藍色的蘋果
====================
END

read -p "input a munber you want:" NUM
case "$NUM" in
  1)
        echo -e ${RED_COLOR}apple${RES}
        ;;
  2)
        echo -e ${GREEN_COLOR}apple${RES}
        ;;
  3)
        echo -e ${YELLOW_COLOR}apple${RES}
        ;;
  4)
        echo -e ${BLUE_COLOR}apple${RES}
        ;;
  *)
        echo "usage:input {1|2|3|4}"
        exit 1
esac
方法二(函式封裝):
#!/bin/sh

#定義好顏色,方便後面使用
RED_COLOR='\E[1;31m'
GREEN_COLOR='\E[1;32m'
YELLOW_COLOR='\E[1;33m'
BLUE_COLOR='\E[1;34m'

RES='\E[0m'

function Menu(){
cat <<END
====================
        1.紅色的蘋果
        2.綠色的蘋果
        3.黃色的蘋果
        4.藍色的蘋果
====================
END
}
function Usage(){

        echo "$usage:input a number{1|2|3|4}"
        exit 1
}
function Choose(){
read -p "input a munber you want:" NUM
case "$NUM" in
  1)
        echo -e ${RED_COLOR}apple${RES}
        ;;
  2)
        echo -e ${GREEN_COLOR}apple${RES}
        ;;
  3)
        echo -e ${YELLOW_COLOR}apple${RES}
        ;;
  4)
        echo -e ${BLUE_COLOR}apple${RES}
        ;;
  *)
        Usage
esac
}
function Main(){
        Menu
        Choose
}
Main          

#函式體裡面可以再次呼叫函式

效果如圖:

實戰五:緊接著上題,請開發一個給指定內容加上指定顏色的指令碼

#!/bin/bash
RED_COLOR='\E[1;31m'
GREEN_COLOR='\E[1;32m'
YELLOW_COLOR='\E[1;33m'
BLUE_COLOR='\E[1;34m'
RES='\E[0m'

function Usage(){
        echo $"usage:$0 txt {red|green|yellow|pink}"
        exit 1
}

if [ $# -ne 2 ];then
           Usage
fi 

function Choose(){
        case "$2" in
                "red")
                        echo -e ${RED_COLOR}$1${RES}
                        ;;
                "green")
                        echo -e ${GREEN_COLOR}$1${RES}
                        ;;
                "yellow")
                        echo -e ${YELLOW_COLOR}$1${RES}
                        ;;
                "blue")
                        echo -e ${BLUE_COLOR}$1${RES}
                        ;;
                *)
                	Usage
        esac
}
function Main(){
        Choose $1 $2
}
Main $*
注:
1.將引數二的顏色付給引數一
2.精確匹配'單詞'的三種方式
  1.grep -w 'oldboy' file 
  2.grep "\boldboy\b" file
  3.grep "^oldboy$" file
以上都是將出現oldboy單詞的行顯示出來,而不是將包含oldboy的行顯示出來

實戰五:

啟動Nginx服務的命令:/application/nginx/sbin/nginx
關閉Nginx服務的命令:/application/nginx/sbin/nginx -s stop

請開發指令碼,以實現Nginx服務啟動和關閉功能,具體指令碼命令為/etc/init.d/nginxd {start|stop|restart},並通過chkconfig進行開機自啟動

思路:
1.判斷開啟或關閉服務(一是檢測pid檔案是否存在,存在就是開啟,不存在就是服務已經關閉),或者使用netstat連結數也可以
2.start和stop分別構成函式
3.對函式和命令執行的返回值進行處理
4.chkconfig實現服務自啟動
程式碼:
#!/bin/sh
#chkconfig:2345 27 83
#description

source /etc/init.d/functions  #載入系統函式庫

#定義檔案路徑
PATH="/application/nginx/sbin"
PID_PATH="/application/nginx/logs/nginx.pid"
REVEAL=0
function Usage(){
        echo $"usage:$0 {start|stop|restart}"
        exit 1
}
#判斷引數的個數
if [ $# -ne 1 ];then
        Usage
fi
#開始函式
function Start(){
        if [ ! -f $PID_PATH ];then  #若原來服務是關閉的
                $PATH/nginx  #開啟服務
                REVEAL=$?  

                if [ $REVEAL -eq 0 ];then  #判斷是否開啟
                        action "nginx is started" /bin/true

                else
                        action "nginx is started" /bin/false
                fi

        else
                echo "nginx is running"

        fi
        return $REVEAL
}
function Stop(){#結束函式
        if [ -f $PID_PATH ];then  #若原來服務是啟動的
                $PATH/nginx -s stop  #關閉服務
                REVEAL=$?

                if [ $REVEAL -eq 0 ];then  #判斷是否關閉
                        action "nginx is stopped" /bin/true
                else
                        action "nginx is stopped" /bin/false
                fi
                return $REVEAL
        else
                echo "nginx is no running"
        fi
        return $REVEAL
}

        case "$1" in
           "start")
                 Start
                REVEAL=$?
                ;;
           "stop")
                Stop
                REVEAL=$?
                ;;
           "restart")
                Stop
                Start
                REVEAL=$?
                ;;
                *)
                Usage
        esac

 exit $REVEAL

效果如下:

[root@mycentos init.d]# /etc/init.d/nginxd start
nginx is running
[root@mycentos init.d]# /etc/init.d/nginxd stop
nginx is stopped                                           [  OK  ]
[root@mycentos init.d]# /etc/init.d/nginxd start
nginx is started                                           [  OK  ]
[root@mycentos init.d]# lsof -i:80
COMMAND  PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   2928  root    6u  IPv4  23795      0t0  TCP *:http (LISTEN)
nginx   2929 nginx    6u  IPv4  23795      0t0  TCP *:http (LISTEN)
[root@mycentos init.d]# /etc/init.d/nginxd restart
nginx is stopped                                           [  OK  ]
nginx is started                                           [  OK  ]
[root@mycentos init.d]# lsof -i:80                
COMMAND  PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   2940  root    6u  IPv4  24824      0t0  TCP *:http (LISTEN)
nginx   2941 nginx    6u  IPv4  24824      0t0  TCP *:http (LISTEN)

彩蛋一枚

return :
    1.用來退出函式,後面的函式裡面的內容不再執行
exit:
     2.用來退出指令碼,後面的內容不再執行

function test(){
  return 1
}  
test    #函式執行完成後,$?會得到test中return後面的返回值
ls -l ./  #這條命令執行成功,$?變成0
    
function test2(){
  exit 99
} 
test2 #函式執行完成後,$?變成99,也就是exit後面的數值
$?的值會不斷的改變,有別於其他語言的函式呼叫的返回值。

結果:

#!/bin/bash
function test(){
        return 8
}
echo $?  #0
test
echo $? #8

function test2(){
        return 9
}
echo $? #0
test2
echo $? #9

ls -l ./
echo $? #0
exit 10

此shell執行結束後,echo $? 結果是10