Shell 函數(shù)
1. Shell 函數(shù)概述
1.1 Shell 函數(shù)簡(jiǎn)介
Shell 和其他語言一樣,也有函數(shù),其本質(zhì)就是一段可以復(fù)用的代碼。將數(shù)據(jù)進(jìn)行抽離處理,傳遞不同的值輸出不同的結(jié)果,在指定的地方進(jìn)行調(diào)用即可。
1.2 為什么要用函數(shù)
如果我們要重復(fù)執(zhí)行一批相同的操作,不想重復(fù)去寫,可以將這一系列操作抽象為一個(gè)函數(shù),后期可以利用變量傳值調(diào)用該函數(shù),從而大大減少重復(fù)性勞動(dòng),提升效率減少代碼量,使得 Shell 腳本更加的靈活和通用。
2. Shell 函數(shù)操作
2.1 函數(shù)語法
2.1.1 標(biāo)準(zhǔn)語法
function fnname() {
statements
return value
}
對(duì)各個(gè)部分的說明:
function
是 Shell 中的關(guān)鍵字,專門用來定義函數(shù);fname
是函數(shù)名;statements
是函數(shù)要執(zhí)行的代碼,也就是一組語句;return value
表示函數(shù)的返回值,其中 return 是 Shell 關(guān)鍵字,專門用在函數(shù)中返回一個(gè)值,這一部分可以寫也可以不寫。
由大括號(hào)包圍的部分稱為函數(shù)體,調(diào)用一個(gè)函數(shù),實(shí)際上就是執(zhí)行函數(shù)體中的代碼。
例如:
function checkuser() {
echo "當(dāng)前用戶為:$USER"
return 0
}
如上就定義了一個(gè) checkuser
函數(shù),其輸出當(dāng)前登錄系統(tǒng)的用戶名,返回值為 0。
2.1.2 簡(jiǎn)化語法
- 函數(shù)名后無括號(hào)
#簡(jiǎn)化寫法1
function fname{
statements
return n
}
- 函數(shù)不寫 function
#簡(jiǎn)化寫法2:
fname() {
statements
return n
}
上述兩種定義函數(shù)的方法都可以,但是還是建議在編寫 Shell 的時(shí)候,也不要浪費(fèi)這點(diǎn)時(shí)間,建議大家都用完整函數(shù)定義,寫 function 關(guān)鍵字,在為函數(shù)定義帶上 (),這樣更加規(guī)范,而且便于別人閱讀修改你的腳本。
Tips:在函數(shù)定義中需要函數(shù)名稱后可以有多個(gè)空格,括號(hào)內(nèi)也可以有多個(gè)空格,如果函數(shù)體寫在一行,需要在語句末尾加上
;
。
2.2 函數(shù)調(diào)用
當(dāng)函數(shù)定義好了,在需要使用函數(shù)的地方,調(diào)用其函數(shù)名稱即可,注意函數(shù)調(diào)用需要在函數(shù)定義之后。
2.2.1 無參數(shù)調(diào)用
當(dāng)函數(shù)沒有參數(shù)的時(shí)候,調(diào)用非常簡(jiǎn)單,直接寫函數(shù)名稱即可,調(diào)用函數(shù)就是在特定地方執(zhí)行函數(shù)體內(nèi)的操作。
// 定義函數(shù)
function fnname() {
statements
return value
}
// 調(diào)用函數(shù)
fname
2.2.2 傳遞參數(shù)調(diào)用
- 語法
我們之前在變量一章節(jié)介紹了 Shell 腳本的參數(shù),知道了參數(shù)的重要性質(zhì)及其各種類特征,與 Shell 腳本傳遞參數(shù)一樣,函數(shù)也可以傳遞參數(shù),例如:
// 定義函數(shù)
function fnname() {
statements
return value
}
// 調(diào)用函數(shù)
fname param1 param2 param3
如上所示,在調(diào)用函數(shù) fname
的時(shí)候,我們傳遞了三個(gè)參數(shù),參數(shù)之間利用空格分割,和 Shell 腳本傳遞參數(shù)一樣,但需要注意 Shell 腳本中,函數(shù)在定義時(shí)候不能指定參數(shù),在調(diào)用的時(shí)候傳遞參數(shù)即可,并且在調(diào)用函數(shù)時(shí)傳遞什么參數(shù)函數(shù)就接受什么參數(shù)。
2.3 函數(shù)參數(shù)
上述我們了解了函數(shù)的定義,在其中無參函數(shù)調(diào)用即調(diào)用函數(shù)名即可,對(duì)于有參函數(shù),需要傳遞一定的參數(shù)來執(zhí)行對(duì)應(yīng)的操作,函數(shù)的參數(shù)和腳本的參數(shù)類型及用法一致,在此我們簡(jiǎn)單回顧下,看參數(shù)在函數(shù)中都有哪些分類,及該如何使用。
2.3.1 位置參數(shù)
位置參數(shù)顧名思義,就是傳遞給函數(shù)參數(shù)的位置,例如給一個(gè)函數(shù)傳遞一個(gè)參數(shù),我們可以在執(zhí)行 Shell 腳本獲取對(duì)應(yīng)位置的參數(shù),獲取參數(shù)的格式為:$n。n 代表一個(gè)數(shù)字,在此需要注意與腳本傳遞參數(shù)不一樣,$0
為依舊為腳本的名稱,在函數(shù)參數(shù)傳遞中,例如傳遞給函數(shù)的第一個(gè)參數(shù)獲取就為 $1
,第 2 個(gè)參數(shù)就為 $2
, 以此類推……,需要其 $0
為該函數(shù)的名稱。
例如:
[root@master func]# cat f1.sh
#!/bin/bash
function f1() {
echo "函數(shù)的第一個(gè)參數(shù)為: ${1}"
echo "函數(shù)的第二個(gè)參數(shù)為: ${2}"
echo "函數(shù)的第三個(gè)參數(shù)為: ${3}"
}
# 調(diào)用函數(shù)
f1 shell linux python go
[root@master func]# bash f1.sh
函數(shù)的第一個(gè)參數(shù)為: shell
函數(shù)的第二個(gè)參數(shù)為: linux
函數(shù)的第三個(gè)參數(shù)為: python
我們可以看到傳遞給 f1
函數(shù)共 4 個(gè)位置參數(shù),在結(jié)果輸出中可以看到由于函數(shù)體內(nèi)部只對(duì)三個(gè)參數(shù)進(jìn)行了處理,后續(xù)的參數(shù)也就不再處理了。
2.3.2 特殊參數(shù)
在 Shell 中也存在特殊含義的參數(shù)如下表:
變量 | 含義 |
---|---|
$# | 傳遞給函數(shù)的參數(shù)個(gè)數(shù)總和 |
$* | 傳遞給腳本或函數(shù)的所有參數(shù),當(dāng)被雙引號(hào) " " 包含時(shí),所有的位置參數(shù)被看做一個(gè)字符串 |
$@ | 傳遞給腳本或函數(shù)的所有參數(shù),當(dāng)被雙引號(hào) " " 包含時(shí),每個(gè)位置參數(shù)被看做獨(dú)立的字符串 |
$? | $? 表示函數(shù)的退出狀態(tài),返回為 0 為執(zhí)行成功,非 0 則為執(zhí)行失敗 |
示例:
[root@master func]# cat f1.sh
#!/bin/bash
function fsum() {
echo "函數(shù)第一個(gè)參數(shù)為: ${1}"
echo "函數(shù)第二個(gè)參數(shù)為: ${2}"
echo "函數(shù)第三個(gè)參數(shù)為: ${3}"
echo "函數(shù)的參數(shù)總數(shù)為: ${#}"
echo "函數(shù)的參數(shù)總數(shù)為: ${@}"
local sum=0
for num in ${@};
do
let sum=${sum}+${num}
done
echo "計(jì)算的總和為: ${sum}"
return 0
}
# 調(diào)用函數(shù)
fsum 10 20 1 2
echo $?
[root@master func]# bash f1.sh
函數(shù)第一個(gè)參數(shù)為: 10
函數(shù)第二個(gè)參數(shù)為: 20
函數(shù)第三個(gè)參數(shù)為: 1
函數(shù)的參數(shù)總數(shù)為: 4
函數(shù)的參數(shù)總數(shù)為: 10 20 1 2
計(jì)算的總和為: 33
0
如上可以看到特殊參數(shù)與 Shell 腳本傳遞參數(shù)一樣。
Tips:局部變量需要特別聲明在函數(shù)內(nèi)部利用
local
關(guān)鍵字來聲明。
2.4 函數(shù)返回值
函數(shù)返回值利用 $?
來接收,在上述示例中我們將計(jì)算的結(jié)果利用 echo 命令打印出來,如果我們?cè)诤罄m(xù)的腳本中需要利用此函數(shù)計(jì)算的結(jié)果,就需要得到這個(gè)返回值,此刻就需要將計(jì)算的結(jié)果不僅僅是打印而是返回了,函數(shù)中返回利用 return
關(guān)鍵字,在函數(shù)調(diào)用完成后,我們利用 $?
來接受函數(shù)的返回值,例如將我們上面的示例改造成返回結(jié)構(gòu)的函數(shù)。
注意:shell 函數(shù)的返回值,只能是整形,并且在 0-257 之間,不能是字符串或其他形式。并且在調(diào)用方法和取得返回值之間,不能有任何操作,不然取不到 return 的值。
[root@master func]# cat f1.sh
#!/bin/bash
function fsum() {
echo "函數(shù)第一個(gè)參數(shù)為: ${1}"
echo "函數(shù)第二個(gè)參數(shù)為: ${2}"
echo "函數(shù)第三個(gè)參數(shù)為: ${3}"
echo "函數(shù)的參數(shù)總數(shù)為: ${#}"
echo "函數(shù)的參數(shù)總數(shù)為: ${@}"
local sum=0
for num in ${@};
do
let sum=${sum}+${num}
done
return $sum
}
fsum 10 20 1 2
echo $?
[root@master func]# bash f1.sh
函數(shù)第一個(gè)參數(shù)為: 10
函數(shù)第二個(gè)參數(shù)為: 20
函數(shù)第三個(gè)參數(shù)為: 1
函數(shù)的參數(shù)總數(shù)為: 4
函數(shù)的參數(shù)總數(shù)為: 10 20 1 2
33
可以看到我們將在函數(shù)內(nèi)部計(jì)算的數(shù)組之和,利用 return 作為返回,此刻在函數(shù)調(diào)用的時(shí)候,利用 $?
就可以拿到函數(shù)返回的值進(jìn)一步處理。
2.5 遞歸函數(shù)
Shell 支持遞歸函數(shù),遞歸函數(shù)也就是自己調(diào)用自己,即在函數(shù)體內(nèi)部又一次調(diào)用函數(shù)自己,例如:
[root@master func]# cat recursion.sh
#!/bin/bash
function myecho() {
echo "$(date)"
sleep 1
myecho inner
}
myecho
[root@master func]# bash recursion.sh
Sat Mar 28 13:14:38 CST 2020
Sat Mar 28 13:14:39 CST 2020
Sat Mar 28 13:14:40 CST 2020
Sat Mar 28 13:14:41 CST 2020
Sat Mar 28 13:14:42 CST 2020
...
如上就是一個(gè)遞歸函數(shù),在函數(shù)體內(nèi)部又調(diào)用了函數(shù) myecho
,在執(zhí)行的時(shí)候就會(huì)陷入無限循環(huán)。
3. 實(shí)例
3.1 需求
系統(tǒng)經(jīng)常在執(zhí)行定時(shí)腳本期間會(huì)將 Linux 系統(tǒng) CPU 利用率跑滿,導(dǎo)致其他服務(wù)受到影響,故查閱資料發(fā)現(xiàn)有大神寫的 CPU 利用率限制程序。
地址:CPU Usage Limiter for Linux
利用此工具可以,配合定時(shí)任務(wù)放置在服務(wù)器上,達(dá)到限制程序 CPU 情況,可根據(jù)自己系統(tǒng) CPU 核心數(shù)進(jìn)行參數(shù)配置,會(huì)記錄 CPU 超過閥值的日志,可供后期進(jìn)行查看分析。
3.2 思路
利用函數(shù)編寫安裝 cpulimit 工具的函數(shù),如果系統(tǒng)不存在該命令則安裝并執(zhí)行 CPU 限制,如果存在則執(zhí)行 cpulimit 函數(shù)對(duì)超過指定 CPU 的進(jìn)程進(jìn)行限制,最后執(zhí)行總體 main 函數(shù)。
3.3 實(shí)現(xiàn)
- 實(shí)現(xiàn)腳本
#!/bin/bash
# Description: count file scripts
# Auth: kaliarch
# Email: kaliarch@163.com
# function: count file
# Date: 2020-03-29 14:00
# Version: 1.0
[ $(id -u) -gt 0 ] && exit 1
# cpu使用超過百分之多少進(jìn)行限制
PEC_CPU=80
# 限制進(jìn)程使用百分之多少,如果程序?yàn)槎嗑€程,單個(gè)cpu限制為85,如果為多核心,就需要按照比例寫,例如cpu為2c,像限制多線程占比80%,就寫170
LIMIT_CPU=85
# 日志
LOG_DIR=/var/log/cpulimit/
# 超過閥值進(jìn)程pid
PIDARG=$(ps -aux |awk -v CPU=${PEC_CPU} '{if($3 > CPU) print $2}')
# 安裝cpulimit 函數(shù)
install_cpulimit() {
[ ! -d /tmp ] && mkdir /tmp || cd /tmp
wget -c https://github.com/opsengine/cpulimit/archive/v0.2.tar.gz
tar -zxf v0.2.tar.gz
cd cpulimit-0.2 && make
[ $? -eq 0 ] && cp src/cpulimit /usr/bin/
}
# 執(zhí)行cpulimit
do_cpulimit() {
[ ! -d ${LOG_DIR} ] && mkdir -p ${LOG_DIR}
for i in ${PIDARG};
do
CPULIMITCMD=$(which cpulimit)
MSG=$(ps -aux |awk -v pid=$i '{if($2 == pid) print $0}')
echo ${MSG}
[ ! -d /tmp ] && mkdir /tmp || cd /tmp
nohup ${CPULIMITCMD} -p $i -l ${LIMIT_CPU} &
echo "$(date) -- ${MSG}" >> ${LOG_DIR}$(date +%F).log
done
}
# 主函數(shù)
main() {
hash cpulimit
if [ $? -eq 0 ];then
# 調(diào)用函數(shù)
do_cpulimit
else
install_cpulimit && do_cpulimit
fi
}
main
- 測(cè)試
需編寫 CPU 壓力測(cè)試 python 腳本,后期可以放入計(jì)劃任務(wù)來監(jiān)控并限制 CPU 使用率。
#!/bin/env python
import math
import random
a=10000
b=10000
c=10000
sum=0
for i in range(0,a):
for j in range(0,b):
randomfloat=random.uniform(1,10)
randompow=random.uniform(1,10)
sum+=math.pow(randomfloat, randompow)
print "sum is %s" % sum
當(dāng)我們執(zhí)行 python 的 CPU 壓力測(cè)試腳本后,發(fā)現(xiàn)單核服務(wù) CPU 跑到了 100%。

