七、使用Shell函数

 

1. 什么是函数?

 

Shell脚本中,将一些需要重复使用的操作,定义为公共的语句块,即可称为函数

 shell中允许将一组命令集合语句形成一段可用代码,这些代码块称为shell函数

给这段代码起个名字称为函数名,后续可以直接调用该段代码的功能

 

函数的作用就是将程序里面多次被调用的代码组合起来,称为函数体,并取一个名字称为(函数名),当我们需要用到这段代码的时候,我们就可以直接来调用函数名。

 

2. 如何定义函数?

 


 

Shell函数的语法

在shell中 if语句有它的语法,for循环也有它的语法,那么shell中的函数,那肯定也有它的语法有以下三种:

 
function 函数名 () {  
        指令...  
        return -n  
}  
  
function 函数名 {  
        指令...  
        return -n  
}  
  
函数名 () {  
    指令...  
    return -n  
}  
 

提示:在以上的函数语法中,前面的funcation 表示声明一个函数!!! 可以不写

return -n 是指退出函数

注:定义函数名,不会被输出

 

函数中return说明:

 

    return可以结束一个函数。类似于循环控制语句break(结束当前循环,执行循环体后面的代码)。
    return默认返回函数中最后一个命令状态值,也可以给定参数值,范围是0-256之间。
    如果没有return命令,函数将返回最后一个指令的退出状态值。

 

 

3. 函数如何调用?

 

 

㈠ 当前命令行调用

 

[root@MissHou shell04]# cat fun1.sh 
#!/bin/bash
hello(){
echo "hello lilei $1"
hostname
}
menu(){
cat <<-EOF
1. mysql
2. web
3. app
4. exit
EOF
}

[root@MissHou shell04]# source fun1.sh 
[root@MissHou shell04]# . fun1.sh 

[root@MissHou shell04]# hello 888
hello lilei 888
MissHou.itcast.cc
[root@MissHou shell04]# menu
1. mysql
2. web
3. app
4. exit

 

 

 

㈡ 定义到用户的环境变量中

 

 

 

[root@MissHou shell05]# vim ~/.bashrc 
文件中增加如下内容:
hello(){
echo "hello lilei $1"
hostname
}
menu(){
cat <<-EOF
1. mysql
2. web
3. app
4. exit
EOF
}

注意:
当用户打开bash的时候会读取该文件

 

 

 ㈢ 脚本中调用

 

#!/bin/bash
#打印菜单
source ./fun1.sh
menu(){
cat <<-END
    h    显示命令帮助
    f    显示磁盘分区
    d    显示磁盘挂载
    m    查看内存使用
    u    查看系统负载
    q    退出程序
    END
}
menu        //调用函数

 

使用函数时的规则:

 

 

 

先定义后调用;可以只定义不调用
在同一个脚本里,函数名相同时,后定义生效
函数名区分字母大小写
在函数体内定义的变量是局部变量,只能在当前函数内使用
在函数体外定义的变量是全局变量,全局变量所有函数都可以使用
函数之间可以互相调用
定义一次,可多次重用
Bash调用函数时不会开启新的子Shell,会在现有的shell环境中执行该函数

 

 

4. 应用案例

 

具体需求:

 

写一个脚本让用户输入基本信息(姓名,性别,年龄),如不输入一直提示输入,最后根据用户的信息输出相对应的内容

 

 

思路:

 

    1.交互式定义多个变量来保存用户信息 姓名、性别、年龄

    2.如果不输一直提示输入
        循环直到输入字符串不为空 while 判断输入字符串是否为空
        每个信息都必须不能为空,该功能可以定义为一个函数,方便下面脚本调用

    3.根据用户输入信息做出匹配判断

 

代码实现:

 

#!/bin/bash
#该函数实现用户如果不输入内容则一直循环直到用户输入为止,并且将用户输入的内容打印出来
input_fun()
{
  input_var=""
  output_var=$1
  while [ -z $input_var ]
    do
    read -p "$output_var" input_var
    done
    echo $input_var
}

input_fun 请输入你的姓名:

或者
#!/bin/bash
fun()
{
    read -p "$1" var
    if [ -z $var ];then
        fun $1
    else
        echo $var
    fi
}


#调用函数并且获取用户的姓名、性别、年龄分别赋值给name、sex、age变量
name=$(input_fun 请输入你的姓名:)
sex=$(input_fun 请输入你的性别:)
age=$(input_fun 请输入你的年龄:)

#根据用户输入的性别进行匹配判断
case $sex in
            man)
            if [ $age -gt 18 -a $age -le 35 ];then
                echo "中年大叔你油腻了吗?加油"
            elif [ $age -gt 35 ];then
                echo "保温杯里泡枸杞"
            else
                echo "年轻有为。。。"
            fi
            ;;
            woman)
            xxx
            ;;
            *)
            xxx
            ;;
