兔⼦繁殖为例c语⾔,Bash脚本编程学习笔记08:函数
简介
正如我们在《Bash脚本编程学习笔记06:条件结构体》中最后所说的,我们应该把⼀些可能反复执⾏的代码块整合起来,避免反复编写使得代码过于臃肿。
函数正是为了解决这个问题⽽存在的。函数在定义时,可以将常⽤的代码整合为⼀个整体,当我们需要执⾏的时候,只需要调⽤这个函数即可。
Bash是过程式编程语⾔,从上⾄下顺序执⾏代码,因此函数定义必须在函数调⽤之前完成。
函数属于shell的基础特性,即不仅仅是针对于bash,包括csh、sh、ksh和zsh等都具有这⾥说明的函数特性。
函数会在当前的shell环境下执⾏,不会创建新的进程(⼦shell)来解释函数。
函数可以通过“unset -f”命令删除。
函数的定义和调⽤
函数的定义有两种⽅式。
⽅式⼀。
FUNC_NAME () {
BODY
}
⽅式⼆。
functionFUNC_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.shThis is justfor 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
username=$1userinfo
⽰例:将《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."
c语言斐波那契数列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 instart)
start
;;
stop
;;
restart)
restart
;;
status)
status
;;*)echo "Usage: $prog {start|stop|restart|status}"exit1;;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; thenreturn1
elseuseradd $1return0
fi}for i in {01..03}; dousername="$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."return0
elsereturn1
fi}for i in 192.168.152.{1..254}; doping_test $idone
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”制表符会使显⽰较不⼈性化。
函数中的变量作⽤域
本地变量:仅当前shell有效(即当前bash进程)。
环境变量:当前shell及其⼦shell(即当前bash进程即其⼦bash进程)。
局部变量:在某部分代码⽚段中有效(例如函数)。
结合作⽤域的概念,我们来看下⾯这个⽰例。
#!/bin/bash
name=tom
set_name() {
name=jerryecho$name
}
set_nameecho $name
我们应该会认为这样吧?
set_name --输出-->jerryecho $name --输出--> tom
其实,真实的结果是:
[root@c7-server ~]# bash func_scope.shjerry
jerry
造成这种结果的原因是,局部变量是需要⼿动定义的,⽽不是其出现在函数体中就是局部变量了。
可以通过内置命令local来定义局部变量,local仅可以在函数内部使⽤!
local [option] name[=value] …
因此我们对脚本进⾏改写,函数体中的变量明确使⽤local命令定义,就可以验证我们此前说的变量作⽤域的理论了。
[root@c7-server ~]# cat func_scope.sh#!/bin/bash
name=tom
set_name() {
local name=jerryecho$name
}
set_nameecho$name
[root@c7-server ~]# bash func_scope.shjerry
tom
函数中的变量作⽤域,是⼀个难点,我其实没搞太明⽩。上⾯的⽰例其实是⼀个⾮常简单的⽰例,在复杂的情况下,例如函数A调⽤函数B 时,local会变得很有帮助。具体遇到的时候,建议⼤家再翻阅⼀下官⽅的⼿册。
函数的递归
⼀个函数可以调⽤另⼀个函数,⽽当⼀个函数调⽤⾃⾝时,就叫做函数的递归。
递归不会⽆限递归,⼀般会有⼀个边界,在边界处会返回,⽽后根据递归的顺序逐⼀按照相反的顺序
返回。
函数的递归很好地解决了计算阶乘(factorial)和斐波那契(fibonacci)数列。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。