2、功能函数

函数基础

基础知识

场景需求

	在shell脚本的编写过程中,我们经常会遇到一些功能代码场景:多条命令组合在一起,实现一个特定的功能场景逻辑、一些命令在脚本内部的多个位置频繁出现。在这些场景的代码量往往不多,但是频繁使用的话,会导致脚本的整体逻辑脉络比较松散和框架散乱。
	所以我们需要一种脚本逻辑,不仅仅能够满足松散代码的功能目的,还能精简重复的代码。函数就是来满足这种场景的解决方案 -- 而函数,也是所谓的面向对象编程的一种表现样式。

函数

	所谓的函数,本质上就是一段能够满足特定功能的代码块。一旦定义好函数代码后,我们就可以在脚本的很多位置随意的使用。
	定义功能代码块的动作叫 函数定义,使用函数代码的动作叫 函数调用。
函数的优势:
	1. 代码模块化,调用方便,节省内存
    2. 代码模块化,代码量少,排错简单
    3. 代码模块化,可以改变代码的执行顺序

基本语法

定义函数:
	样式1:标准格式
    function 函数名{		
        函数体				
    }						
	样式2:简约格式
    函数名() {		
        函数体				
    }
注意:
	function 的作用和 () 的作用是一样的,都是定义一个函数。
	函数的名称是自定义的,而且在脚本范围内必须唯一。
	函数体内是普通的能够正常执行的命令,命令的执行流程符合顺序逻辑。
调用函数:			
	函数名					
注意:
	函数名出现在任何位置,就代表在该位置调用函数内代码块的执行。
	函数名一般在函数定义后调用,否则的话会发生报错。

简单实践

实践1-标准函数的实践

[root@localhost ~]# cat function_simple_test.sh
#!/bin/bash
# 功能:简单函数的定义和调用

# 定制一个函数,提示脚本的使用方式
function Usage {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
}

# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    echo "您输入的脚本参数是1个"
else
    Usage
fi
脚本执行效果
[root@localhost ~]# /bin/bash function_simple_test.sh
脚本的使用帮助信息: xxx
[root@localhost ~]# /bin/bash function_simple_test.sh aa
您输入的脚本参数是1个
[root@localhost ~]# /bin/bash function_simple_test.sh aa bb
脚本的使用帮助信息: xxx

image

实践2-变种函数的实践

[root@localhost ~]# cat function_simple_test2.sh
#!/bin/bash
# 功能:简单函数的定义和调用

# 定制一个函数,提示脚本的使用方式
Usage() {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
}

# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    echo "您输入的脚本参数是1个"
else
    Usage
fi
脚本执行效果
[root@localhost ~]# /bin/bash function_simple_test2.sh
脚本的使用帮助信息: xxx
[root@localhost ~]# /bin/bash function_simple_test2.sh aa
您输入的脚本参数是1个
[root@localhost ~]# /bin/bash function_simple_test2.sh aa bb
脚本的使用帮助信息: xxx

image

实践3-函数的调用顺序和名称唯一 实践

[root@localhost ~]# cat function_simple_test3.sh
#!/bin/bash
# 功能:简单函数的定义和调用

# 定制一个函数,提示脚本的使用方式
Usage() {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
}
echo "第一次调用效果: "
Usage

# 同名函数会覆盖前面的
# 定制同名的函数,提示脚本的使用方式
Usage() {
    echo -e "\e[31m脚本的使用帮助信息-------: xxx\e[0m"
}
# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    # 调用一个后面才会生成的函数,所以先调用会报错没有这个函数
    func
else
    Usage
fi

# 定制一个函数
func() {
    echo "您输入的脚本参数是1个"
}

脚本执行效果
[root@localhost ~]# /bin/bash function_simple_test3.sh
第一次调用效果:
脚本的使用帮助信息: xxx
脚本的使用帮助信息-------: xxx
[root@localhost ~]# /bin/bash function_simple_test3.sh a
第一次调用效果:
脚本的使用帮助信息: xxx
function_simple_test3.sh:行18: func: 未找到命令

结果显示:
	函数名称重复的话,会导致同名函数被覆盖
	函数在没有定义前调用的话,会导致异常报错

函数退出

基础知识

简介

	我们可以将函数代码块,看成shell脚本内部的小型脚本,所以说函数代码块也会有执行状态返回值。对于函数来说,它通常支持两种种状态返回值的样式。
样式1-默认的退出状态
	默认情况下,函数的退出状态是函数体内的最后一条命令的退出状态,可以通过 $? 来获取
样式2-return定制状态返回值
	在函数体内部,通过return定制状态返回值的内容
	注意:
		return的状态返回值必须尽快使用,否则会被其他return的值覆盖
		return的状态返回值必须在 0-255,否则失效

简单实践

实践1-默认退出状态

[root@localhost ~]# cat function_exit_status1.sh
#!/bin/bash
# 功能:函数默认状态返回值

# 定制成功运行的函数
ok_func() {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
}
# 定制一个运行失败的函数
err_func() {
    666666
}
# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    err_func
    echo "错误函数的执行状态返回值: " $?
