人人都会AI编程

14.4 函数:定义、调用、参数、返回值、作用域

更新时间:2026-07-12

在 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 来实现(它只能返回整型状态码),而是要通过标准输出(echoprintf)将结果打印出来,然后在调用处用命令替换 $(...) 来捕获。
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 脚本的关键一步。下一节我们将学习脚本的调试技巧,让你在出错时能快速定位问题。