【运维进阶】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
相关推荐
AI视觉网奇38 分钟前
zsh 使用笔记 命令行智能提示 bash智能
linux·运维·笔记
IT成长日记2 小时前
【自动化运维神器Ansible】Playbook调用Role详解:从入门到精通
运维·自动化·ansible·playbook·role
2401_831501732 小时前
Linux之Ansible自动化运维(二)
运维·自动化·ansible
pwj去战斗吧2 小时前
一、部署LNMP
linux·运维
Tim风声(网络工程师)3 小时前
DNS有关知识(根域名服务器、顶级域名服务器、权威域名服务器)
linux·运维·服务器
热爱跑步的恒川3 小时前
告别服务器!Amazon Lambda无服务开发实战指南
运维·服务器
you秀4 小时前
linux中的iptables的简介与常用基础用法
linux·运维·服务器
画中有画4 小时前
使用AI来实现拼多多自动化运营脚本
运维·人工智能·自动化·ai编程·rpa·自动化脚本
vvilkim8 小时前
Java主流框架全解析:从企业级开发到云原生
java·运维·云原生