esac

 

扩展延伸:

 

 

描述以下代码含义:    
:()
{
   :|:&
}
:

 

递归调用示例

Shell版的fork炸弹

 

 

 

仅13个字符:.(){.|.& };.
递归死循环,可迅速耗尽系统资源

 

无限的生成新的进程,造成死机

 

 

 

代码解析

 

.()               #定义一个名为.的函数
{                #函数块的开始标记
.|.&           #在后台递归调用函数.
;              #函数块的结束标记
.             #再次调用函数

 

 

三、综合案例

 

1. 任务背景

现有的跳板机虽然实现了统一入口来访问生产服务器,yunwei用户权限太大可以操作跳板机上的所有目录文件,存在数据被误删的安全隐患,所以希望你做一些安全策略来保证跳板机的正常使用。

 

2. 具体要求

1.只允许yunwei用户通过跳板机远程连接后台的应用服务器做一些维护操作

2.公司运维人员远程通过yunwei用户连接跳板机时,跳出以下菜单供选择:

欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit

 

3.当用户选择相应主机后,直接免密码登录成功

4.如果用户不输入一直提示用户输入,直到用户选择退出

 

 

3. 综合分析

 

1.将脚本放到yunwei用户家目录里的.bashrc文件里(/shell05/jumper-server.sh)

2.将菜单定义为一个函数[打印菜单],方便后面调用

3.用case语句来实现用户的选择【交互式定义变量】

4.当用户选择了某一台服务器后,进一步询问用户需要做的事情 case...esac 交互式定义变量

5.使用循环来实现用户不选择一直让其选择

6.限制用户退出后直接关闭终端 exit

 

4. 落地实现

 

#!/bin/bash
# jumper-server
# 定义菜单打印功能的函数
menu()
{
cat <<-EOF
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
    EOF
}
# 屏蔽以下信号
trap '' 1 2 3 19
# 调用函数来打印菜单
menu
#循环等待用户选择
while true
do
# 菜单选择,case...esac语句
read -p "请选择你要访问的主机:" host
case $host in
    1)
    ssh root@10.1.1.1
    ;;
    2)
    ssh root@10.1.1.2
    ;;
    3)
    ssh root@10.1.1.3
    ;;
    h)
    clear;menu
    ;;
    q)
    exit
    ;;
esac
done


将脚本放到yunwei用户家目录里的.bashrc里执行:
bash ~/jumper-server.sh
exit

 

 

 

进一步完善需求

为了进一步增强跳板机的安全性,工作人员通过跳板机访问生产环境,但是不能在跳板机上停留。

#!/bin/bash
#公钥推送成功
trap '' 1 2 3 19
#打印菜单用户选择
menu(){
cat <<-EOF
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
EOF
}

#调用函数来打印菜单
menu
while true
do
read -p "请输入你要选择的主机[h for help]:" host

#通过case语句来匹配用户所输入的主机
case $host in
    1|DB1)
    ssh root@10.1.1.1
    ;;
    2|DB2)
    ssh root@10.1.1.2
    ;;
    3|web1)
    ssh root@10.1.1.250
    ;;
    h|help)
    clear;menu
    ;;
    q|quit)
    exit
    ;;
esac
done

自己完善功能:
1. 用户选择主机后,需要事先推送公钥;如何判断公钥是否已推
2. 比如选择web1时,再次提示需要做的操作,比如:
clean log
重启服务
kill某个进程

 

回顾信号:

 

1) SIGHUP             重新加载配置    
2) SIGINT            键盘中断^C
3) SIGQUIT          键盘退出
9) SIGKILL             强制终止
15) SIGTERM            终止(正常结束),缺省信号
18) SIGCONT           继续
19) SIGSTOP           停止
20) SIGTSTP         暂停^Z

 

例1:Shell函数实例如下

 

 

function fj () {  
    echo "我是风姐!"  
}  
  
function zhangsan () {  
    echo "我是张三!"  
}  
  
fj  
zhangsan 

 

 

 

剖析:

 

function fj () {             # 前面的function是声明一个函数 名字叫 fj () {
echo "我是风姐!"          #下面呢 我们执行操作.echo 我是风姐
}                                    #最后 我们以 } 为结束 

function zhangsan () {    # 前面的function是声明一个函数 名字叫 zhangsan () {
echo "我是张三!"    #下面呢 我们执行操作.echo 我是张三
}                                        #最后 我们以 } 为结束 

fj                   #调用fj 函数
zhangsan      #调用zhangsan函数

 

 

那么定义了两个函数,我们最终目的是要调用它所以 在函数的下面输入了fj 和 zhangsan 意思是说 我要调用这两个函数,那么这两个函数中有两个命令 输出一个张三和凤姐,所以在我们执行脚本的时候结果就会是如下:

 

 