else
    ok_func
    echo "成功函数的执行状态返回值: " $?
fi

image

脚本执行效果
[root@localhost ~]# /bin/bash function_exit_status1.sh
脚本的使用帮助信息: xxx
成功函数的执行状态返回值:  0
[root@localhost ~]# /bin/bash function_exit_status1.sh aa
function_exit_status1.sh:行10: 666666: 未找到命令
错误函数的执行状态返回值:  127
[root@localhost ~]# lll; echo $?
bash: lll: 未找到命令
127
结果显示:
	对于异常的函数来说,默认的状态返回值有安全隐患

image

实践2-return定制函数的返回值实践

[root@localhost ~]# cat function_exit_status2.sh
#!/bin/bash
# 功能:return定制函数状态返回值

# 定制成功运行的函数
ok_func() {
    echo -e "\e[31m脚本的使用帮助信息: xxx\e[0m"
    # 定制超范围的状态返回值
    return 666
}
# 定制一个运行失败的函数
err_func() {
    666666
    # 定制状态返回值
    return 222
}
# 定制脚本使用逻辑
if [ $# -eq 1 ]
then
    err_func
    echo "错误函数的执行状态返回值: " $?
else
    ok_func
    echo "成功函数的执行状态返回值: " $?
fi
脚本执行效果
[root@localhost ~]# /bin/bash function_exit_status2.sh
脚本的使用帮助信息: xxx
成功函数的执行状态返回值:  154
[root@localhost ~]# /bin/bash function_exit_status2.sh aa
function_exit_status2.sh:行12: 666666: 未找到命令
错误函数的执行状态返回值:  222
结果显示:
	return的状态返回值范围必须满足要求

函数进阶实践

传参函数

简介

	简单的函数定义和调用的实践,我们只能实现固定内容的输出,不具有灵活性。其实函数作为shell脚本内部的小脚本也支持脚本传参的一系列能力。基本语法效果如下
定义函数:
    函数名() {		
        函数体	${变量名}			
    }
注意:
	函数体内通过 ${变量名} 来实现函数体的功能通用性。
调用函数:			
	函数名 参数			
注意:
	函数在调用的时候,接收一些参数并传输到函数体内部。

实践1-传参函数实践

查看脚本内容
[root@localhost ~]# cat function_arg_input.sh
#!/bin/bash
# 功能:传参函数定义和调用

# 定制数据运算的函数
add_func() {
    echo $(( $1 + $2 ))
}
sub_func() {
    echo $(( $1 - $2 ))
}
mul_func() {
    echo $(( $1 * $2 ))
}
div_func() {
    echo $(( $1 / $2 ))
}

echo -n "4+3="; add_func 4 3
echo -n "4-3="; sub_func 4 3
echo -n "4*3="; mul_func 4 3
echo -n "4/3="; div_func 4 3
脚本执行效果
[root@localhost ~]# /bin/bash function_arg_input.sh
4+3=7
4-3=1
4*3=12
4/3=1

脚本传参

简介

	传参函数定义和调用的实践,实现了函数层面的灵活性,但是它受到函数调用本身的参数限制。往往这些参数我们需要在脚本执行的时候传递进去,从而实现脚本功能的灵活性。
	基本语法效果如下
定义函数:
    函数名() {		
        函数体	${函数参数}			
    }
    
调用函数:
	函数名	${脚本参数}

脚本执行:
	/bin/bash /path/to/scripts.sh arg
	
注意:
	由于脚本内部调用脚本参数和函数体内调用函数参数都遵循位置变量的使用
	所以,一般情况下,我们会借助于临时变量的方式接收各自的参数,从而避免引起误会

实践1-脚本传参函数实践

查看脚本内容
[root@localhost ~]# cat function_arg_scripts.sh
#!/bin/bash
# 功能:脚本传参函数调用

# 定制数据运算的函数
add_func() {
    echo $(( $1 + $2 ))
}
sub_func() {
    echo $(( $1 - $2 ))
}
mul_func() {
    echo $(( $1 * $2 ))
}
div_func() {
    echo $(( $1 / $2 ))
}

[ $# -ne 2  ] && echo "必须传递两个数字参数" && exit
echo -n "$1+$2="; add_func $1 $2
echo -n "$1-$2="; sub_func $1 $2
echo -n "$1*$2="; mul_func $1 $2
echo -n "$1/$2="; div_func $1 $2

注意:
	这种简单的脚本传参函数调用,导致大量的位置参数,容易引起混乱,需要改造
脚本执行效果
[root@localhost ~]# /bin/bash function_arg_scripts.sh
必须传递两个数字参数
[root@localhost ~]# /bin/bash function_arg_scripts.sh 5 4
5+4=9
5-4=1
5*4=20
5/4=1

实践2-脚本传参函数进阶实践

查看脚本内容
[root@localhost ~]# cat function_arg_scripts2.sh
#!/bin/bash
# 功能:传参函数定义和调用

# 接收脚本传参
arg1=$1
arg2=$2
# 定制数据运算的函数
add_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} + ${num2} ))
}
sub_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} - ${num2} ))
}
mul_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} * ${num2} ))
}
div_func() {
    num1=$1
    num2=$2
    echo $(( ${num1} / ${num2} ))
}

