【运维进阶】Shell 函数的知识与实践

Shell 函数的知识与实践

Shell 函数介绍

  • 定义方式:Shell 函数用于封装一段可重复执行的代码,有两种基本定义形式,一种是通过 "函数名 () { 命令 }",另一种是使用 "function 函数名 { 命令 }"。

  • 下面是Shell 函数的常见语法格式。

    标准写法

    bash 复制代码
    function 函数名 () {
      指令...
      return n
    }

    简化写法1:不写()

    bash 复制代码
    function 函数名 {
      指令...
      return n
    }

    简化写法2:不写function

    bash 复制代码
    函数名 () {
      指令...
      return n
    }
  • 核心作用:主要是实现代码复用,避免重复编写相同逻辑;将复杂脚本拆分为多个功能单元,提升模块化程度;通过清晰的函数名,增强脚本的可读性和可维护性。

  • 调用方法:调用时直接使用函数名,若有参数,在函数名后依次添加参数即可,无需额外的括号包裹。

  • 参数与返回值 :函数可通过$1 $2等位置参数接收外部传入的参数,通过$#获取参数数量;返回值默认是最后一条命令的退出状态,也可通过return关键字显式指定(范围为 0-255)。

  • 变量作用域 :函数内部定义的变量默认是全局的,在函数内外都能访问;若要定义仅在函数内部有效的变量,可使用local关键字。

Shell 函数的基础实践

**示例1:**hello函数

bash 复制代码
# 想象就像拍电影,得先确定演员和剧本,才能喊开始拍。
# 第一个脚本 fun1.sh 是正确的流程:
[lth@controller ~ 11:21:45]$ cat fun1.sh 
#!/bin/bash
# 先确定演员和表演内容(定义函数)
function hello () {
  echo "Hello World !"  # 演员的台词
}
# 一切准备就绪,让演员上场表演(调用函数)
hello

# 执行后演员顺利出场,说出了台词:Hello World !
[lth@controller ~ 11:21:57]$ bash fun1.sh 
Hello World !

# 第二个脚本 fun2.sh 就乱了套:
[lth@controller ~ 11:38:07]$ cat fun2.sh
#!/bin/bash
# 还没确定谁来演,就直接喊开始(先调用函数)
hello
# 这时候才确定演员和剧本(后定义函数)
function hello () {
  echo "Hello World !"
}
# 结果自然是找不到演员,报错:fun2.sh: line 2: hello: command not found(找不到叫 hello 的演员)
[lth@controller ~ 11:38:16]$ bash fun2.sh
fun2.sh: line 2: hello: command not found
# 所以 Bash 这个 "导演" 很较真,必须按 "先定角色,再让角色出场" 的顺序来~

**示例2:**调用外部函数

bash 复制代码
# 首先,第一部分是创建 "工具库" 的过程:
[lth@controller ~ 20:57:37]$ cat >> mylib << 'EOF'
function hello () {
  echo "Hello World !"
}
EOF
# 这一步就像编写了一本叫mylib的工具手册,里面定义了一个hello函数 ------ 就像记录了一套 "打招呼流程",执行它就会输出 "Hello World !"


# 然后是主程序fun3.sh的内容:
[lth@controller ~ 20:57:41]$ cat fun3.sh 
#!/bin/bash
if [ -r mylib ];then
  source mylib
else
  echo mylib is not exist 
  exit 1
fi
hello

# 当运行脚本时:
[lth@controller ~ 20:57:54]$ bash fun3.sh 
Hello World !
# 整个过程顺利完成:找到了工具库→加载了函数→成功执行→输出了结果,完美展现了 "模块化调用" 的编程思想。

**示例3:**带参数的函数

bash 复制代码
# 首先是脚本`fun4.sh`的内容:
[lth@controller ~ 21:17:30]$ cat fun4.sh 
#!/bin/bash
function print () {
  if [ "$1" == "PASS" ];then
    echo -e '\033[1;32mPASS\033[0;39m'
  elif [ "$1" == "FAIL" ];then
    echo -e '\033[1;31mFAIL\033[0;39m'
  elif [ "$1" == "DONE" ];then
    echo -e '\033[1;35mDONE\033[0;39m'
  else
    echo "Usage: print PASS|FAIL|DONE"
  fi
}
read -p "请输入你想要打印的内容:" str
print $str