例2:分离函数体执行函数的脚本文件

 

 

cat >>/etc/init.d/functions<< EOF  
function zhangsan () {  
    echo "我就是张三"  
}  
EOF  

 

 

以上代码什么意思:我们可以看到第一行是cat >> /etc/....这个路径 我们完全可以理解为,把下面的内容 导入到/etc/init.d/functions这个文件中; 那么这个文件是Linux系统内置的脚本函数库

 


我们可以清楚的看到我们写的函数已经导入到了/etc/init.d/functions 这个文件中,那么接下来进行操作了 如下:

#!/bin/bash  
if [ -f /etc/init.d/functions ]  
  then  
    . /etc/init.d/functions  
fi  
zhangsan  

 

 

解释:if 如果/etc/init.d/functions是一个普通文件,那么我们执行. /etc/init.d/functions 在这里这个"."是用来加载functions 中的命令或者变量参数等;最后因为我们在上面定义了zhangsan这个函数,那我们在最后一行可以调用这个zhangsan函数 输出如下:

 

那么在这里我们来一个总结:

当我们定义函数过多的情况下,我们可以把函数写在某一个文件中,当我们写脚本的时候需要用到这个函数中的某一个指令,那么我们就可以直接来调用文件中的函数名!

 

例3:编写带参数的shell函数实例

 

 

#!/bin/bash  
  
function lisi () {  
        echo 我的名字叫: $1  
}  
lisi $1  

 

 

 

 

在这里我们定义了一个名字叫做lisi的函数,这里需要注意的是echo那行,正常的输出是没有问题的,但是在后面加了一个特殊的位置变量。$1 最后在我们调用函数的时候 后面也加了特殊位置变量 $1 执行效果如下:

 

 

 

 

3、利用Shell函数开发企业级URL检测脚本

这是书上的一部分案例,但是我还会在这里通过我的想法来解释整个脚本中的全部过程!

1)首先我们不用函数来写一个检测URL的脚本如下:

 

 

#!/bin/bash  
#no.1  
if [ "$#" -ne 1 ]  
  then  
      echo "/root/sh/ $0" 请您输入一个网址  
      exit 1  
fi  
#no.2  
wget --spider -q -o /dev/null --tries=1 -T 3 $1  
if [ "$?" -eq 0 ]  
  then  
    echo "$1 检测是成功的!"  
  else  
    echo "$1 检测是失败的!"  
    exit 1  
fi  

 

 

 

 

 

剖析:

 

 

#!/bin/bash  
#no.1  
if [ "$#" -ne 1 ]              #如果用户输入的传参参数不是1  
  then          # 那么    
      echo "/root/sh/ $0" 请您输入一个网址  #输出脚本路径和脚本名称 最后输出请您输入一个网址!  
      exit 1            #退出脚本  
fi              #结束  
#no.2  
wget --spider -q -o /dev/null --tries=1 -T 3 $1 #如果用户输入传参数是对的,那么执行wget命令最后$1是用户输入的内容  
if [ "$?" -eq 0 ]    #如果以上wget测试成功了!  
  then  #那么  
    echo "$1 检测是成功的!"  #输出用户输入网址并输出是成功的  
  else  #否则  
    echo "$1 检测是失败的!"  #输出用户输入网址并输出是失败的  
    exit 1      #退出脚本  
fi    #fi结束  

 

 

 

感觉不够养眼?没有关系,如下图

 

 

 

 

最后运行的结果如下:

 

 

 

这不是重点,重点是我们使用shell函数来写整个过程,如下:

 

 

function TS () {         
        echo "/root/sh/ $0" 请您输入一个网址  
        exit 1  
}  
  
function Check_url () {  
        wget --spider -q -o /dev/null --tries=1 -T 3 $1  
        if [ "$?" -eq 0 ]   
          then  
            echo "$1 检测成功!"  
          else  
            echo "$1 检测失败"  
            exit 1  
        fi        
}  
  
function JG () {  
        if [ "$#" -ne 1 ]  
          then  
              TS  
        fi  
          Check_url $1  
}  
JG $*            

 

 

 

 

剖析:

 

 

function TS () {       #定义一个名字为TS的函数 (提示的意思)  
        echo "/root/sh/ $0" 请您输入一个网址 #执行的指令是 输出一个:请您输入一个网址  
        exit 1    #退出  
}  
  
function Check_url () {  #定义一个名字为Check_url的函数 (检测的意思)  
        wget --spider -q -o /dev/null --tries=1 -T 3 $1 #如果用户输入传参数是对的,那么执行wget命令最后$1是用户输  
入的内容  
        if [ "$?" -eq 0 ] #如果以上wget去测试成功了!  
          then  #那么  
            echo "$1 检测成功!"  #输出用户输入网址并输出是成功的  
          else  #否则  
            echo "$1 检测失败"    # 输出用户输入网址并输出是失败的  
            exit 1  #退出  
        fi        
}  
  