之后運(yùn)行我們的 CPU 限制腳本,可以看到 CPU 被成功限制。

在此案例中,我們著重來看 Shell 函數(shù),在實(shí)例中我們編寫了安裝與執(zhí)行 CPU 限制兩個(gè)函數(shù),最后編寫主函數(shù),在主函數(shù)中調(diào)用其他函數(shù),配合定時(shí)任務(wù)就能達(dá)到限制某進(jìn)程的 CPU。
4. 注意事項(xiàng)
- 函數(shù)定義建議使用見名知意并需要有一定原則,不僅為了美觀更是為了規(guī)范,使得其他人更好理解與閱讀你的 Shell 腳本,增強(qiáng)腳本可維護(hù)性;
- 對(duì)于函數(shù)調(diào)用,必須在函數(shù)定義之后,Shell 運(yùn)行為順序運(yùn)行,沒有定義函數(shù)則調(diào)用函數(shù)會(huì)有異常;
- 函數(shù)定義不能指定參數(shù),在調(diào)用的時(shí)候傳遞參數(shù)即可,并且調(diào)用函數(shù)傳遞什么參數(shù),函數(shù)就接受什么參數(shù);
- 函數(shù)傳遞參數(shù)與 Shell 腳本傳遞參數(shù)的類型及用法一致,在此可以融會(huì)貫通,對(duì)比理解記憶;
- shell 函數(shù)的返回值,只能是整形,并且在 0-257 之間,不能為字符串或其他形式。
5. 小結(jié)
我們編寫 Shell 就是為了實(shí)現(xiàn)簡(jiǎn)化操作,通常將數(shù)據(jù)和程序分離,我們將一組實(shí)現(xiàn)具體業(yè)務(wù)的邏輯編寫為一個(gè)函數(shù),也可以稱為一段代碼塊,將其模塊化,賦予函數(shù)名稱,利用函數(shù)可以達(dá)到代碼復(fù)用,使得數(shù)據(jù)與邏輯分離,腳本更加便于維護(hù)和管理。