# 这个脚本的工作流程就像一场互动表演:

#  首先定义了一个叫`print`的 "染色播报员" 函数,它认识三种特殊指令:
#   - 收到 "PASS" 就用绿色高亮显示(`\033[1;32m`是绿色代码)
#   - 收到 "FAIL" 就用红色高亮显示(`\033[1;31m`是红色代码)
#   - 收到 "DONE" 就用紫色高亮显示(`\033[1;35m`是紫色代码)
#   - 遇到不认识的指令,就会提示正确用法
#  然后脚本会友好地问用户:"请输入你想要打印的内容:"(`read -p`实现)
#  最后把用户输入的内容传给 "染色播报员" 去处理(`print $str`)

# 当我们运行脚本并输入 "PASS" 时:
[lth@controller ~ 21:17:58]$ bash fun4.sh 
请输入你想要打印的内容:PASS
PASS
# 此时 "播报员" 会用绿色高亮显示 "PASS"(只是这里文本无法显示颜色效果)

# 如果输入不认识的内容,比如 "hello":
[lth@controller ~ 21:19:28]$ bash fun4.sh 
请输入你想要打印的内容:hello
Usage: print PASS|FAIL|DONE
# "播报员" 就会礼貌地告诉你它能处理哪些指令,非常尽职尽责!

**示例4:**hello函数

bash 复制代码
#这段代码就像一个 "参数传递小剧场",让我们看看其中的角色和剧情:

#首先是脚本`fun5.sh`的内容:
[lth@controller ~ 21:24:40]$ cat fun5.sh 
#!/bin/bash
function print () {
  if [ "$1" == "PASS" ];then
    echo -e '\033[1;32mPASS\033[0;39m'
  elif [ "$1" == "FAIL" ];then
    echo -e '\033[1;31mFAIL\033[0;39m'
  elif [ "$1" == "DONE" ];then
    echo -e '\033[1;35mDONE\033[0;39m'
  else
    echo "Usage: $0 PASS|FAIL|DONE"
fi
}
str=$2
print $str
# 这个脚本的角色关系很有趣:

# 首先还是那个熟悉的 "染色播报员" 函数`print`,它能根据收到的指令输出不同颜色的文字
# 但这次脚本增加了 "参数传递员" 的角色:`str=$2`表示把脚本收到的第二个参数交给变量`str`
# 最后让 "播报员" 处理这个`str`变量:`print $str`

# 当我们运行脚本并传入两个参数时:
[lth@controller ~ 21:25:34]$ bash fun5.sh PASS FAIL
FAIL
# 这里的剧情是:

# 脚本收到两个 "包裹":第一个是`PASS`($1),第二个是`FAIL`($2)
# "传递员" 只取了第二个包裹`FAIL`给`str`
# "播报员" 收到`FAIL`,于是输出红色的`FAIL`

# 当我们不带参数运行时:
# 结果表明,$0 仍然使用脚本名,而非函数名。
[lth@controller ~ 21:25:51]$ bash fun5.sh 
Usage: fun5.sh PASS|FAIL|DONE
# 整个脚本展示了 shell 中参数传递的规则:函数有自己的参数(`$1`代表函数收到的第一个参数),脚本也有自己的参数(`$2`代表脚本收到的第二个参数),它们就像不同角色的 "专属邮箱",互不干扰。

函数的递归调用

示例1:求1+2+3+...+10 的和
bash 复制代码
[lth@controller bin 22:07:05]$ cat num1.sh
#!/bin/bash
# 定义了一个叫sum_out的函数,它能计算从1加到某个数的总和
function sum_out() {
  # 如果输入的数字是1,那总和自然就是1(这是递归的"终点")
  if [ $1 -eq 1 ];then
    sum=1
  else
    # 否则,总和就是当前数字加上"1到当前数字减1的总和"
    # 这里调用了自身(sum_out $[ $1 - 1 ]),就像把问题拆成更小的同类问题
    sum=$[ $1 + $(sum_out $[ $1 - 1 ] ) ]
  fi
  # 输出计算好的总和
  echo $sum
}
# 友好地让用户输入一个整数
read -p "输入一个你想计算和的整数:" num
# 调用sum_out函数,让它计算从1加到用户输入的数字的总和
sum_out $num