[ $# -ne 2  ] && echo "必须传递两个数字参数" && exit
echo -n "${arg1}+${arg2}="; add_func ${arg1} ${arg2}
echo -n "${arg1}-${arg2}="; sub_func ${arg1} ${arg2}
echo -n "${arg1}*${arg2}="; mul_func ${arg1} ${arg2}
echo -n "${arg1}/${arg2}="; div_func ${arg1} ${arg2}
脚本执行效果
[root@localhost ~]# /bin/bash function_arg_scripts2.sh
必须传递两个数字参数
[root@localhost ~]# /bin/bash function_arg_scripts2.sh 7 5
7+5=12
7-5=2
7*5=35
7/5=1

函数基础案例

信息采集

脚本实践-采集系统负载信息

查看脚本内容
[root@localhost ~]# cat function_systemctl_load.sh
#!/bin/bash
# 功能:采集系统负载信息
# 版本:v0.3
# 作者:书记
# 联系:www.superopsmsb.com

# 定制资源类型
resource_type=(CPU MEM)


# 定制cpu信息输出函数
cpu_info() {
	cpu_attribute=(1 5 15)
	cpu_load=($(uptime | tr -s " " | cut -d " " -f 11-13 | tr "," " "))
    echo -e "\e[31m\t系统CPU负载信息\e[0m"
    echo -e "\e[32m================================"
    for index in ${!cpu_attribute[@]}
    do
        echo "CPU ${cpu_attribute[$index]} min平均负载为: ${cpu_load[$index]}" 
    done
    echo -e "================================\e[0m"	
}
# 获取内存相关属性信息
mem_info() {
	free_attribute=(总量 使用 空闲)
	free_info=($(free -m | grep Mem | tr -s " " | cut -d " " -f 2-4))
    echo -e "\e[31m\t系统内存负载信息\e[0m"
    echo -e "\e[32m================================"
    for index in ${!free_attribute[@]}
    do
        echo "内存 ${free_attribute[$index]} 信息为: ${free_info[$index]} M" 
    done
    echo -e "================================\e[0m"
}

# 服务的操作提示
echo -e "\e[31m---------------查看资源操作动作---------------
 1: CPU  2: MEM
-------------------------------------------"'\033[0m'
# 选择服务操作类型
while true
do
    read -p "> 请输入要查看的资源信息类型: " resource_id
    echo
    case ${resource_type[$resource_id-1]} in
       "CPU")
            cpu_info;;
        "MEM")
            mem_info;;
        *)
            echo -e "\e[31m\t请输入有效的信息类型\e[0m";;
    esac
done
脚本使用效果
[root@localhost ~]# /bin/bash function_systemctl_load.sh
---------------查看资源操作动作---------------
 1: CPU  2: MEM
-------------------------------------------
> 请输入要查看的资源信息类型: 1

        系统CPU负载信息
================================
CPU 1 min平均负载为: 0.00
CPU 5 min平均负载为: 0.01
CPU 15 min平均负载为: 0.05
================================
> 请输入要查看的资源信息类型: 2

        系统内存负载信息
================================
内存 总量 信息为: 3770 M
内存 使用 信息为: 237 M
内存 空闲 信息为: 3290 M
================================
> 请输入要查看的资源信息类型: 3

        请输入有效的信息类型
> 请输入要查看的资源信息类型: ^C
[root@localhost ~]#

环境部署

需求

定制kubernetes环境部署管理的功能脚本改造
	1 功能函数实现
	2 扩充while循环执行功能
	3 增加q退出环境功能

脚本内容

查看脚本内容
[root@localhost ~]# cat function_kubernetes_manager.sh
#!/bin/bash
# 功能:定制kubernetes环境部署管理的功能
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

# 定制数组变量
env_array=(base ha k8s_base master slave)

# 监控平台的信息提示
menu(){
    echo -e "\e[31m     欢迎使用kubernetes部署平台"
    echo -e "\e[32m-----------请选择部署阶段-----------"
    echo -e " 1: 基础环境部署"
    echo -e " 2: 高可用环境部署"
    echo -e " 3: kubernetes基础环境部署"
    echo -e " 4: 主角色环境部署"
    echo -e " 5: 从角色环境部署"
    echo -e " q: 退出"
    echo -e "----------------------------------\033[0m"
}
# 定制基础环境
os_base_func(){
    echo -e "\e[31m开始基础环境部署..."
    echo "1 执行跨主机免密码操作"
    echo "2 执行时间同步操作"
    echo "3 执行内核配置操作"
    echo -e "4 执行容器私有仓库部署操作\e[0m"
}

