一:为什么函数是 Shell 脚本的灵魂?
在日常运维工作中,我们经常需要编写大量重复性任务的脚本。如果你的脚本通篇都是“裸奔”的命令,没有函数封装,那只能说明你还在“脚本小白”阶段。真正专业的运维脚本,一定是模块化、可复用、易维护的——而这一切,都离不开函数。
本文将系统讲解 Shell 函数的定义、传参、返回值、变量作用域、递归调用、函数库构建等核心知识,并结合实际案例(如动态菱形输出、服务控制封装)带你写出专业级 Shell 脚本。
二、Shell 函数基础语法
1. 函数定义方式(两种等效)
bash
编辑
# 方式一:带 function 关键字
function myfunc {
echo "Hello from function"
}
# 方式二:函数名后加括号(更常用)
myfunc() {
echo "Hello from function"
}
建议:推荐使用第二种,简洁且兼容性更好。
三、函数传参与返回值
1. 函数传参(位置参数)
函数内部通过 $1, $2... 获取传入参数:
bash
编辑
sum() {
echo $(($1 + $2))
}
sum 10 20 # 输出 30
2. 函数“返回值”的两种理解
(1)使用 return:仅支持 0~255 的退出状态码
bash
编辑
db1() {
read -p "请输入数字: " value
return $((value * 2 % 256)) # 超出 255 会取模
}
db1
echo $? # 获取 return 值
⚠️ 注意:
return不是返回计算结果,而是退出状态码,类似命令的 exit code。
(2)使用 echo + 命令替换:真正返回“数据”
bash
编辑
db2() {
read -p "请输入数字: " value
echo $((value * 2))
}
result=$(db2)
echo "结果是: $result"
最佳实践:需要返回计算结果时,用
echo+$();需要表示成功/失败状态时,用return。
四、变量作用域:全局 vs 局部
Shell 中变量默认是全局的,容易造成污染。使用 local 限定函数内变量:
bash
编辑
myfun() {
local i=8
echo "函数内 i = $i"
}
i=9
myfun
echo "函数外 i = $i" # 仍为 9
规范建议:所有函数内部临时变量都应加
local,避免副作用。
五、递归:函数调用自身
案例1:阶乘计算
bash
编辑
fact() {
if [ $1 -le 1 ]; then
echo 1
else
local n=$(( $1 - 1 ))
local res=$(fact $n)
echo $(( $1 * res ))
fi
}
read -p "输入 n: " n
echo "$n! = $(fact $n)"
案例2:递归遍历目录
bash
编辑
list_files() {
local dir="$1"
local indent="$2"
for f in "$dir"/*; do
[ -e "$f" ] || continue # 防止空目录报错
basename=$(basename "$f")
if [ -d "$f" ]; then
echo "${indent}📁 $basename"
list_files "$f" " $indent"
else
echo "${indent}📄 $basename"
fi
done
}
list_files "/var/log" ""
递归虽强大,但需注意栈深度限制,避免无限递归。
六、实战:动态输出菱形(函数+参数控制)
bash
编辑
#!/bin/bash
draw_diamond() {
local n=$1
if ! [[ "$n" =~ ^[0-9]+$ ]] || [ "$n" -le 0 ]; then
echo "请输入正整数!"
return 1
fi
# 上半部分(含中间行)
for ((i=1; i<=n; i++)); do
spaces=$((n - i))
stars=$((2*i - 1))
printf "%*s" $((spaces + stars))
printf "%0.s*" $(seq 1 $stars)
echo
done
# 下半部分
for ((i=n-1; i>=1; i--)); do
spaces=$((n - i))
stars=$((2*i - 1))
printf "%*s" $((spaces + stars))
printf "%0.s*" $(seq 1 $stars)
echo
done
}
read -p "请输入菱形大小(正整数): " size
draw_diamond "$size"
效果:输入
3输出:
text
编辑
*
***
*****
***
*
七、函数库:模块化脚本开发
将通用函数抽离为库文件,实现复用。
1. 创建函数库 myfuncs.sh
bash
编辑
# myfuncs.sh
jiafa() { echo $(($1 + $2)); }
chengfa() { echo $(($1 * $2)); }
chufa() {
if [ $2 -ne 0 ]; then
echo $(($1 / $2))
else
echo "错误:除数不能为0" >&2
return 1
fi
}
2. 在脚本中引用库
bash
编辑
# test.sh
#!/bin/bash
. ./myfuncs.sh # 或 source ./myfuncs.sh
a=10; b=3
echo "加法: $(jiafa $a $b)"
echo "乘法: $(chengfa $a $b)"
echo "除法: $(chufa $a $b)"
工程化思维:把
myfuncs.sh放入/usr/local/lib/,所有脚本均可复用。
八、高级实战:封装服务控制函数(兼容 CentOS 6/7)
bash
编辑
# function.sh
servicectl_usage() {
echo "Usage: servicectl <service> <start|stop|restart|status>"
return 1
}
chk_centos_ver() {
if grep -q "CentOS.*release 7" /etc/centos-release 2>/dev/null; then
echo "7"
elif grep -q "CentOS.*release 6" /etc/centos-release 2>/dev/null; then
echo "6"
else
echo "unknown"
fi
}
servicectl() {
local service=$1
local action=$2
if [ -z "$service" ] || [ -z "$action" ]; then
servicectl_usage
return 1
fi
case $(chk_centos_ver) in
7) systemctl "$action" "${service}.service" ;;
6) service "$service" "$action" ;;
*) echo "不支持的系统版本"; return 2 ;;
esac
}
# 调用
servicectl "$1" "$2"
使用方式:
bash
编辑
./function.sh nginx start
此函数可无缝兼容新旧系统,是运维自动化的典型封装。
九、总结:写出专业 Shell 脚本的 5 个准则
- 函数化:避免重复代码,每个功能封装成函数。
- 参数化:通过参数控制行为,提升灵活性。
- 局部变量:函数内变量一律
local。 - 错误处理:检查参数合法性,合理使用
return状态码。 - 模块化:通用函数抽成库,实现跨脚本复用。
460

被折叠的 条评论
为什么被折叠?