# 执行
[lth@controller bin 22:07:11]$ chmod +x num1.sh 
[lth@controller bin 22:07:28]$ bash num1.sh 
输入一个你想计算和的整数:9
45

这个脚本的工作方式很像剥洋葱:比如计算 1 到 5 的和,它会先问 "1 到 4 的和是多少",计算 1 到 4 的和时又会问 "1 到 3 的和是多少"... 一直问到 "1 到 1 的和是多少"(答案是 1),然后再一层层把结果加回来,最终得到 1+2+3+4+5 的总和。这种自己调用自己解决问题的方式,就是编程中很有意思的 "递归" 思想。

示例2:求1*2*3*...*10 的阶乘
bash 复制代码
[lth@controller bin 22:12:29]$ cat num2.sh
#!/bin/bash
# 这是一个计算阶乘的函数,名叫fact_out,阶乘就像数字的"连环乘法"游戏
function fact_out() {
  # 当输入的数字是1时,阶乘就是1(这是游戏的终点,1的阶乘规定为1)
  if [ $1 -eq 1 ];then
    sum=1
  else
    # 否则,这个数字的阶乘 = 它自己 × 比它小1的数字的阶乘
    # 这里自己调用自己,就像剥洋葱,一层层把问题变小
    sum=$[ $1 * $(fact_out $[ $1 - 1 ] ) ]
  fi
  # 把算好的阶乘结果"喊"出来
  echo $sum
}
# 友好地请用户输入一个整数(其实是要算这个数的阶乘,提示文字里写"和"是笔误哦)
read -p "输入一个你想计算和的整数:" num
# 调用fact_out函数,让它计算用户输入数字的阶乘
fact_out $num

# 执行
[lth@controller bin 22:12:34]$ chmod +x num2.sh 
[lth@controller bin 22:13:06]$ bash num2.sh 
输入一个你想计算和的整数:5
120

这个脚本就像一个 "阶乘计算器",比如算 5 的阶乘时,它会这样思考:

5 的阶乘 = 5 × 4 的阶乘

4 的阶乘 = 4 × 3 的阶乘

3 的阶乘 = 3 × 2 的阶乘

2 的阶乘 = 2 × 1 的阶乘

1 的阶乘 = 1

然后从最里面的 1 开始往外算:1→2→6→24→120,最后得出 5 的阶乘是 120。这种自己调用自己的 "递归" 方式,完美贴合了阶乘的数学定义呢!

示例3:fork 炸弹(太危险我就不演示了)
bash 复制代码
:(){ :|:& };:

解释如下:

bash 复制代码
:()   # 定义函数,函数名为":",即每当输入":"时就会自动调用{}内代码
{        # ":"函数起始字元
    :    # 用递归方式调用":"函数本身
    |    # 使用管道一次产生两个函数调用
    :    # 另一次递归调用的":"函数
    &    # 放后台运行,以便产生更多的子进程
}        # ":"函数终止
;        # ":"函数定义结束后将要进行的操作...
:        # 调用":"函数,"引爆"fork炸弹

fork 炸弹原因:无限制的启用新进程,直到消耗完所有计算资源。

解决办法:限制用户进程数量。

例如:限制100个进程

bash 复制代码
[lth@controller bin 22:13:16]$ ulimit -u 100
相关推荐
七夜zippoe10 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
Fcy64812 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满12 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
代码游侠12 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
Harvey90312 小时前
通过 Helm 部署 Nginx 应用的完整标准化步骤
linux·运维·nginx·k8s
珠海西格电力科技13 小时前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
释怀不想释怀14 小时前
Linux环境变量
linux·运维·服务器
zzzsde14 小时前
【Linux】进程(4):进程优先级&&调度队列
linux·运维·服务器
聆风吟º15 小时前
CANN开源项目实战指南:使用oam-tools构建自动化故障诊断与运维可观测性体系
运维·开源·自动化·cann
NPE~15 小时前
自动化工具Drissonpage 保姆级教程(含xpath语法)
运维·后端·爬虫·自动化·网络爬虫·xpath·浏览器自动化