# 定制高可用环境
ha_func(){
    echo -e "\e[31高可用环境部署..."
    echo "1 执行高可用环境部署操作"
    echo -e "2 执行负载均衡环境部署操作\e[0m"
}

# 定制k8s基础环境
k8s_base_func(){
    echo -e "\e[31mkubernetes基础环境部署..."
    echo "1 执行证书管理操作"
    echo "2 执行etcd环境部署操作"
    echo -e "3 执行集群证书配置操作\e[0m"
}

# 定制主角色环境
master_func(){
    echo -e "\e[31m主角色环境部署..."
    echo "1 执行apiserver环境部署操作"
    echo "2 执行scheduler环境部署操作"
    echo "3 执行controller环境部署操作"
    echo "4 执行认证配置操作"
    echo "5 执行容器环境部署操作"
    echo "6 执行kubelet环境部署操作"
    echo -e "7 执行kube-proxy环境部署\e[0m"
}
# 定制从角色环境
slave_func(){
    echo -e "\e[31m主角色环境部署..."
    echo "1 执行容器环境部署操作"
    echo "2 执行kubelet环境部署操作"
    echo -e "3 执行kube-proxy环境部署\e[0m"
}
# 定制错误提示信息
usage_func(){
    echo -e "\e[31m请输入有效的功能场景标识\e[0m"
}


# 脚本内容的判断
while true
do
    # 定制业务逻辑
    menu
    read -p "请输入功能标识: " env_id
    if [ ${env_id} == "q" ];then
        exit
    else
        # 执行配套业务逻辑
        case "${env_array[$env_id-1]}" in
            "base")
                os_base_func;;
            "ha")
                ha_func;;
            "k8s_base")
                k8s_base_func;;
            "master")
                master_func;;
            "slave")
                slave_func;;
            *)
                usage_func;;
        esac
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash function_kubernetes_manager.sh
     欢迎使用kubernetes部署平台
-----------请选择部署阶段-----------
 1: 基础环境部署
 2: 高可用环境部署
 3: kubernetes基础环境部署
 4: 主角色环境部署
 5: 从角色环境部署
 q: 退出
----------------------------------
请输入功能标识: 6
请输入有效的功能场景标识
     欢迎使用kubernetes部署平台
-----------请选择部署阶段-----------
 1: 基础环境部署
 2: 高可用环境部署
 3: kubernetes基础环境部署
 4: 主角色环境部署
 5: 从角色环境部署
 q: 退出
----------------------------------
请输入功能标识: 5
主角色环境部署...
1 执行容器环境部署操作
2 执行kubelet环境部署操作
3 执行kube-proxy环境部署
     欢迎使用kubernetes部署平台
-----------请选择部署阶段-----------
 1: 基础环境部署
 2: 高可用环境部署
 3: kubernetes基础环境部署
 4: 主角色环境部署
 5: 从角色环境部署
 q: 退出
----------------------------------
请输入功能标识: q

函数进阶

函数变量

基础知识

简介

	在函数作用范围中,我们可以通过大量的变量来实现特定数据的临时存储,不仅仅可以实现脚本层面的变量可视化,还可以借助于变量本身的全局和本地的特点实现更加强大的功能场景。

变量类型

全局变量:
	默认情况下,脚本中的普通变量就是全局变量,作用范围是shell脚本的所有地方,在函数内部也可以正常使用
	而且函数内可以可以修改脚本级别的全局变量
局部变量:
	我们可以通过local语法,将变量的作用范围限制在一段代码块范围中。
	注意:脚本内无法使用local语法,仅限于函数体内

简单实践

实践1-全局变量实践

查看脚本内容
[root@localhost ~]# cat function_env_test.sh
#!/bin/bash
# 功能:全局变量实践

# 定制普通的全局变量
message="helloworld"

# 定制一个函数,提示脚本的使用方式
function Usage {
    echo "直接调用脚本的message: ${message}"
    message="function-message"
    echo "函数体重置后的message: ${message}"
}

# 定制脚本使用逻辑
while true
do
    read -p "查看变量的方式[ 1-脚本内 | 2-函数内 ]:" type
    if [ ${type} == "1" ];then
        # 直接在脚本环境使用全局变量
        echo ${message}
    elif [ ${type} == "2" ];then
        # 函数内部使用全局变量
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash function_env_test.sh
查看变量的方式[ 1-脚本内 | 2-函数内 ]:1
helloworld
查看变量的方式[ 1-脚本内 | 2-函数内 ]:2
直接调用脚本的message: helloworld
函数体重置后的message: function-message
查看变量的方式[ 1-脚本内 | 2-函数内 ]:1
function-message						# 结果显示,函数体内的变量生效了
查看变量的方式[ 1-脚本内 | 2-函数内 ]:^C
[root@localhost ~]#
结果显示:
	对于脚本的普通变量,函数内外都可以正常使用,而且函数内可以直接修改脚本级别的普通变量

实践2-本地变量实践

查看脚本内容
[root@localhost ~]# cat function_env_test2.sh
#!/bin/bash
# 功能:local定制函数级别的局部变量实践

