Bash脚本编程学习笔记08:函数
简介
正如我们在《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 ,,
fi
}
username=$
userinfo
示例:将《Bash脚本编程学习笔记06:条件结构体》中最后的服务脚本中冗余的代码改写为函数,即函数式编程。
#!/bin/bash
#
# chkconfig: -
# Description: test service script
# prog=$(basename $)
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 $ in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status
;;
*)
echo "Usage: $prog {start|stop|restart|status}"
exit
;;
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 $ &> /dev/null; then
return
else
useradd $
return
fi
}
for i in {..}; do
username="$1$i"
userAdd $username
retval=$?
if [ $retval -eq ]; then
echo "The user $username has been added."
elif [ $retval -eq ]; 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=$
if ping -c -q $ip &> /dev/null; then
echo "The host $ip is online."
return
else
return
fi
}
for i in 192.168..{..}; do
ping_test $i
done
Linux中的ping命令,默认是发送无限的请求数据包持续ping的,需要使用-c指定包的数量。这个脚本不太好,因为ping遇到不通的情况会等待一段时间,导致脚本比较耗时。
3、编写一个脚本,通过函数实现乘法口诀表,注意,不是九九乘法口诀表,而是NN乘法口诀表,N作为用户参数传入脚本。
#!/bin/bash
multi() {
N=$
for ((i=;i<=N;i++)); do
for ((j=;j<=i;j++)); do
echo -ne "$j*$i=$((i*j))\t"
done
echo
done
}
multi $
注意:这个脚本有瑕疵,在输出时,当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!=****...*n
阶乘存在一个规律。
!=****
!=***
!=**
!=*
!=
因此。
!=!*
!=!*
!=!*
...
n!=(n-)!*n=n*(n-)!
我们就可以把阶乘定义为一个函数并通过递归的方式来实现阶乘的计算。
#!/bin/bash
fact() {
if [ $ -eq -o $ -eq ]; then
echo
else
echo $(($*$(fact $(($-)))))
fi
}
fact $
斐波那契数列
斐波那契数列是数学家莱昂纳多·斐波那契以兔子繁殖为例引入的,又作兔子数列。
该数列F,第一项和第二项都为1,从第二项开始,每一项的值都是前两项之和。
数列F
F()=
F()=
F()=F()+F()
...
F(n)=F(n-)+F(n-)
...
因此我们可以把求斐波那契数列的第N项的值写为一个函数。
[root@c7-server ~]# cat func_factorial.sh
#!/bin/bash fact() {
if [ $ -eq -o $ -eq ]; then
echo
else
echo $(($*$(fact $(($-)))))
fi
} fact $
[root@c7-server ~]# vim func_fibo.sh
[root@c7-server ~]# bash func_fibo.sh [root@c7-server ~]# bash func_fibo.sh [root@c7-server ~]# bash func_fibo.sh [root@c7-server ~]# bash func_fibo.sh [root@c7-server ~]# bash func_fibo.sh [root@c7-server ~]# bash func_fibo.sh [root@c7-server ~]# bash func_fibo.sh [root@c7-server ~]# bash func_fibo.sh
不过这种方式仅仅是求得第N项的值,我们可以改为打印这个斐波那契数列。
[root@c7-server ~]# cat func_fibo.sh
#!/bin/bash fibo() {
if [ $ -eq -o $ -eq ]; then
echo -n "1 "
else
echo -n "$(($(fibo $(($1-2)))+$(fibo $(($1-1))))) "
fi
} for i in $(seq $); do
fibo $i
done
echo
[root@c7-server ~]# bash func_fibo.sh
Bash脚本编程学习笔记08:函数的更多相关文章
- Bash脚本编程学习笔记06:条件结构体
简介 在bash脚本编程中,条件结构体使用if语句和case语句两种句式. if语句 单分支if语句 if TEST; then CMD fi TEST:条件判断,多数情况下可使用test命令来实现, ...
- Bash脚本编程学习笔记07:循环结构体
本篇中涉及到算术运算,使用了$[]这种我未在官方手册中见到的用法,但是确实可用的,在此前的博文<Bash脚本编程学习笔记03:算术运算>中我有说明不要使用,不过自己忘记了.大家还是尽量使用 ...
- Bash脚本编程学习笔记04:测试命令test、状态返回值、位置参数和特殊变量
我自己接触Linux主要是大学学习的Turbolinux --> 根据<鸟哥的Linux私房菜:基础篇>(第三版) --> 马哥的就业班课程.给我的感觉是这些课程对于bash的 ...
- bash脚本编程学习笔记(二)
1.脚本编程之函数 函数是实现结构化编程重要的思想,主要目的是实现代码重用 定义一个函数: function FUNCNAME { command //函数体 } FUNCNAME(){ //函数 ...
- Bash脚本编程学习笔记05:用户交互与脚本调试
用户交互 在<学习笔记04>中我们有提到位置参数,位置参数是用来向脚本传递参数的一种方式.还有一种方式,是read命令. [root@c7-server ~]# read name alo ...
- bash脚本编程学习笔记(一)
bash脚本语言,不同于C/C++是一种解释性语言.即在执行前不需要事先转变为可执行的二进制代码,而是每次执行时经解释器解释后执行.bash脚本语言是命令的堆砌,即按照实际需要,结合命令流程机制实现的 ...
- Linux Shell脚本编程学习笔记和实战
http://www.1987.name/141.html shell基础 终端打印.算术运算.经常使用变量 Linux下搜索指定文件夹下特定字符串并高亮显示匹配关键词 从键盘或文件里获取标准输入 [ ...
- shell脚本编程学习笔记(一)
一.脚本格式 vim shell.sh #!/bin/bash //声明脚本解释器,这个‘#’号不是注释,其余是注释 #Program: //程序内容说明 #History: //时间和作者 二.sh ...
- 网络编程学习笔记-listen函数
listen函数使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程.在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接 ...
随机推荐
- java线程基础梳理
java线程 概述 进程:运行时概念,运行的应用程序,进程间不能共享内存 线程:应用程序内并发执行的代码段,可以共享堆内存和方法区内存,而栈内存是独立的. 并发理解:在单核机器上,从微观角度来看,一段 ...
- Docker扩展内容之容器环境变量
介绍 docker容器设置环境变量除了可以在容器层面的变量文件中加载也可以在容器运行之初进行预加载环境变量,下面介绍在Dockerfile中编写环境变量的方式 ENV TZ=Asia/Shanghai ...
- window nginx 中文路径, 文件名乱码问题解决
window nginx 中文路径, 文件名乱码, error, not found 此问题是由于windows系统编码与nginx编码设置不一致导致的,因此我们要统一二者的编码 nginx编码设置 ...
- Springboot | @RequestBody 接收到的参数对象属性为空
背景 今天在调试项目的时候遇到一个坑,用Postman发送一个post请求,在Springboot项目使用@RequestBody接收时参数总是报不存在,但是多次检查postman上的请求格式以及项目 ...
- Spring Boot 2.x基础教程:使用国产数据库连接池Druid
上一节,我们介绍了Spring Boot在JDBC模块中自动化配置使用的默认数据源HikariCP.接下来这一节,我们将介绍另外一个被广泛应用的开源数据源:Druid. Druid是由阿里巴巴数据库事 ...
- 【Qt学习笔记】Qt+VS2010的配置
http://blog.csdn.net/jocyln9026/article/details/8575218 关于Qt Qt是1991年由Trolltech公司开发的一个跨平台的C++图形用户界面应 ...
- POJ_1185_状态压缩dp
http://poj.org/problem?id=1185 一次考虑两行,比一行略为复杂.sta保存每种状态炮兵位置,sum保存每种状态当行炮兵总数,a保存地形,dp[i][j][k]表示到第i行当 ...
- 刚安装了ftp之后无法使用root访问,服务器发回了不可路由的地址。使用服务器地址代替。
真的艰辛,用了整整一个下午加晚上,才把服务器搭建好,中间真的好多坑... 错误1: vsftpd正确配置: vsftpd.conf: pam_service_name=vsftpduserlist_e ...
- AppBox实战: 如何实现一对多表单的增删改查
本篇通过完整示例介绍如何实现一对多关系表单的相应服务及视图. 一.准备数据结构 示例所采用的数据结构为"物资需求"一对多"物资清单",通过IDE的实体设 ...
- [Effective Java 读书笔记] 第8章 通用程序设计
本章主要讲了以下几条基本的JAVA编程原则: 1.将局部变量的作用域控制在最小,在使用时才定义 2.for-each优于for循环 有三个例外(1,2点主旨就是,for each只能用于读取,不能用于 ...