[Shell编程] Shell 函数
如果你经常编写 Shell 脚本,会发现很多代码片段需要重复使用:比如计算某个值、检查文件状态、或者收集系统信息。如果每次都复制粘贴,不仅麻烦,还会让脚本臃肿难懂。这时候,Shell 函数就能帮你解决这些问题。
一、什么是 Shell 函数?为什么要用它?
简单来说,Shell 函数就是一组预先定义好的 Shell 命令集合,可以像 "工具" 一样被反复调用。它的优势主要有三点:
- 速度快:函数定义后会被加载到内存,调用时无需从硬盘读取,比重复写命令效率更高;
- 代码整洁:把独立功能封装成函数,让脚本结构更清晰,可读性更强;
- 避免重复:一次定义,多次调用,再也不用复制粘贴相同代码,减少出错概率。
二、Shell 函数的定义:两种方式任你选
定义 Shell 函数很简单,有两种常用语法,初学者可以任选其一:
方式 1:使用function
关键字
bash
function 函数名() {命令序列 # 函数要执行的操作[return 数值;] # 可选,用于返回退出值
}
方式 2:省略function
关键字(更简洁)
bash
函数名() {命令序列[return 数值;]
}
注意要点:
{
和}
是函数体的边界,{
可以和函数名同行,也可以单独占一行(但必须在句首);return
语句用于退出函数并返回一个整数(0-255),0 表示成功,非 0 表示失败;- 若省略
return
,函数会默认以最后一条命令的执行结果作为返回值; return
vsexit
:return
只退出当前函数,脚本继续执行;exit
直接终止整个脚本。
三、函数的调用与返回值:如何使用定义好的函数?
定义好函数后,调用方法非常简单:直接写函数名,如果需要传参数,后面跟参数即可。
1️⃣基本调用示例
bash
# 定义一个简单函数
demoFun() {echo "这是我的第一个Shell函数!"
}# 调用函数(直接写函数名)
demoFun
执行脚本后,会输出:这是我的第一个Shell函数!
2️⃣返回值的获取:用$?
查看结果
函数的返回值需要用$?
变量获取,它会保存上一次命令(或函数)的退出状态。
bash
# 定义带返回值的函数
addNum() {echo "计算两个数字的和..."read -p "请输入第一个数字:" aread -p "请输入第二个数字:" breturn $((a + b)) # 返回两数之和(注意:返回值需在0-255之间)
}# 调用函数
addNum
echo "两数之和为:$?" # 用$?获取返回值
注意:
- 若两数之和超过 255(比如 256),
$?
会返回0
(因为 256÷256=1 余 0),这是 Shell 的限制; - 若需要返回超过 255 的结果,建议直接在函数中用
echo
输出,再通过$(函数名)
捕获(见下文实战案例)。
3️⃣必须先定义,后调用!
Shell 脚本是顺序执行的,函数必须在调用前定义,否则会报错。比如下面的代码会失败:
bash
# 错误示例:先调用,后定义
demoFun # 此时函数还未定义,会报错
demoFun() {echo "Hello"
}
四、函数参数:给函数传递 "原材料"
和脚本一样,函数也可以接收参数,让功能更灵活。参数的使用规则如下:
- 调用时:
函数名 参数1 参数2 ...
(函数名和参数用空格分隔); - 函数内部:用
$1
表示第一个参数,$2
表示第二个,以此类推; - 第 10 个及以上参数:必须用
${10}
、${11}
,否则$10
会被解析为$1
+0
。
常用的参数处理特殊变量
变量 | 含义 |
---|---|
$# | 参数的总个数 |
$* | 所有参数合并为一个字符串 |
$@ | 所有参数,每个参数单独作为字符串(加引号时生效) |
$? | 上一条命令的退出状态(0 表示成功) |
实例:处理多个参数
bash
# 定义处理参数的函数
showParams() {echo "第一个参数:$1"echo "第二个参数:$2"echo "第十个参数:${10}" # 注意用${10}echo "参数总数:$#"echo "所有参数:$*"
}# 调用函数,传递11个参数
showParams 1 2 3 4 5 6 7 8 9 10 11
执行结果:
plaintext
第一个参数:1
第二个参数:2
第十个参数:10
参数总数:11
所有参数:1 2 3 4 5 6 7 8 9 10 11
五、变量作用范围:全局变量与局部变量
在 Shell 函数中,变量的作用范围需要特别注意:
- 默认全局有效:函数内部定义的变量,在函数外部也能访问;
- 局部变量:用
local
关键字定义,仅在函数内部有效,不影响外部变量。
实例:全局变量 vs 局部变量
bash
# 全局变量示例
globalDemo() {a=100 # 未用local,全局有效
}
globalDemo
echo "全局变量a:$a" # 输出100# 局部变量示例
localDemo() {local b=200 # 用local,局部有效echo "函数内的b:$b" # 输出200
}
localDemo
echo "函数外的b:$b" # 输出空(外部无法访问)
建议:在函数内部定义临时变量时,尽量用local
,避免意外修改全局变量。
六、函数递归:自己调用自己的技巧
递归是指函数调用自身的行为,常用于处理有层次结构的问题(如目录遍历、阶乘计算)。
实例 1:计算阶乘(n! = n × (n-1) × ... × 1)
bash
fact() {if [ $1 -eq 1 ]; then # 递归终止条件:1的阶乘是1echo 1elselocal temp=$(( $1 - 1 )) # 计算n-1local res=$(fact $temp) # 递归调用自身,计算(n-1)!echo $(( $1 * res )) # n! = n × (n-1)!fi
}read -p "请输入一个数字:" n
echo "$n的阶乘是:$(fact $n)"
实例 2:递归遍历目录下的所有文件
bash
listFiles() {for f in $(ls $1); do # 遍历$1目录下的内容if [ -d "$1/$f" ]; then # 若为目录echo "$f:目录"listFiles "$1/$f" # 递归遍历子目录else # 若为文件echo "$f:文件"fidone
}# 遍历/root/a目录
listFiles "/root/a"
七、函数库:让函数复用更简单
如果多个脚本需要用到相同的函数,可以把函数集中放在一个 "函数库" 脚本中,其他脚本直接调用,无需重复定义。
步骤 1:创建函数库(如func_lib.sh
)
bash
# 定义加法函数
add() {echo $(( $1 + $2 ))
}# 定义减法函数
sub() {echo $(( $1 - $2 ))
}
步骤 2:在其他脚本中调用函数库
bash
# 引入函数库(注意路径)
source ./func_lib.sh # 或 . ./func_lib.sh# 调用函数库中的函数
read -p "输入第一个数:" x
read -p "输入第二个数:" yecho "和:$(add $x $y)"
echo "差:$(sub $x $y)"
八、实战:定时生成系统性能报告
学会了 Shell 函数,我们来做一个实用工具:每小时采集一次 CPU、内存、磁盘使用情况,生成报告保存到文件中。
bash
#!/bin/bash# 定义输出目录(不存在则创建)
output_dir="/var/log/system_reports"
mkdir -p $output_dir# 1. 获取CPU使用率
get_cpu() {echo "===== CPU使用情况($(date)) ====="top -bn1 | grep "Cpu(s)" # top命令获取CPU信息echo
}# 2. 获取内存使用率
get_mem() {echo "===== 内存使用情况 ====="free -h # 简化显示内存信息echo
}# 3. 获取磁盘使用率
get_disk() {echo "===== 磁盘使用情况 ====="df -h # 简化显示磁盘信息echo
}# 4. 生成报告
generate_report() {# 报告文件名:包含日期(如report-20240809.txt)report_file="$output_dir/report-$(date +%Y%m%d).txt"# 写入报告内容get_cpu >> $report_fileget_mem >> $report_fileget_disk >> $report_fileecho "报告已生成:$report_file"
}# 主循环:每小时生成一次报告
while true; dogenerate_reportsleep 3600 # 暂停3600秒(1小时)
done
使用方法:
- 保存为
sys_report.sh
,添加执行权限:chmod +x sys_report.sh
; - 后台运行:
./sys_report.sh &
; - 查看报告:
cat /var/log/system_reports/report-20240809.txt
。
九、总结:Shell 函数学习要点
- 核心价值:组织代码、提高复用性、加快执行速度;
- 基础语法:两种定义方式,注意
return
和exit
的区别; - 参数处理:用
$1
、$2
... 传参,$#
、$*
等处理参数集合; - 变量作用域:
local
关键字定义局部变量,避免全局污染; - 进阶技巧:递归解决层次问题,函数库实现代码复用;
- 实战关键:结合实际需求封装功能,比如系统监控、日志分析等。