# 定制普通变量
message="helloworld"
local local_env="local"

# 定制一个函数,提示脚本的使用方式
function Usage {
    echo "直接调用脚本的变量: ${message}-${local_env}"
    local message="function-message"
    echo "函数体重置后的变量: ${message}-${local_env}"
}

# 定制脚本使用逻辑
while true
do
    read -p "查看变量的方式[ 1-脚本内 | 2-函数内 ]:" type
    if [ ${type} == "1" ];then
        # 直接在脚本环境使用普通变量
        echo ${message}
    elif [ ${type} == "2" ];then
        # 函数内部使用普通变量
        Usage
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash function_env_test2.sh
function_env_test2.sh: 第 6 行:local: 只能在函数中使用
查看变量的方式[ 1-脚本内 | 2-函数内 ]:1
helloworld
查看变量的方式[ 1-脚本内 | 2-函数内 ]:2
直接调用脚本的变量: helloworld-
函数体重置后的变量: function-message-
查看变量的方式[ 1-脚本内 | 2-函数内 ]:1
helloworld
查看变量的方式[ 1-脚本内 | 2-函数内 ]:^C
[root@localhost ~]#
结果显示:
	local仅限于函数中使用,函数外部无法使用
	经过local限制后的环境变量,无法被函数体外进行使用

数组传递

基础知识

简介

	我们知道在shell脚本中,数组可以帮助我们做很多灵活的事情,尤其是数据的归类存储。而数组在函数的使用过程中,还是受到了一定程度的限制。这些限制主要体现在以下几个方面;
限制1:
	以变量的方式传递数组给函数,函数内部无法正常使用
限制2:
	我们只能先解析所有数组元素,然后再传递给函数,接着在函数体内部重新组合
限制3:
	函数内部以echo方式输出数组所有元素,然后再函数外部重新组合为数组

简单实践

实践1-函数无法正常接收数据元素

查看脚本内容
[root@localhost ~]# cat function_array_input.sh
#!/bin/bash
# 功能: 函数接收数组元素

# 定制功能函数
func_array(){
    echo "函数内接收的参数: $@"
}

# 定制数组变量
myarray=(aa bb cc dd ee)
echo "myarray数组的所有元素有: ${myarray[@]}"

# 数组传递给函数
func_array $myarray

脚本执行效果
[root@localhost ~]# /bin/bash function_array_input.sh
myarray数组的所有元素有: aa bb cc dd ee
函数内接收的参数: aa

结果显示:
	虽然我们传递数组给函数,但是函数无法正常接收数组

实践2-函数接收数组

查看脚本内容
[root@localhost ~]# cat function_array_input2.sh
#!/bin/bash
# 功能: 函数接收数组元素

# 定制功能函数
func_array(){
    echo "函数内接收的参数: $@"
    func_arr=($(echo $@))
    echo "函数内func_arr的数组元素有: ${func_arr[@]}"
}

# 定制数组变量
myarray=(aa bb cc dd ee)
echo "myarray数组的所有元素有: ${myarray[@]}"

# 数组解析后,将所有元素传递给函数
func_array ${myarray[@]}
脚本执行效果
[root@localhost ~]# /bin/bash function_array_input2.sh
myarray数组的所有元素有: aa bb cc dd ee
函数内接收的参数: aa bb cc dd ee
函数内func_arr的数组元素有: aa bb cc dd ee

实践3-脚本接收函数内数组

查看脚本内容
[root@localhost ~]# cat function_array_output.sh
#!/bin/bash
# 功能: 脚本接收函数内数组元素