function JG () {   #这是最后的函数JG(结果的意思)   
        if [ "$#" -ne 1 ]   ##如果用户输入的传参参数不是1  
          then      #那么  
              TS         #注意了!!! 调用TS   
        fi            
          Check_url $1  #如果上面传参是1 那么就调用Check_url函数  
}  
JG $*  #这里的$*就是把命令行接收的所有参数作为函数传给函数内部  

 

 

 

 

最后我们再次执行脚本

 

 

最后为了能够让大家清晰的看到if语句的脚本和函数脚本的对比,一张图解释:

 

 

4、案例二 实现菜单脚本,使用函数编写如下:

 

 

function CDAN(){  
cat << yankerp    
+------------------------------------------------+  
|                                                |  
|        _o0o_          1. 安装Nginx             |  
|        08880          2. 安装Apache            |  
|       88"."88         3. 安装MySQL             |  
|       (|-_-|)         4. 安装PHP               |  
|        0\=/0          5. 部署LNMP环境          |  
|      __/   \__        6. 安装zabbix监控        |  
|     ‘\   ///‘       7. 退出此管理程序         |  
|    / Linux一键 \      8. 关闭计算机            |  
|  ||    Server   ||    ======================   |    
|  \        ////         一键安装服务           |  
|   |||  i i i    |||               by Yankerp   |  
|   ___        ___      ======================   |  
|___‘.  /--.--\ .‘___                            |  
+------------------------------------------------+  
yankerp  
}  
CDAN  
  
LOG_DIR=/usr/local/src  
read -p "请您输入1-8任意数值:" NUM  
  
if [ ${#NUM} -ne 1 ]  
  then  
        echo "请您输入1|2|3|4|5|6|7|8"  
        exit 1  
fi  
  
expr $NUM + 1 &>/dev/null  
if [ "$?" -ne 0 ]  
   then  
        echo "请您输入数值!"  
        exit 1  
fi  
  
if [ "$NUM" -gt 8 ];then  
        echo "请您输入比8小的数值"  
        exit 1  
elif [ "$NUM" -eq 0 ];then  
        echo "请您输入比0大的数值"  
        exit 1  
fi  
######################  
function Nginx_DIR() {  
        yum install -y gcc gcc-c++ pcre-devel zlib-devel openssl-devel &>/dev/null  
        if [ $? -eq 0 ]  
           then  
                cd $LOG_DIR  && wget http://nginx.org/download/nginx-1.12.2.tar.gz &>/dev/null && useradd -M -s /sbin/nologin nginx && \  
        tar zxf nginx-1.12.2.tar.gz && cd nginx-1.12.2/ && \  
                ./configure --prefix=/usr/local/nginx --with-http_dav_module --with-http_stub_status_module --with-http_addition_module --with-http_sub_module  --with-http_flv_module --with-http_mp4_module --with-pcre --with-http_ssl_module --with-http_gzip_static_module --user=nginx &>/dev/null && make &>/dev/null && make install &>/dev/null   
        fi  
  
        if [ -e /usr/local/nginx/sbin/nginx ];then  
                ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/ && nginx && echo "Nginx安装并启动成功!!!"  
        fi  
}  
  
if [ $NUM -eq 1 ]  
  then  
     echo "开始安装Nginx请稍等..." && Nginx_DIR  
fi  

 

 

 

 

 

输出如下:

 

 

 

图2:

 

 

 

以上脚本首先通过了if语句进行各种判断, 判断用户输入的参数等等;在后面使用了函数 来定义了安装Nginx的操作,最后我们调用了函数使整个函数运行了起来!!!

呃,大家可以参考参考,若写的不好请谅解~ 我感觉对刚开始接触shell的人还是蛮有帮助的,只不过使用了多分支的if语句,感觉有点繁琐,接下来就会更新case语句了这样就可以解决多分支if语句很繁琐的问题!!!

 

 

 

 

函数可以递归调用(函数自己调用自己)

 

function函数 +case分支结构 给源码apache启动脚本

apache指定安装目录/usr/local/httpd2,脚本名叫apached.sh

答:

写完脚本把apached.sh的权限改成x执行权限并拷贝到系统存储启动服务的目录/etc/init.d下并去掉后缀.sh

然后把/etc/init.d/apached脚本加入到chkconfig的开启自启动服务中。

1.进入到系统存储运行的目录

 

 

 

 

 

 

 

 


 

posted @ 2018-05-03 10:52  钟桂耀  阅读(316)  评论(0编辑  收藏  举报