#什么是函数
盛放某一功能的容器
#为什么要用函数
没有引入函数前,遇到重复使用某一个功能的地方,只能复制黏贴实现该功能的代码,这会导致:
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 "oldboy" 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