# 定制功能函数
func_array(){
    # 函数体内构造新数组
    func_arr=($(echo $@))
    # 生成新数组
    for (( i=0; i<${#func_arr[@]}; i++ ))
    do
        newarray[$i]=$[ ${func_arr[$i]} * 3 ]
    done
    # 逐个返回数组元素
    echo ${newarray[@]}
}

# 定制数组变量
myarray=(1 2 3 4 5)
echo "myarray数组的所有元素有: ${myarray[@]}"

# 接收函数体返回的数组内容
result=($(func_array ${myarray[@]}))
echo "函数返回的result数组元素:${result[@]}"
脚本执行效果
[root@localhost ~]# /bin/bash function_array_output.sh
myarray数组的所有元素有: 1 2 3 4 5
函数返回的result数组元素:3 6 9 12 15

函数嵌套

基础知识

简介

	所谓的函数嵌套,主要是在函数间或者文件间相互使用的一种方式。它主要有三种样式:
样式1:函数间调用
	- 函数体内部调用其他的函数名
样式2:文件间调用
	- 函数体内部调用另外一个文件的函数名
	- 需要额外做一步文件source的加载动作
	注意:我们将专门提供函数的文件称为 -- 函数库
样式3:函数自调用
	- 函数体内部调用自己的函数名,将复杂的逻辑简单化

简单实践

函数间调用实践1-图形信息打印

	按照信息提示,分别打印 三角形 和 等腰梯形
        *                  *****
       * *                *******
      * * *              *********
     * * * *            ***********
    * * * * *          *************
[root@localhost ~]# cat function_drawn_graph.sh
#!/bin/bash
# 功能:打印相关图形
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

graph_type=(三角形 梯形)
# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------查看可以绘制的图形---------------"
    echo -e " 1: 三角形  2: 梯形"
    echo -e "-------------------------------------------\033[0m"
}

# 定制打印左侧空格效果
left_bland_func(){
   layer_num="$1"
   sub_num="$2"
   for m in $(seq $[${layer_num}-${sub_num}]);do
        echo -n " "
   done
}

# 打印图形的核心内容部分
kernel_character_func(){
   char_num="$1"
   char_mark="$2"
   for j in $(seq ${char_num});do
       echo -n "${char_mark}"
   done
}
# 定制打印三角形的函数
triangle_func(){
    # 接收函数传参
    layer_num=$1
    # 定制打印n层的三角形
    for i in $(seq 1 ${layer_num});do
       # 定制打印三角形左侧的空格效果
       left_bland_func ${layer_num} $i
       # 定制打印三角形核心部分
       kernel_character_func $i "* "
       # 打印完每行就换行
       echo
   done
}
# 定制梯形的功能函数
trapezium_func(){
    print_num=${layer_num}
    for i in $(seq 1 ${layer_num});do
        # 定制打印梯形左侧的空格效果
        left_bland_func ${layer_num} $i
        # 定制打印梯形核心部分
        kernel_character_func $print_num "*"
        let print_num+=2
        echo
    done
}
# 选择服务操作类型
while true;do
    menu
    read -p "> 请输入要查看的资源信息类型: " graph_id
    case ${graph_type[$graph_id-1]} in
        "三角形")
            read -p "> 请输入三角形绘制的层数: " layer_num
            triangle_func ${layer_num}
            ;;
        "梯形")
            read -p "> 请输入梯形绘制的层数: " layer_num
            # 定制打印n层的梯形
            trapezium_func ${layer_num}
            ;;
        *)
            echo -e "\e[31m\t请输入正确的绘图类型id\e[0m";;
    esac
done
脚本执行后效果
[root@localhost ~]# /bin/bash function_drawn_graph.sh
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 1
> 请输入三角形绘制的层数: 5
    *
   * *
  * * *
 * * * *
* * * * *
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 2
> 请输入梯形绘制的层数: 5
    *****
   *******
  *********
 ***********
*************
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: ^C
[root@localhost ~]#

文件间调用实践2-拆分function_drawn_graph.sh脚本

需求:拆分绘图脚本文件
	1 将脚本文件中的功能逻辑函数拆分出来以单独的文件存在
	2 脚本文件保留核心逻辑功能
创建功能函数库文件目录
[root@localhost ~]# mkdir lib

查看库文件内容
[root@localhost ~]# cat lib/drawn_func.sh
#!/bin/bash
# 功能:打印相关图形功能函数库
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------查看可以绘制的图形---------------"
    echo -e " 1: 三角形  2: 梯形"
    echo -e "-------------------------------------------\033[0m"
}

# 定制打印三角形左侧空格效果
left_bland_func(){
   layer_num="$1"
   sub_num="$2"
   for m in $(seq $[${layer_num}-${sub_num}]);do
        echo -n " "
   done
}

# 打印图形的核心内容部分
kernel_character_func(){
   char_num="$1"
   char_mark="$2"
   for j in $(seq ${char_num});do
       echo -n "${char_mark}"
   done
}
# 定制打印三角形的函数
triangle_func(){
    # 接收函数传参
    layer_num=$1
    # 定制打印n层的三角形
    for i in $(seq 1 ${layer_num});do
       # 定制打印三角形左侧的空格效果
       left_bland_func ${layer_num} $i
       # 定制打印三角形核心部分
       kernel_character_func $i "* "
       # 打印完每行就换行
       echo
   done
}
# 定制梯形的功能函数
trapezium_func(){
    print_num=${layer_num}
    for i in $(seq 1 ${layer_num});do
        # 定制打印梯形左侧的空格效果
        left_bland_func ${layer_num} $i
        # 定制打印梯形核心部分
        kernel_character_func $print_num "*"
        let print_num+=2
        echo
    done
}
查看脚本框架文件
[root@localhost ~]# cat function_drawn_graph-lib.sh
#!/bin/bash
# 功能:打印相关图形
# 版本:v0.3
# 作者:书记
# 联系:www.superopsmsb.com

# 定制数组变量
graph_type=(三角形 梯形)

# 加载功能函数库文件
source ./lib/drawn_func.sh

# 选择服务操作类型
while true;do
    menu
    read -p "> 请输入要查看的资源信息类型: " graph_id
    case ${graph_type[$graph_id-1]} in
        "三角形")
            read -p "> 请输入三角形绘制的层数: " layer_num
            triangle_func ${layer_num}
            ;;
        "梯形")
            read -p "> 请输入梯形绘制的层数: " layer_num
            # 定制打印n层的梯形
            trapezium_func ${layer_num}
            ;;
        *)
            echo -e "\e[31m\t请输入正确的绘图类型id\e[0m";;
    esac
