# Shell 函数

URL: https://caijiao.org/linux/shell/functions
Source: docs/linux/shell/functions.md
Description: 深入学习Shell函数的定义、调用、参数传递、返回值处理及高级用法，实现脚本模块化

## 1. Shell 函数基础

### 1.1 函数的概念与作用

Shell函数是一组命令的集合，被赋予一个名称，可以像执行命令一样调用。函数将相关代码组织在一起，实现代码复用，提高脚本的可读性和可维护性。使用函数是Shell脚本模块化编程的核心方式。

### 1.2 函数的定义方式

Shell提供了两种主要的函数定义语法：

```bash
# 方式1：标准语法
function function_name {
  # 函数体
  commands
}

# 方式2：简洁语法（更常用）
function_name() {
  # 函数体
  commands
}
```

示例：

```bash
# 定义一个简单的函数
greeting() {
  echo "Hello, World!"
}

# 使用function关键字定义函数
function show_date {
  echo "当前日期: $(date +"%Y-%m-%d")"
}
```

### 1.3 函数的调用

```bash
# 定义函数
greeting() {
  echo "Hello, Shell!"
}

# 调用函数
greeting

# 可以在其他命令中使用函数
result=$(greeting)
echo "函数返回: $result"

# 可以在条件语句中使用函数
if greeting; then
  echo "函数执行成功"
fi
```

### 1.4 函数的位置

在Shell脚本中，函数定义必须出现在函数调用之前：

```bash
#!/bin/bash

# 正确：先定义，后调用
function say_hello {
  echo "Hello!"
}

say_hello  # 正确

# 错误的顺序在下面的脚本中会导致问题
```

通常的做法是将所有函数定义放在脚本的开头或单独的函数库文件中。

## 2. 函数参数

### 2.1 位置参数

Shell函数可以接收命令行参数，使用`$1`, `$2`, `$3`等特殊变量来访问这些参数：

```bash
# 定义带参数的函数
greet() {
  local name=$1
  echo "Hello, $name!"
}

# 调用函数并传递参数
greet "John"
greet "Alice"
```

### 2.2 特殊参数变量

函数内部可以使用以下特殊变量来访问参数信息：

| 变量 | 描述 |
|-----|------|
| `$1`, `$2`, ... | 第一个、第二个参数，依此类推 |
| `$#` | 参数的数量 |
| `$*` | 所有参数，作为一个单词 |
| `$@` | 所有参数，每个参数作为独立的单词（推荐使用） |
| `$0` | 脚本名称（不是函数名） |

```bash
# 显示所有参数
display_args() {
  echo "参数数量: $#"
  echo "所有参数 (\$*): $*"
  echo "所有参数 (\$@): $@"
  
  # 遍历所有参数
  echo "逐个显示参数:"
  for arg in "$@"; do
    echo "- $arg"
  done
}

display_args "apple" "banana" "cherry"
```

### 2.3 参数验证与默认值

```bash
# 参数验证和默认值设置
process_file() {
  # 检查参数数量
  if [ $# -eq 0 ]; then
    echo "错误: 请提供文件路径"
    return 1
  fi
  
  local file=$1
  local mode=${2:-"read"}  # 默认模式为"read"
  
  # 检查文件是否存在
  if [ ! -f "$file" ]; then
    echo "错误: 文件 '$file' 不存在"
    return 1
  fi
  
  echo "处理文件: $file"
  echo "处理模式: $mode"
  
  # 函数主体逻辑
  if [ "$mode" = "read" ]; then
    echo "读取文件内容..."
  elif [ "$mode" = "process" ]; then
    echo "处理文件内容..."
  else
    echo "不支持的模式: $mode"
    return 1
  fi
  
  return 0
}

# 调用示例
process_file "data.txt"
process_file "report.txt" "process"
```

## 3. 返回值与退出状态

### 3.1 return语句

Shell函数使用`return`语句返回一个退出状态码（0-255之间的整数），而不是像其他编程语言那样返回任意值：

