Shell 函数详解
Shell 函数详解
函数基础:定义与调用
1.1 函数定义格式
Shell 函数有两种标准定义格式,核心是 “将重复执行的命令序列封装为一个命名模块”,两种格式功能完全一致,仅语法风格不同
格式类型 | 语法结构 | 说明 |
---|---|---|
function 关键字型 | bash function 函数名 { 命令序列 } 或 bash function 函数名() { 命令序列 } | 显式用 function 声明,可读性强,推荐初学者使用;() 可加可不加。 |
简化括号型 | bash 函数名() { 命令序列 } | 省略 function ,仅用 () 标识函数,语法更简洁,是实际开发中的主流写法。 |
示例:定义一个 “打印欢迎信息” 的函数
[root@syf ~]# vim y.sh
#!/bin/bash
function welcome(){echo "================="echo "欢迎使用Shell函数"echo "================="
}
hello() {echo "Hello! 当前时间:$(date +%H:%M:%S)"
}
welcome
hello
~
执行结果:
[root@syf ~]# ./y.sh
=================
欢迎使用Shell函数
=================
Hello! 当前时间:19:34:11
1.2 函数调用规则
- 调用时机:函数必须 “先定义,后调用”—— 脚本中函数定义需放在调用代码之前,否则会报错 “命令未找到”。
- 调用方式:直接输入函数名(如
welcome
),无需加()
(与其他编程语言不同)。 - 参数传递:调用时可跟参数(如
sum 10 20
),函数内部通过$1
、$2
… 读取(类似脚本的位置参数)。
函数返回值:两种核心方式
Shell 函数的 “返回值” 有两种场景:退出状态码(用 return) 和 实际结果(用 echo),需根据需求选择,避免混淆
2.1 rturn
:返回退出状态码(0-255)
- 作用:标识函数执行结果的 “成功 / 失败”,或返回小范围整数(0-255),脚本中用
$?
读取 - 规则:
return
后的值必须在 0-255 之间,超出则自动对 256 取余(如 257 → 1,250 → 244,256 → 0)$?
仅保存 “最后一条命令 / 函数” 的退出状态码,需在函数调用后立即读取,否则会被后续命令覆盖- 0 表示 “执行成功”,非 0 表示 “执行失败”(遵循 Shell 命令的通用约定)
示例:用 return
返回整数的 2 倍(验证范围限制)
[root@syf ~]# vim e.sh
#!/bin/bash
function sy {read -p "请输入任意一个整数值:" nreturn $[$n*2]
}
sy
echo "$?"
~
执行结果(验证范围):
[root@syf ~]# ./e.sh # 输入 40 → 40*2=80(在0-255内)
请输入任意一个整数值:40 请输入一个整数值:40
80 函数返回值($?):80
[root@syf ~]# ./e.sh # 输入 250 → 250*2=500 → 500%256=244
请输入任意一个整数值:250 请输入一个整数值:250
244 函数返回值($?):244
[root@syf ~]# ./e.sh # 输入 256 → 256*2=512 → 512%256=0
请输入任意一个整数值:256 请输入一个整数值:256
0 函数返回值($?):0
2.2 echo
:返回实际结果(推荐)
- 作用:当需要返回 “超出 0-255 的整数”“字符串” 或 “复杂计算结果” 时,用
echo
输出结果,脚本中用 命令替换($(函数名) 或函数名
) 读取 - 优势:无范围限制,支持任意类型的结果返回,是实际开发中传递函数结果的主流方式
示例:用 echo
返回两数之和(无范围限制)
[root@syf ~]# vim u.sh
#!/bin/bash
sum1 () {sum=$[ $1 + $2 ]echo $sum
}
sum1 $1 $2
~
执行结果:
[root@syf ~]# ./u.sh 100 200
300
2.3 关键区别总结
返回方式 | 适用场景 | 范围限制 | 读取方式 |
---|---|---|---|
return | 标识成功 / 失败、返回小整数 | 0-255 | $? (调用后立即读取) |
echo | 返回任意结果(整数 / 字符串) | 无限制 | 命令替换($(函数名) ) |
函数传参与变量作用域
3.1 函数传参:用位置参数传递
Shell 函数不支持 “显式参数列表”(如 add(a,b)
),需通过 位置参数 传递:
- 函数调用时,参数跟在函数名后(如
sum 10 20
) - 函数内部,用
$1
读取第一个参数,$2
读取第二个参数,$#
读取参数个数,$*
读取所有参数
示例:传递两个参数,计算和与积
[root@syf ~]# vim w.sh
#!/bin/bash
sum1 () {sum=$[ $1 + $2 ]echo $sum
}
sum1 $1 $2
~
执行结果:
[root@syf ~]# ./w.sh 20 30
50
[root@syf ~]# ./w.sh 1 6
7
3.2 变量作用域:局部变量与全局变量
Shell 脚本中变量默认是 全局变量(整个脚本可见,包括函数内部);若需限制变量仅在函数内生效,需用 local
关键字声明为 局部变量
3.2.1 局部变量:local
关键字
- 语法:
local 变量名=值
(必须在函数内部声明)。 - 特性:仅在当前函数内可见,函数外无法访问;若与全局变量同名,函数内优先使用局部变量(屏蔽全局变量)。
示例 1:基础局部变量
[root@syf ~]# vim o.sh
#!/bin/bash
abc () {echo "函数内的未经过local的变量i值$i"local ii=6echo "函数内的变量i值是$i"
}
i=9
abc
echo "函数外面的变量i值是$i"~
执行结果:
[root@syf ~]# ./o.sh
函数内的未经过local的变量i值9
函数内的变量i值是6
函数外面的变量i值是9
local的进击:
#!/bin/bash
abc () {echo "inside1 $i"let i++local ii=8echo "inside2 $i"
}
i=9
abc
echo "outside $i"
~
执行结果:
[root@syf ~]# ./o.sh
inside1 9
inside2 8
outside 10
3.3 注意事项
- 局部变量声明位置:
local
必须在函数内部声明,且建议在函数开头声明,避免 “先使用后声明” 的逻辑混乱。 - 参数与变量区分:函数内的
$1
、$2
是 “函数参数”,不是 “脚本参数”—— 脚本参数在函数外通过$1
读取,函数内的$1
仅对应函数调用时传递的参数。
函数进阶:递归与函数库
4.1 递归函数:函数调用自身
递归是 “函数调用自身” 的编程技巧,核心是 明确终止条件(避免无限递归),适用于 “分治问题”(如阶乘、斐波那契数列、目录遍历)
示例:递归计算阶乘(n! = n × (n-1) × … × 1)
[root@syf ~]# vim a.sh
#!/bin/bash
function cy() {if [ $1 -eq 1 ];thenecho 1elselocal temp=$[ $1 - 1 ]local result=`cy $temp`echo $[ result * $1 ]fi
}
read -p "输入一个值:" vaule
result=`cy $vaule`
echo "阶乘的值为: $result"
~
执行结果:
[root@syf ~]# ./a.sh
输入一个值:5
阶乘的值为: 120 # 5! = 5×4×3×2×1=120
[root@syf ~]# ./a.sh
输入一个值:3
阶乘的值为: 6 # 3! = 3×2×1=6
递归关键原则
- 必须有终止条件:如示例中
$1 -eq 1
时直接返回 1,否则会无限递归导致脚本崩溃(栈溢出) - 问题规模递减:每次递归调用时,参数需 “缩小问题规模”(如
n→n-1
),确保最终触发终止条件
4.2 函数库:封装复用函数
当多个脚本需要使用相同的函数时,可将这些函数集中到一个 “函数库文件” 中,其他脚本通过 source
或 .
命令调用函数库,实现 “一次定义,多次复用”
步骤 1:创建函数库文件(如 b.sh
)
封装常用的数学运算函数(加、减、乘、除):
[root@syf ~]# vim b.sh
#!/bin/bash
#ee.sh函数库
jia() {result=$[ $1 + $2 ]echo "$result"
}jian() {result=$[ $1 - $2 ]echo "$result"
}cheng() {result=$[ $1 * $2 ]echo "$result"
}chu() {if [ $2 -ne 0 ];thenresult=$[ $1 / $2 ]echo "$result"elseecho "除法中分母不能为0"fi
}
步骤 2:调用函数库(如 b.sh
)
通过 source 函数库路径
或 . 函数库路径
加载函数库,然后调用其中的函数:
[root@syf ~]# vim c.sh
#!/bin/bash
. /root/b.shread -p "请输入第一个数字:" n
read -p "请输入第二个数字:" mresult1=`jia $n $m`
result2=`jian $n $m`
result3=`cheng $n $m`
result4=`chu $n $m`echo "两数之和为:$result1"
echo "两数之差为:$result2"
echo "两数之乘为:$result3"
echo "两数之除为:$result4"
~
步骤 3:执行脚本
[root@syf ~]# chmod +x c.sh
[root@syf ~]# ./c.sh
[root@syf ~]# vim c.sh
[root@syf ~]# ./c.sh
请输入第一个数字:10
请输入第二个数字:3
两数之和为:13
两数之差为:7
两数之乘为:30
两数之除为:3
[root@syf ~]# ./c.sh
请输入第一个数字:10
请输入第二个数字:0
两数之和为:10
两数之差为:10
两数之乘为:0
两数之除为:除法中分母不能为0
函数库使用注意事项
- 路径问题:加载函数库时,推荐用 绝对路径(如
/root/math_lib.sh
),避免脚本在不同目录执行时找不到函数库 - 权限问题:函数库文件无需加执行权限(
chmod +x
),因它是被source
加载到当前 Shell 环境,不是作为独立脚本执行 - 命名冲突:若调用脚本中的函数与函数库中的函数同名,会优先使用 “后定义” 的函数(建议函数名加前缀,如
math_add
,避免冲突)
总结
知识点 | 核心要点 |
---|---|
函数定义 | 两种格式:function 名() { ... } 或 名() { ... } ;先定义后调用 |
返回值 | return 返退出状态码(0-255),echo 返实际结果(无限制,推荐) |
传参 | 用位置参数 $1 、$2 传递;函数内参数与脚本参数独立 |
变量作用域 | 默认全局,local 声明局部变量(仅函数内生效) |
递归 | 需有终止条件 + 问题规模递减;适用于分治问题(如阶乘) |
函数库 | 集中封装复用函数,用 source 或 . 加载;推荐绝对路径,避免权限问题 |
Shell 函数的核心价值是 “代码复用” 和 “逻辑拆解”—— 将重复代码封装为函数,可减少冗余、提升维护效率;将复杂逻辑拆分为多个函数,可让脚本结构更清晰。实际开发中,结合 “函数库” 和 “递归”,可应对大部分 Shell 脚本的模块化需求