done
脚本执行效果
[root@localhost ~]# /bin/bash function_drawn_graph-lib.sh
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: 1
> 请输入三角形绘制的层数: 5
    *
   * *
  * * *
 * * * *
* * * * *
---------------查看可以绘制的图形---------------
 1: 三角形  2: 梯形
-------------------------------------------
> 请输入要查看的资源信息类型: ^C
[root@localhost ~]#

函数自调用

简单实践

简介

	函数自调用也称函数递归,说白了就是 函数调用自身,实现数据递归能力的实现

实践-函数自调用

需求: 实现数学阶乘的实践
	示例:5的阶乘
		 完整格式:5! = 1 * 2 * 3 * 4 * 5 = 120
		 简写格式:5! = 5 * (1 * 2 * 3 * 4) = 5 * 4!
	公式: x! = x * (x-1)!
查看脚本内容
[root@localhost ~]# cat function_func_test1.sh
#!/bin/bash
# 功能:函数自调用实践

# 定制功能函数框架
self_func(){
    # 接收一个参数
    num=$1
    if [ ${num} -eq 1 ];then
        echo 1
    else
        # 定制一个临时本地变量,获取递减后的值
        local temp=$[ ${num} - 1 ]
        # 使用函数自调用方式获取内容
        local result=$(self_func $temp)
        # 格式化输出信息
        echo $[ $result * ${num} ]
    fi
}

# 检测逻辑效果
while true
do
    read -p "请输入一个您要查询的阶乘:" value
    result=$(self_func ${value})
    echo "${value}的阶乘是: ${result}"
done
脚本执行效果
[root@localhost ~]# /bin/bash function_func_test1.sh
请输入一个您要查询的阶乘:5
5的阶乘是: 120
请输入一个您要查询的阶乘:6
6的阶乘是: 720
请输入一个您要查询的阶乘:7
7的阶乘是: 5040
请输入一个您要查询的阶乘:^C
[root@localhost ~]#

案例实践

实践1-遍历制定目录下的所有文件

准备工作
[root@localhost ~]# mkdir -p dir/{softs/{nginx,tomcat},logs,server/{java,python}}
[root@localhost ~]# touch dir/softs/{nginx/nginx.conf,tomcat/server.xml}
[root@localhost ~]# touch dir/logs/user{1..3}.log
[root@localhost ~]# touch dir/server/{java/java.jar,python/python.py}
[root@localhost ~]# tree dir/
dir/
├── logs
│   ├── user1.log
│   ├── user2.log
│   └── user3.log
├── server
│   ├── java
│   │   └── java.jar
│   └── python
│       └── python.py
└── softs
    ├── nginx
    │   └── nginx.conf
    └── tomcat
        └── server.xml

7 directories, 7 files
[root@localhost ~]# cat function_scan_dir.sh
#!/bin/bash
# 功能:扫描目录下所有文件
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 定制功能函数框架
# 定制目录扫描功能函数
scan_dir() {
    # 定制临时局部功能变量
	# cur_dir 当前目录 workdir 工作目录
    local cur_dir workdir
	
	# 接收要检查的目录,进入到目录中
    workdir=$1
    cd ${workdir}
	
	# 对工作目录进行简单判断,根目录没有父目录
    if [ ${workdir} = "/" ]
    then
        cur_dir=""
    else
        cur_dir=$(pwd)
    fi
    
	# 查看当前目录下的文件列表
    for item in $(ls ${cur_dir})
    do
        # 如果文件是目录,则继续查看目录下文件
        if test -d ${item};then
            cd ${item}
            scandir ${cur_dir}/${item}
            cd ..
        # 如果文件是普通文件,则输出信息即可
        else
            echo ${cur_dir}/${item}
        fi
    done
}

# 检测逻辑效果
while true
do
    read -p "请输入一个您要查询的目录:" value
    if [ -d ${value} ]
    then
        scandir ${value}
    else
        echo "您输入的不是目录,请重新输入!"
    fi
done
脚本执行效果
[root@localhost ~]# /bin/bash function_scan_dir.sh
请输入一个您要查询的目录:dir
/root/dir/logs/user1.log
/root/dir/logs/user2.log
/root/dir/logs/user3.log
/root/dir/server/java/java.jar
/root/dir/server/python/python.py
/root/dir/softs/nginx/nginx.conf
/root/dir/softs/tomcat/server.xml
请输入一个您要查询的目录:^C
[root@localhost ~]#
结果显示:
	该脚本达到了我们需要的目录遍历效果

综合练习

案例解读

案例需求

使用shell脚本绘制一个杨辉三角

image

案例解读

image

