函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程 。它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分
函数和shell程序区别:
Shell程序在子Shell中运行
Shell函数在当前Shell中运行。因此在当前Shell中,函数可对shell中变量进行修改

定义函数

函数由两部分组成:函数名和函数体
image.pngimage.png
删除函数:unset func_name

函数调用

函数的调用方式

  • 可在交互式环境下定义函数
  • 可将函数放在脚本文件中作为它的一部分
  • 可放在只包含函数的单独文件中

调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时创建,返回时终止

交互式环境调用函数

  1. [root@cent7 /data/script]$ dir () { ls -l / }
  2. [root@cent7 /data/script]$ dir
  3. total 36
  4. -rw-r--r--. 1 root root 0 Jul 27 18:04 bigfile
  5. lrwxrwxrwx. 1 root root 7 May 8 16:59 bin -> usr/bin
  6. dr-xr-xr-x. 5 root root 4096 Oct 7 02:30 boot
  7. #判断centOS主版本号
  8. [root@cent7 /data/script]$ judge () { sed -nr 's@^.*([0-9]+)\..*@\1@p' /etc/redhat-release ; }
  9. [root@cent7 /data/script]$ judge
  10. 7

在脚本中定义及使用函数

函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用,调用函数仅使用其函数名即可
image.png image.png

cat reset.sh 
#!/bin/bash
disable_selinux(){
 sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
 setenforce 0
 echo "SElinux已禁用,重新启动后才可生效"
}
disable_firewall(){
 systemctl disable --now firewalld &> /dev/null
 echo "防火墙已禁用"
}
set_ps1() {
 echo "PS1='\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]'" > /etc/profile.d/reset.sh
 echo "提示符已修改成功,请重新登录生效"
}
set_eth(){
 sed -i.bak  '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub
 grub2-mkconfig -o /boot/grub2/grub.cfg &> /dev/null
 echo "网络名称已修改成功,请重新启动才能生效"
}
PS3="请选择相应的编号(1-6): "
MENU='
禁用SELinux
关防火墙
修改提示符
修改网卡名
以上全实现
退出
select M in $MENU ;do
case $REPLY in
1)
 disable_selinux
 ;;
2)
 disable_firewall
 ;;
3)
 set_ps1
 ;;
4) 
 set_eth
 ;;
5) 
   disable_selinux
 disable_firewall
 set_ps1
 set_eth
 ;;
6)
 break
 ;;
*)
 echo "请输入正确的数字"
esac
done

使用函数文件

可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入shell,再进行调用函数
文件名可任意选取,但最好与相关任务有某种联系,例如:functions。一旦函数文件载入shell,就可以在命令行或脚本中调用。可以使用delcare -fset命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数
若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件
实现函数文件的过程:
1. 创建函数文件,只存放函数的定义
2. 在shell脚本或交互式shell中调用函数文件,格式如下:. filenamesource filename
image.pngimage.png

[root@centos8 script40]#cat reset.sh 
#!/bin/bash
. /etc/init.d/functions
disable_selinux(){
 sed -i.bak 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
 action "SElinux已禁用,重新启动后才可生效"
}
disable_firewall(){
 systemctl disable --now firewalld &> /dev/null
 action "防火墙已禁用"
}
set_ps1() {
 echo "PS1='\[\e[1;35m\][\u@\h \W]\\$\[\e[0m\]'" > /etc/profile.d/reset.sh
 action "提示符已修改成功,请重新登录生效"
}
set_eth(){
 sed -i.bak  '/GRUB_CMDLINE_LINUX=/s#"$# net.ifnames=0"#' /etc/default/grub
 grub2-mkconfig -o /boot/grub2/grub.cfg &> /dev/null
 action "网络名称已修改成功,请重新启动才能生效"
}
PS3="请选择相应的编号(1-6): "

函数返回值

函数的执行结果返回值:

  • 使用echo等命令进行输出
  • 函数体中调用命令的输出结果

函数的退出状态码:

  • 默认取决于函数中执行的最后一条命令的退出状态码
  • 自定义退出状态码,其格式为:

return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回

环境函数