```bash
# 定义带返回值的函数
is_positive() {
  local num=$1
  
  if [ $num -gt 0 ]; then
    return 0  # 成功状态码，表示是正数
  else
    return 1  # 非零状态码，表示不是正数
  fi
}

# 调用函数并检查返回值
is_positive 5
result=$?

if [ $result -eq 0 ]; then
  echo "是正数"
else
  echo "不是正数"
fi
```

### 3.2 函数输出

对于需要返回字符串或其他数据的情况，可以使用函数的标准输出：

```bash
# 使用echo返回字符串
format_name() {
  local first=$1
  local last=$2
  echo "$last, $first"
}

# 捕获函数输出
full_name=$(format_name "John" "Doe")
echo "格式化的姓名: $full_name"
```

### 3.3 全局变量

另一种在函数间传递数据的方式是使用全局变量：

```bash
# 使用全局变量
total=0

add_to_total() {
  total=$((total + $1))
}

add_to_total 10
add_to_total 20
add_to_total 30

echo "总和: $total"
```

## 4. 变量作用域

### 4.1 全局变量

在函数外部定义的变量默认是全局的，可以在函数内部访问和修改：

```bash
# 全局变量
global_var="I am global"

access_global() {
  echo "函数内访问全局变量: $global_var"
  global_var="Modified in function"  # 修改全局变量
}

echo "函数调用前: $global_var"
access_global
echo "函数调用后: $global_var"
```

### 4.2 局部变量

使用`local`关键字在函数内部定义的变量只在函数内部可见：

```bash
local_demo() {
  local local_var="I am local"
  echo "函数内访问局部变量: $local_var"
  
  # 局部变量在嵌套函数中也可见
  inner_func() {
    echo "嵌套函数内访问局部变量: $local_var"
  }
  
  inner_func
}

local_demo
# 下面的语句会输出空行，因为local_var在函数外部不可见
echo "函数外访问局部变量: $local_var"
```

### 4.3 变量作用域最佳实践

```bash
#!/bin/bash

# 全局配置变量（使用大写字母）
CONFIG_FILE="config.txt"
LOG_LEVEL="info"

# 函数中使用局部变量
process_data() {
  # 声明局部变量
  local input_file=$1
  local output_file=$2
  local status=""
  
  # 函数逻辑
  if [ ! -f "$input_file" ]; then
    echo "错误: 输入文件不存在"
    return 1
  fi
  
  # 使用局部变量
  echo "处理 $input_file -> $output_file"
  
  # 模拟处理过程
  status="success"
  
  # 返回状态
  [ "$status" = "success" ]
}

# 主程序
main() {
  local result=0
  
  echo "使用配置文件: $CONFIG_FILE"
  process_data "data.csv" "output.csv"
  result=$?
  
  if [ $result -eq 0 ]; then
    echo "处理完成"
  else
    echo "处理失败"
  fi
}

# 调用主函数
main
```

## 5. 高级函数特性

### 5.1 函数递归

Shell函数可以递归调用自己，实现递归算法：

```bash
# 阶乘函数
factorial() {
  local n=$1
  
  if [ $n -le 1 ]; then
    echo 1
  else
    local prev=$((n - 1))
    local prev_fact=$(factorial $prev)
    echo $((n * prev_fact))
  fi
}

# 调用递归函数
for i in {1..5}; do
  echo "$i! = $(factorial $i)"
done
```

### 5.2 函数别名

可以为函数创建别名，简化调用：

```bash
# 定义函数
show_system_info() {
  echo "主机名: $(hostname)"
  echo "系统: $(uname -a)"
  echo "用户: $(whoami)"
}

# 创建别名
alias sysinfo='show_system_info'

# 使用别名
sysinfo
```

### 5.3 内联函数

对于简单的函数，可以定义为单行形式：

```bash
# 单行函数定义
square() { echo $(( $1 * $1 )); }
add() { echo $(( $1 + $2 )); }

# 使用内联函数
result=$(square 5)
echo "5的平方: $result"

sum=$(add 10 20)
echo "10 + 20 = $sum"
```

### 5.4 数组参数

函数可以接收和处理数组参数：

```bash
# 处理数组参数
process_array() {
  # 接收数组
  local -a arr=(