1、每行数字左右对称,从1开始变大,然后变小为1。   
2、第n行的数字个数为n个,所有数字和为 2^(n-1)。  
3、每个数字等于上一行的左右临近两个数字之和。
4、第n行的数字依次为 1、1×(n-1)、1×(n-1)×(n-2)/2、1×(n-1)×(n-2)/2×(n-3)/3 ...   
...

脚本实践

脚本实践

查看脚本内容
[root@localhost ~]# cat yanghui_triangle.sh
#!/bin/bash
# 功能:shell定制杨辉三角功能
# 版本:v0.1
# 作者:书记
# 联系:www.superopsmsb.com

# 判断输入是否为整数
check_int(){
    # 设定数据标识
    flag=true
    read -p "请输入一个数据值: " layer_num
    # 通过在循环内部进行数据操作判断是否是数据
    while $flag;do
      expr $layer_num + 0 > /dev/null 2>&1
      if [ $? -eq 0 ]
      then
          flag=false
      else
          read -p "请输入一个数据值: " layer_num
      fi
    done
}

# 定制一个数组
declare -a num_array

check_int
# 定制杨辉三角的行数变量 row
for(( row=1; row<=layer_num; row++ ))
do
   #打印杨辉三角的左侧空白
   for k in $(seq $[$layer_num - $row])
   do
       echo -n "    "
   done
   # 定制每行的数据获取
   for(( col=1; col<=row; col++ ))
   do
       # 第n行的第1个和第n行的第n个数字为1
       if [ $col -eq 1  -o $row -eq $col ]
       then
         # 设定每行的两个边界数字为1
         num_array[$row$col]=1
       else
         # 获取上一行的两个临近数据
         let row_up=row-1  # 获取上一行的数据
         let col_up=col-1  # 获取上一行的临近数据
         # 获取当前行的数据值为 上一行临近数据的数据和
         let num_array[$row$col]=${num_array[$row_up$col_up]}+${num_array[${row_up}${col}]}
       fi
   done
   # 打印每行的数据
   for(( col=1; col<=row; col++ ))
   do
       printf "%-8s" ${num_array[$row$col]}
   done
   echo
done
脚本执行效果
[root@localhost ]# /bin/bash yanghui_triangle.sh
请输入一个数据值: 8
                            1
                        1       1
                    1       2       1
                1       3       3       1
            1       4       6       4       1
        1       5       10      10      5       1
    1       6       15      20      15      6       1
1       7       21      35      35      21      7       1

函数嵌套改造

脚本改造后内容
[root@localhost ~]# cat yanghui_triangle.sh
#!/bin/bash
# 功能:shell定制杨辉三角功能
# 版本:v0.2
# 作者:书记
# 联系:www.superopsmsb.com

# 定制一个数组
declare -a num_array

# 判断输入是否为整数
check_int(){
    # 设定数据标识
    flag=true
    read -p "请输入一个数据值(q退出): " layer_num
    [ $layer_num == "q" ] && exit
    # 通过在循环内部进行数据操作判断是否是数据
    while $flag;do
      expr $layer_num + 0 > /dev/null 2>&1
      if [ $? -eq 0 ]
      then
          flag=false
      else
          read -p "请输入一个数据值: " layer_num
      fi
    done
}

# 定制左侧空格打印逻辑
left_blank_func(){
   # 获取参数值
   layer_num=$1
   row=$2
   # 空格打印逻辑
   for k in $(seq $[$layer_num - $row])
   do
       echo -n "    "
   done
}

# 获取每行的数据值
col_num_count(){
   # 获取参数值
   row=$1
   # 数据获取逻辑   
   for(( col=1; col<=row; col++ ))
   do
       # 第n行的第1个和第n行的第n个数字为1
       if [ $col -eq 1  -o $row -eq $col ]
       then
         # 设定每行的两个边界数字为1
         num_array[$row$col]=1
       else
         # 获取上一行的两个临近数据
         let row_up=row-1  # 获取上一行的数据
         let col_up=col-1  # 获取上一行的临近数据
         # 获取当前行的数据值为 上一行临近数据的数据和
         let num_array[$row$col]=${num_array[$row_up$col_up]}+${num_array[${row_up}${col}]}
       fi
   done
}
# 每行数据打印逻辑
col_num_print(){
   # 获取参数值
   row=$1
   # 数据打印逻辑
   for(( col=1; col<=row; col++ ))
   do
       printf "%-8s" ${num_array[$row$col]}
   done
   echo
}
while true
do
  check_int
  # 定制杨辉三角的行数变量 raw
  for(( row=1; row<=layer_num; row++ ))
  do
     #打印杨辉三角的左侧空白
     left_blank_func $layer_num $row
     # 获取数据的值
     col_num_count $row
     # 打印每行的所有数据
     col_num_print $row
  done
done
[root@localhost ~]# /bin/bash yanghui_triangle.sh
请输入一个数据值(q退出): 5
                1
            1       1
        1       2       1
    1       3       3       1
1       4       6       4       1
请输入一个数据值(q退出): q
[root@localhost ~]#
posted @ 2023-01-16 22:28  aBiu--  阅读(31)  评论(0编辑  收藏  举报