函数的定义
当需要重复使用一个脚本中的功能,或者脚本达到一定程度时,使用函数就比较方便。
定义函数语法:
#函数名 function_name() { #函数体,在函数中执行的命令行 commands... #参数返回,return可有可无,如果没有,则返回函数最后一条命令运行结果作为返回值;如果有,则return后跟数值n(0~255) [return int;] }
第二种方法定义函数语法:
function functionname() { commands... } #如果有function关键字,()可以省略
函数体外的大括号与函数体之间必须有空白符或者换行符,因为{}时保留字,只有存在空白符或者换行符时,{}才会被识别为保留字
函数的参数、变量与返回值
向函数传递参数
函数使用特殊变量$1、$2....来访问传递的参数。
如:
[root#rs1 test]# cat func_1.sh passed() { a=$1 echo "passed() : \$0 is $0" echo "passed() : \$1 is $1" echo "passed() : \$a is $a" echo "passed() : total args passed to me $#" echo "passed() : all args (\$@) passed to me - \"$@\"" echo "passed() : all args (\$*) passed to me - \"$*\"" } echo "****** calling passed() first time*******" passed one echo "****** calling passed() second time*******" passed one two three
运行结果:
[root#rs1 test]# bash func_1.sh ****** calling passed() first time******* passed() : $0 is func_1.sh passed() : $1 is one passed() : $a is one passed() : total args passed to me 1 passed() : all args ($@) passed to me - "one" passed() : all args ($*) passed to me - "one" ****** calling passed() second time******* passed() : $0 is func_1.sh passed() : $1 is one passed() : $a is one passed() : total args passed to me 3 passed() : all args ($@) passed to me - "one two three" passed() : all args ($*) passed to me - "one two three"
所有函数参数都可以通过$1、$2、...、$N,即位置参数来访问
$0指代Shell脚本的名字
$*或者$@保存传递给函数的所有参数
$#保存传递给函数的位置参数的个数
本地变量
默认情况下脚本所有的变量都是全局的,在函数中修改一个变量将改变这个脚本中此变量的值。
如:
[root#rs1 test]# cat native_fuc.sh #定义函数 create_logFile() { d=$1 echo "create_logFile(): d is set to $d" } #定义变量d d=/var/log/message echo "Before calling create_logFile d is set to $d" #调用函数create_logFile,并指定一个参数 create_logFile "/etc/passwd" echo "After calling create_logFile d is set to $d"
执行结果:
[root#rs1 test]# bash native_fuc.sh Before calling create_logFile d is set to /var/log/message create_logFile(): d is set to /etc/passwd After calling create_logFile d is set to /etc/passwd
如果只想函数传递的参数被函数体本身使用,而不更改全局变量的值,则可以添加local关键字
如:
[root#rs1 test]# cat native_fuc.sh #定义函数 create_logFile() { local d=$1 #d=$1 echo "create_logFile(): d is set to $d" } #定义变量d d=/var/log/message echo "Before calling create_logFile d is set to $d" #调用函数create_logFile,并指定一个参数 create_logFile "/etc/passwd" echo "After calling create_logFile d is set to $d"
运行结果:
[root#rs1 test]# bash native_fuc.sh "/etc/passwd" Before calling create_logFile d is set to /var/log/message create_logFile(): d is set to /etc/passwd After calling create_logFile d is set to /var/log/message
local只能在函数内部使用
local命令将变量名的可见范围限制在函数内部
使用return命令
如果在函数里面有Shell内置命令return,则函数执行到return语句时结束,并返回到Shell脚本中调用函数位置的下一个命令。如果return带有一个数值型参数,则这个参数就是函数的返回值,该值范围:0~255;否则,函数的返回值是函数体内最后一个执行的命令的返回状态。
如:
[root@rs1 test]# cat func_return.sh checkpid() { #定义本地变量i local i #使用for循环遍历传递给此函数的所有参数 for i in $* do #如果目录/proc/$i存在,则执行"存在"语句、否则执行"不存在"语句 #每个进程的pid在/proc/目录下,都会有一个以pid号命名的目录来维护进程 test -d "/proc/$i" && echo "pid:$i 存在" || echo "pid:$i 不存在" done } checkpid 3027 24781 24789 1
执行结果:
[root@rs1 test]# bash func_return.sh pid:3027 存在 pid:24781 不存在 pid:24789 不存在 pid:1 存在
函数返回值测试
可以直接在脚本调用函数语句的后面(必须紧跟着)使用Shell特殊参数"?"来测试函数调用的返回值,通过特殊参数"?"可以得到最近一次执行的前台命令的退出状态。
如:
[root@rs1 test]# cat func_return_1.sh checkpid() { #定义本地变量i local i #使用for循环遍历传递给此函数的所有参数 for i in $* do #如果目录/proc/$i存在,则执行"存在"语句、否则执行"不存在"语句 #每个进程的pid在/proc/目录下,都会有一个以pid号命名的目录来维护进程 test -d /proc/$i if [ $? = 0 ] then echo "The $i is running" else echo "The $i is not running" fi done } checkpid $*
执行结果:
[root@rs1 test]# bash func_return_1.sh 1 222333 1803 The 1 is running The 222333 is not running The 1803 is running
函数的调用
在Shell命令行调用函数
在命令行中,可以通过直接输入函数的名字,来调用或引用函数。
如:
[root@rs1 test]# yday (){ date --date='1 day ago'; } [root@rs1 test]# yday Thu Jul 5 16:05:47 CST 2018
在脚本中调用函数
在脚本中定义并调用一个函数方法:
[root@rs1 test]# cat func_call.sh yday() { #显示一天前的日期和时间 date --date='1 day ago' } #调用函数 yday
执行脚本:
[root@rs1 test]# bash func_call.sh Thu Jul 5 17:51:36 CST 2018
要在脚本中调用函数,首先要创建函数,并确保函数定义的位置比调用函数更靠前。否则会执行失败:
[root@rs1 test]# cat func_call_erro.sh #调用函数 yday yday() { #显示一天前的日期和时间 date --date='1 day ago' } [root@rs1 test]# bash func_call_erro.sh func_call_erro.sh: line 4: yday: command not found
从函数文件中调用函数
可以将所有的函数存储在一个函数文件中,然后将函数加载到当前脚本或者命令行。
加载函数文件中所有函数的方法:
. /path/to/funcution.sh
如:
创建一个函数文件:
[root@rs1 test]# cat my_func.sh #定义变量 declare -r TURE=0 declare -r FALSE=1 declare -r PASSWD_FILE=/etc/passwd #用途:将字符串转化为小写 #参数: # $1 -> 要转换为小写的字符串 function to_lower() { #定义本地变量str local str="$@" #定义本地变量output local output #将变量str的值转换为小写,然后赋值给output变量 output=$(tr '[A-Z]' '[a-z]' <<< "${str}") echo $output } #用途:如果是root用户执行则返回true #参数:无 #返回值:True或False function is_root() { #如果运行次脚本的用户uid等于0,则返回0,否则返回1 if test $(id -u) -eq 0 then return $True else return $False fi } #用途:如果用户名存在于文件/etc/passwd中,则返回true #参数:$1(用户名) -> 要在文件/etc/passwd中检查用户名 #返回值:True或False function is_user_exits() { #定义本地变量u local u=$1 #如果文件/etc/passwd中存在以变量$1的值为开头行,则返回0,否则返回1 grep -q "^${u}" $PASSWD_FILE && return $True || return $False }
创建一个脚本,在脚本中调用函数文件:
[root@rs1 test]# cat func_demo.sh #加载函数文件my_func.sh #根据自己文件路径进行加载 . /mnt/test/my_func.sh #定义一个本地变量用于测试 var="I Like You But Just Like You." #调用函数is_root,执行成功或失败,打印不同结果 is_root && echo "You are logged in as root." || echo "You are not logged in as root." #调用函数is_user_exits is_user_exits `whoami` && echo "Account found." || echo "Account not found." #打印修改前var的值 echo -e "*** Orignal quote: \n${var}" #调用函数to_lower() #将$var作为参数传递给函数to_lower() #在echo内使用命令替换 echo -e "*** Lowercase version: \n$(to_lower ${var})"
执行结果:
以root用户身份运行:
[root@rs1 test]# whoami root [root@rs1 test]# bash func_demo.sh You are logged in as root. Account found. *** Orignal quote: I Like You But Just Like You. *** Lowercase version: i like you but just like you.
切换成别的用户:
[kiosk@rs1 test]$ whoami kiosk [kiosk@rs1 test]$ bash func_demo.sh You are not logged in as root. Account found. *** Orignal quote: I Like You But Just Like You. *** Lowercase version: i like you but just like you.
递归函数调用
递归函数是重复调用函数本身的函数,并且没有递归调用次数的限制。
一个递归函数调用的例子:
[root@rs1 test2]# cat fact.sh #定义函数factorial(计算给丁命令行参数的阶乘) factorial() { #定义本地变量i local i=$1 #定义本地变量f local f #声明变量i为整数 declare -i i #声明变量f为整数 declare -i f #factorial函数被调用直到$f的值<=2 #开始递归 test $i -le 2 && echo $i || { f=$(( i - 1));f=$(factorial $f);f=$((f * i )); echo $f; } } #显示函数用法 test $# -eq 0 && { echo "Usage: $0 number"; exit 1; } #调用函数 factorial $1
执行脚本:
[root@rs1 test2]# bash -x fact.sh 3 + test 1 -eq 0 + factorial 3 + local i=3 + local f + declare -i i + declare -i f + test 3 -le 2 + f=2 ++ factorial 2 //到这里进行递归 ++ local i=2 ++ local f ++ declare -i i ++ declare -i f ++ test 2 -le 2 //到这个条件递归结束 ++ echo 2 + f=2 + f=6 //f = f * i 此时,f=2,i=3 + echo 6 6 [root@rs1 test2]# bash fact.sh 4 24
函数后台运行
"&"操作符可以将命令放在后台运行并释放当前终端。同样也可以将函数放在后台运行/
定义语法规则如下:
#定义函数name name() { echo "Do something" sleep 1 } #将函数放在后台运行 name & #继续执行其他命令 ...
如:
[root@rs1 test2]# cat progressdots.sh #定义函数progress,显示进度条 progress() { # echo -n "$0:Please wait" #执行无限while循环 while true do echo -n "." #休眠2秒 sleep 2 done } #定义函数dobackup dobackup() { sleep 10 } #将函数放在后台运行 progress & #保存函数progress()运行的进程号 #需要使用PID来结束该函数 MYSELF=$! #转移控制到函数dobackup dobackup #杀死进程 kill $MYSELF > /dev/null 2>&1 echo -n "done"
执行结果:
[root@rs1 test2]# bash progressdots.sh .....done //总共用时10秒,每个点隔二秒出现一次
总结
Shell中函数的使用,可以:
节省大量时间
避免代码编写的冗余
更容易的编写程序
非常简单的维护程序
函数也是DevoPS的一部分