类拟于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数
定义环境函数:export -f function_name
declare -xf function_name
查看环境函数: export -f
declare -xf

函数参数

函数可以接受参数:

  • 传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1 arg2 ...
  • 在函数体中当中,可使用$1, $2, …调用这些参数;还可以使用$@, $*, $#等特殊变量
    #实现进度条功能
    [root@centos8 ~]#cat progress_chart.sh
    #!/bin/bash
    function print_chars()
    {
      # 传入的第一个参数指定要打印的字符串
     local char="$1"
      # 传入的第二个参数指定要打印多少次指定的字符串
     local number="$2"
     local c
      for ((c = 0; c < number; ++c)); do
         printf "$char"
      done
    }
    COLOR=32
    declare -i end=50
    for ((i = 1; i <= end; ++i)); do
     printf "\e[1;${COLOR}m\e[80D["
     print_chars "#" $i
     print_chars " " $((end - i))
     printf "] %3d%%\e[0m" $((i * 2))
      sleep 0.1s
    done
    echo
    

    函数变量

    变量作用域:
    普通变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
    环境变量:当前shell和子shell有效
    本地变量:函数的生命周期;函数结束时变量被自动销毁
    注意:
    如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量。由于普通变量和局部变量会冲突,建议在函数中只使用本地变量
    在函数中定义本地变量的方法:local NAME=VALUE

    函数递归

    函数递归:函数直接或间接调用自身,注意递归层数,可能会陷入死循环
    ```bash [root@centos8 ~]#func () { let i++;echo $i;echo “run func”; func; } #不合理 [root@centos8 ~]#func

阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语,一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0和1的阶乘为1,自然数n的阶乘写作n! n!=1×2×3×…×n 阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n n!=n(n-1)(n-2)…1 n(n-1)! = n(n-1)(n-2)!

!/bin/bash

# fact() { if [ $1 -eq 0 -o $1 -eq 1 ]; then echo 1 else echo $[$1*$(fact $[$1-1])] fi } fact $1

fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程<br />序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源<br />参考:[https://en.wikipedia.org/wiki/Fork_bomb](https://en.wikipedia.org/wiki/Fork_bomb)<br />函数实现:`:(){ :|:& };:`<br />               `bomb() { bomb | bomb & }; bomb`
```bash
#脚本实现
cat Bomb.sh
#!/bin/bash
./$0|./$0&

练习

练习
1. 编写函数,实现OS的版本判断
2. 编写函数,实现取出当前系统eth0的IP地址
3. 编写函数,实现打印绿色OK和红色FAILED
4. 编写函数,实现判断是否无位置参数,如无参数,提示错误
5. 编写函数,实现两个数字做为参数,返回最大值
6. 编写服务脚本/root/bin/testsrv.sh,完成如下要求
(1) 脚本可接受参数:start, stop, restart, status
(2) 如果参数非此四者之一,提示使用格式后报错退出
(3) 如是start:则创建/var/lock/subsys/SCRIPT_NAME, 并显示“启动成功”考虑:如果事先已经启动过一次,该如何处理?
(4) 如是stop:则删除/var/lock/subsys/SCRIPT_NAME, 并显示“停止完成”考虑:如果事先已然停止过了,该如何处理?
(5) 如是restart,则先stop, 再start 考虑:如果本来没有start,如何处理?
(6) 如是status, 则如果/var/lock/subsys/SCRIPT_NAME文件存在,则显示“SCRIPT_NAME is running…”,如/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“SCRIPT_NAME is stopped…”
(7)在所有模式下禁止启动该服务,可用chkconfig 和 service命令管理说明:SCRIPT_NAME为当前脚本名
7. 编写脚本/root/bin/copycmd.sh
(1) 提示用户输入一个可执行命令名称
(2) 获取此命令所依赖到的所有库文件列表
(3) 复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下 如:/bin/bash ==> /mnt/sysroot/bin/bash
/usr/bin/passwd ==> /mnt/sysroot/usr/bin/passwd
(4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下: 如:/lib64/ld-linux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2
(5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述功能;直到用户输入quit退出
8. 斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2),利用函数求n阶斐波那契数列
9. 汉诺塔(又称河内塔)问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