第 38 课 - Shell 函数定义与使用
所属模块: Shell 基础
难度: Intermediate
标签: functions, bash
上一课: for 与 while 循环
下一课: 输入输出重定向
1. 学习目标
完成本课学习后,你将能够:
- 掌握 Shell 函数的定义与调用语法。
- 理解并正确使用函数参数(位置参数)和特殊变量。
- 掌握函数返回值的两种处理方式(
return和echo)。 - 理解函数内的变量作用域,并正确使用
local关键字。 - 学会利用函数来组织、复用和简化 Shell 脚本。
2. 核心概念
为什么需要函数?
想象一下,你在脚本中需要多次执行“检查磁盘使用率并报警”的操作。如果每次都在主流程中写一大段代码,脚本会变得冗长且难以维护。函数(Function) 就像一个小工具或子程序,它将一系列命令封装起来,起个名字。需要时,只需要“呼叫”它的名字(即调用函数),就能执行那段封装好的代码。这带来了代码复用、模块化和可读性的巨大好处。
函数定义与调用
定义函数的通用语法有两种,推荐第一种:
# 语法一(推荐)
function_name () {
# 函数体(要执行的命令)
命令1
命令2
...
}
# 语法二
function function_name {
# 函数体
命令1
命令2
...
}
调用函数非常简单,只需在脚本中使用函数名即可。
function_name # 调用函数
参数传递
函数可以像脚本一样接收参数。在函数内部,通过 $1, $2, $3, ... $N 来访问传入的第1个、第2个、第3个...第N个参数。$0 代表脚本名,但在函数中通常不使用。$# 代表传入参数的总个数。
返回值
Shell 函数有两种“返回”结果的方式:
return命令:返回一个整数状态码(0-255),通常用 0 表示成功,非 0 表示失败。我们可以通过$?来获取上一个命令(或函数)的返回状态码。echo命令:通过标准输出打印一个字符串。这是返回字符串或计算结果的常用方式。调用函数时,通常使用命令替换$( )来捕获这个输出。
变量作用域
默认情况下,函数内定义的变量是全局变量,会影响整个脚本。这可能在大型脚本中导致意外的变量覆盖。使用 local 关键字可以在函数内声明局部变量,它的作用域仅限于该函数内部及其调用的子函数。
3. 代码示例
示例1:基础函数 - 参数传递与返回值
#!/bin/bash
# 定义一个函数,用于计算两个数字的和
# 使用 echo 输出结果,而不是 return
calculate_sum () {
# 检查是否提供了两个参数
if [ $# -ne 2 ]; then
echo "Error: 请提供两个数字参数"
return 1 # 返回非0状态码表示错误
fi
local num1=$1
local num2=$2
local sum=$(( num1 + num2 ))
echo $sum # 将结果输出到标准输出
}
# 定义一个函数,用于检查数字是否为偶数
# 使用 return 返回状态码(0: 是偶数,1: 不是)
is_even () {
local num=$1
if [ $(( num % 2 )) -eq 0 ]; then
return 0 # 成功,是偶数
else
return 1 # 失败,不是偶数
fi
}
# --- 主脚本开始 ---
echo "=== 测试 calculate_sum 函数 ==="
result=$(calculate_sum 10 20) # 使用命令替换捕获函数的输出
echo "10 + 20 = $result"
echo ""
echo "=== 测试 is_even 函数 ==="
number=7
if is_even $number; then
echo "$number 是偶数"
else
echo "$number 不是偶数" # 这里会执行
fi
number=12
is_even $number
echo "函数 is_even 对数字 $number 的返回状态码是: $?"
示例2:实用函数 - 简化日志记录
#!/bin/bash
# 定义一个日志函数,自动添加时间戳和级别
log_msg () {
local level=$1
local message=$2
local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
# 使用 local 定义局部变量,避免污染全局
echo "[$timestamp] [$level] $message"
}
# 主脚本使用
log_msg "INFO" "脚本开始运行"
sleep 1 # 模拟一些操作
log_msg "WARNING" "磁盘空间使用率已达到 85%"
log_msg "INFO" "脚本执行结束"
输出将类似于:
[2023-10-27 14:30:45] [INFO] 脚本开始运行
[2023-10-27 14:30:46] [WARNING] 磁盘空间使用率已达到 85%
[2023-10-27 14:30:46] [INFO] 脚本执行结束
4. 实践练习
练习1:基本函数调用
编写一个函数 say_hello,它接受一个参数(名字),并输出问候语 “你好,[名字]!”。在主脚本中调用它两次,分别传入你的名字和“Linux”。
练习2:参数与返回值
编写一个函数 get_max,它接收两个数字作为参数,使用 echo 输出其中较大的那个数。编写主脚本调用 get_max 45 78 并打印结果 “最大的数字是:78”。
练习3:综合应用(挑战)
编写一个脚本,包含一个函数 check_service,该函数接收一个服务名(如 nginx, sshd)作为参数。
函数功能:
- 使用
systemctl is-active $service_name命令检查服务状态。 - 根据命令的返回状态码(
$?),使用log_msg函数(参考示例2)输出一条日志,格式为INFO或WARNING。 主脚本中,依次调用check_service函数检查sshd和cups服务的状态。
5. 常见错误
-
函数定义后忘记调用:定义了函数,但在脚本中没有写下调用它的语句,函数体内的代码永远不会执行。
my_function () { echo "这个不会显示" } # 下面这行不能少! my_function -
混淆
return和echo的用途:- 试图用
return返回一个字符串或计算结果(如return “hello”或return $((a+b)))是错误的,return只能返回 0-255 的整数状态码。 - 应该用
echo输出结果,然后用$(函数名)来捕获。
- 试图用
-
变量作用域污染:在函数内意外使用了全局变量,或者没有用
local声明,导致函数内部的操作影响了外部的同名变量。value=10 my_function () { value=20 # 这里修改了全局变量 value echo "函数内部 value: $value" } my_function echo "函数外部 value: $value" # 输出 20,可能不是预期行为 -
参数传递错误:调用函数时忘记传递参数,或者传递的参数顺序不对,导致函数逻辑出错。建议在函数开头对参数数量进行检查(
if [ $# -eq 0 ])。
6. 小结
- 函数是脚本组织的基石:它通过封装代码块,实现了逻辑复用、模块化和更好的可读性。
- 定义与调用:使用
function_name() { ... }定义,直接使用function_name调用。 - 参数与返回值:
- 参数通过
$1,$2…$#访问。 - 整数状态码用
return,通过$?获取。 - 字符串或复杂数据用
echo输出,通过$( )捕获。
- 参数通过
- 变量作用域:默认为全局。在函数内声明变量时,务必使用
local关键字,以避免难以调试的变量冲突问题。
掌握函数,你就拥有了编写健壮、可维护的 Shell 脚本的关键能力。在接下来的运维脚本编写中,尝试识别并封装重复的逻辑为函数吧!