在 Shell 脚本中,函数就是将一组命令封装成一个可复用的逻辑块。它让脚本结构更清晰,也能避免大量重复代码。Bash 的函数机制虽然不如高级编程语言那样功能丰富,但足够应对绝大多数自动化任务。
1. 定义函数
Bash 中定义函数有两种等价的写法:
# 写法一(常用)
函数名() {
命令1
命令2
...
}
# 写法二(显式 function 关键字)
function 函数名 {
命令1
命令2
...
}
实际工作中通常使用第一种,因为它更简洁且兼容性更好。函数体内部可以包含任意 Shell 命令、条件判断、循环、变量操作等。函数必须在使用前定义,因此建议将函数定义放在脚本的开头,或者放入独立的函数库文件中,通过 source 加载。
一个简单的例子:定义一个在屏幕上输出系统当前时间和负载的函数。
show_status() {
echo "当前时间: $(date)"
echo "系统负载: $(uptime | awk -F'load average:' '{print $2}')"
}
2. 调用函数
调用函数只需要直接写出函数名,就像执行普通命令一样。
show_status # 执行函数
函数可以被多次调用。在一个函数内也可以调用另一个函数,但要注意避免无限递归。调用时不需要带括号,原因在于 Shell 的语法中,函数名直接被当作命令名来解析。如果你错误地写成 show_status(),Shell 会将其解释为又一次函数定义。
3. 传递参数
函数调用时可以传入参数,函数内部通过位置参数来获取它们:
$1、$2、$3…… 分别表示第 1 个、第 2 个、第 3 个参数;$0仍然表示脚本自身的名字,不会变成函数名;$#表示传入参数的个数;$@和$*表示所有参数的列表(用法与脚本级位置参数完全一致)。
下面是一个用参数来指定备份源目录和目标目录的示例:
backup_dir() {
src=$1
dest=$2
if [ -z "$src" ] || [ -z "$dest" ]; then
echo "用法: backup_dir <源目录> <目标目录>"
return 1
fi
echo "正在将 $src 备份到 $dest ..."
cp -a "$src" "$dest"
}
# 调用时传入两个参数
backup_dir /home/user/data /mnt/backup/data
参数在函数内部是完全独立的,与脚本的命令行参数互不干扰。你可以在函数内任意使用 shift 操作参数列表,这与脚本级别一样。
4. 返回值
Shell 函数的返回值实际上包含两层含义:
- 退出状态码:每个命令或函数都会返回一个 0~255 的整数,其中 0 表示成功,非 0 表示失败。函数使用
return语句显式返回一个状态码;如果没有return,函数的退出状态码就是其最后一条命令的退出状态码。 - 数据输出:如果需要从函数中“返回”字符串或其他数据,不能通过
return来实现(它只能返回整型状态码),而是要通过标准输出(echo、printf)将结果打印出来,然后在调用处用命令替换$(...)来捕获。
get_top_cpu_process() {
ps -eo pid,comm,%cpu --sort=-%cpu | head -2 | tail -1
}
process_info=$(get_top_cpu_process) # 捕获函数输出的字符串
echo "CPU 占用最高的进程: $process_info"
再结合退出状态码判断执行是否成功:
divide() {
if [ "$2" -eq 0 ]; then
echo "除数不能为零" >&2
return 1
fi
echo $(( $1 / $2 ))
}
result=$(divide 10 2)
if [ $? -eq 0 ]; then
echo "结果: $result"
else
echo "计算失败"
fi
注意:return 只能在函数内部使用,在脚本顶层使用会报错。习惯上用 exit 退出整个脚本,用 return 退出函数。
5. 作用域与局部变量
默认情况下,所有在函数内部定义的变量都是全局变量,函数执行结束后依然可以在脚本的其他地方访问。这在短脚本中可能很方便,但容易造成变量名冲突和难以调试的副作用。因此,推荐在函数内部使用 local 关键字将变量显式声明为局部变量。
#!/bin/bash
count=10
my_func() {
local count=5 # 局部变量,不影响外部同名变量
echo "内部 count: $count"
}
my_func
echo "外部 count: $count"
输出:
内部 count: 5
外部 count: 10
如果没有加 local,函数内对 count 的修改就会直接覆盖外部的值。在处理复杂脚本时,应当养成给所有函数内部变量添加 local 的习惯,除非你明确需要修改全局变量。
另外,局部变量的作用域仅限于定义它的函数以及该函数调用的其他函数(被调用函数可以访问调用者的局部变量?实际上 Bash 的动态作用域允许被调用函数看见调用者的局部变量,这属于高级话题,通常不建议依赖)。实际使用中,只需记住:用 local 限制在函数体内,就基本解决了最常见的作用域污染问题。
实用建议
- 将频繁使用的功能封装成函数放在脚本头部,提高可读性和复用性;
- 用
local避免变量作用域混乱; - 通过标准输出返回数据,用
return仅返回成功或失败状态; - 函数名前可以加上一个特殊前缀(如
_或fn_)以避免与系统命令重名。
理解并灵活运用函数,是编写可靠、可维护 Shell 脚本的关键一步。下一节我们将学习脚本的调试技巧,让你在出错时能快速定位问题。