Shell 函数详解

Shell函数简介

函数是Shell脚本编程中实现代码重用和模块化的核心工具。通过函数,我们可以将复杂的脚本分解为小的、可管理的部分,提高代码的可读性、可维护性和重用性。

代码重用

将常用功能封装为函数,避免代码重复。

模块化

将复杂任务分解为小的、专注的函数。

可读性

通过函数名表达意图,提高代码可读性。

维护性

修改功能只需修改一个地方。

调试

可以单独测试和调试每个函数。

递归

函数可以调用自身,解决复杂问题。

函数的重要性

函数是编写高质量Shell脚本的基础。通过合理的函数设计,我们可以创建模块化、可维护、可重用的脚本,大大提高开发效率和代码质量。函数还有助于团队协作,因为不同的开发者可以负责不同的函数模块。

函数定义和调用

学习如何定义函数和调用函数。

函数执行流程
主程序执行
函数调用
函数执行
返回主程序
继续执行

函数定义的基本语法

方法1: 使用function关键字
function 函数名 {
  # 函数体
  # 可以包含任意Shell命令
}
方法2: 使用圆括号
函数名() {
  # 函数体
  # 可以包含任意Shell命令
}
方法3: 组合写法
function 函数名() {
  # 函数体
  # 可以包含任意Shell命令
}
function_basic.sh
#!/bin/bash

# 函数定义和调用示例

echo "=== 基本函数定义 ==="

# 方法1: 使用function关键字
function say_hello {
echo "Hello, World!"
}

# 方法2: 使用圆括号
greet_user() {
local name="$1"
echo "Hello, $name!"
}

# 方法3: 组合写法
function show_date() {
echo "当前日期: $(date)"
}

# 调用函数
echo "调用 say_hello 函数:"
say_hello

echo -e "\n调用 greet_user 函数:"
greet_user "Alice"

echo -e "\n调用 show_date 函数:"
show_date

echo -e "\n=== 函数执行顺序 ==="
# 函数可以在定义前调用(在Bash中)
early_call

function early_call {
echo "这个函数在调用之后才定义"
}

echo -e "\n=== 函数重复定义 ==="
# 后面的定义会覆盖前面的定义
duplicate_func() {
echo "第一个定义"
}

duplicate_func() {
echo "第二个定义(覆盖第一个)"
}

duplicate_func

echo -e "\n=== 函数调用其他函数 ==="
function inner_function {
echo "这是内部函数"
}

function outer_function {
echo "这是外部函数,正在调用内部函数:"
inner_function
}

outer_function

echo -e "\n=== 检查函数是否存在 ==="
# 检查函数是否定义
if declare -f non_existent_function > /dev/null; then
echo "函数 non_existent_function 存在"
else
echo "函数 non_existent_function 不存在"
fi

if declare -f say_hello > /dev/null; then
echo "函数 say_hello 存在"
fi

echo -e "\n=== 列出所有函数 ==="
# 显示当前定义的所有函数
echo "当前定义的函数:"
declare -F | cut -d' ' -f3

echo -e "\n=== 函数作为命令 ==="
# 函数可以像普通命令一样使用
create_dir() {
local dir_name="$1"
if [ -z "$dir_name" ]; then
    echo "错误: 请提供目录名"
    return 1
fi

if mkdir -p "$dir_name"; then
    echo "目录创建成功: $dir_name"
else
    echo "目录创建失败: $dir_name"
    return 1
fi
}

# 使用函数
create_dir "test_directory"
create_dir ""  # 这会触发错误

echo -e "\n=== 函数中的局部变量 ==="
global_var="我是全局变量"

function test_scope {
local local_var="我是局部变量"
echo "在函数内: $local_var"
echo "在函数内访问全局变量: $global_var"
}

test_scope
echo "在函数外访问局部变量: ${local_var:-未定义}"
echo "在函数外访问全局变量: $global_var"

echo -e "\n=== 删除函数 ==="
# 使用unset删除函数
function temporary_function {
echo "这是一个临时函数"
}

echo "删除前:"
temporary_function

unset -f temporary_function

echo "删除后:"
if ! declare -f temporary_function > /dev/null; then
echo "函数已被删除"
fi
函数定义最佳实践:
  • 使用有意义的函数名,反映函数的功能
  • 在脚本开头集中定义所有函数,或在单独的文件中定义
  • 使用local关键字定义局部变量
  • 为函数添加注释说明其用途、参数和返回值
  • 保持函数功能单一,每个函数只做一件事

函数参数

学习如何向函数传递参数和处理参数。

函数参数的特殊变量
变量 说明 示例
$1, $2, ... 第1、2...个参数 echo "第一个参数: $1"
$# 参数个数 echo "参数个数: $#"
$@ 所有参数(单独单词) for arg in "$@"; do
$* 所有参数(单个单词) echo "所有参数: $*"
$0 函数名(在函数内) echo "函数名: $0"

函数参数处理示例

function_parameters.sh
#!/bin/bash

# 函数参数处理示例

echo "=== 基本参数处理 ==="

# 简单参数函数
show_params() {
echo "函数名: $0"
echo "参数个数: $#"
echo "所有参数: $@"

if [ $# -gt 0 ]; then
    echo "第一个参数: $1"
    echo "第二个参数: ${2:-未提供第二个参数}"
else
    echo "没有提供参数"
fi
}

echo "调用 show_params:"
show_params
echo -e "\n调用 show_params arg1 arg2 arg3:"
show_params "arg1" "arg2" "arg3"

echo -e "\n=== 参数验证 ==="
# 带参数验证的函数
validate_params() {
if [ $# -lt 2 ]; then
    echo "错误: 需要至少2个参数,但只提供了 $# 个"
    echo "用法: $0 参数1 参数2 [参数3...]"
    return 1
fi

local required="$1"
local optional="${2:-默认值}"

echo "必需参数: $required"
echo "可选参数: $optional"

# 处理剩余参数
shift 2
if [ $# -gt 0 ]; then
    echo "额外参数: $@"
fi
}

echo "测试参数验证:"
validate_params
validate_params "必需参数"
validate_params "必需参数" "可选参数" "额外1" "额外2"

echo -e "\n=== 使用shift处理参数 ==="
# 使用shift处理多个参数
process_args() {
echo "开始处理参数..."
local count=1

while [ $# -gt 0 ]; do
    echo "参数 $count: $1"
    ((count++))
    shift
done

echo "参数处理完成"
}

echo "调用 process_args:"
process_args "苹果" "香蕉" "橙子" "葡萄"

echo -e "\n=== 命名参数模拟 ==="
# 模拟命名参数(使用选项)
named_params() {
local name=""
local age=""
local verbose=false

# 解析选项
while [[ $# -gt 0 ]]; do
    case $1 in
        -n|--name)
            name="$2"
            shift 2
            ;;
        -a|--age)
            age="$2"
            shift 2
            ;;
        -v|--verbose)
            verbose=true
            shift
            ;;
        *)
            echo "未知选项: $1"
            return 1
            ;;
    esac
done

echo "名字: ${name:-未提供}"
echo "年龄: ${age:-未提供}"
if [ "$verbose" = true ]; then
    echo "详细模式: 开启"
fi
}

echo "测试命名参数:"
named_params --name "Alice" --age 25 --verbose
named_params -n "Bob" -a 30

echo -e "\n=== 参数默认值 ==="
# 使用默认参数值
create_file() {
local filename="${1:-default.txt}"
local content="${2:-默认内容}"

echo "$content" > "$filename"
echo "文件创建成功: $filename"
}

echo "测试默认参数:"
create_file
create_file "custom.txt"
create_file "message.txt" "这是自定义内容"

echo -e "\n=== 数组作为参数 ==="
# 处理数组参数
process_array() {
local array_name="$1"
shift
local array=("$@")

echo "数组名: $array_name"
echo "数组元素:"
for element in "${array[@]}"; do
    echo "  - $element"
done
echo "数组长度: ${#array[@]}"
}

fruits=("苹果" "香蕉" "橙子")
echo "测试数组参数:"
process_array "水果" "${fruits[@]}"

echo -e "\n=== 参数引用 ==="
# 通过引用修改参数(使用eval)
increment() {
local var_name="$1"
local current_value=${!var_name}
local new_value=$((current_value + 1))
eval "$var_name=$new_value"
}

counter=5
echo "修改前: counter=$counter"
increment "counter"
echo "修改后: counter=$counter"

echo -e "\n=== 参数类型检查 ==="
# 参数类型验证
validate_number() {
local input="$1"
local param_name="$2"

if ! [[ "$input" =~ ^-?[0-9]+$ ]]; then
    echo "错误: $param_name 必须是整数,但得到: $input"
    return 1
fi
}

calculate_sum() {
validate_number "$1" "第一个参数" || return 1
validate_number "$2" "第二个参数" || return 1

local sum=$(($1 + $2))
echo "和: $1 + $2 = $sum"
}

echo "测试参数类型检查:"
calculate_sum 10 20
calculate_sum 10 "abc"
函数参数最佳实践:
  • 总是验证参数的数量和类型
  • 为可选参数提供合理的默认值
  • 使用shift处理多个参数
  • 对包含空格的参数使用引号
  • 使用描述性的参数名或选项名
  • 在函数开头进行参数验证和错误处理

函数返回值

学习如何处理函数的返回值。

函数返回值的几种方式
方法 说明 适用场景
return 返回退出状态码(0-255) 表示成功/失败
echo 输出到标准输出 返回字符串或数据
全局变量 修改全局变量 返回多个值或复杂数据
命令替换 使用$(function) 捕获函数输出

函数返回值处理示例

return_values.sh
#!/bin/bash

# 函数返回值处理示例

echo "=== 使用return返回状态码 ==="

# 返回成功/失败状态
file_exists() {
local filename="$1"

if [ -f "$filename" ]; then
    return 0  # 成功
else
    return 1  # 失败
fi
}

# 测试文件存在性
echo "测试文件存在性:"
if file_exists "/etc/passwd"; then
echo "文件存在"
else
echo "文件不存在"
fi

if file_exists "/nonexistent/file"; then
echo "文件存在"
else
echo "文件不存在"
fi

# 检查退出状态码
file_exists "/etc/passwd"
echo "退出状态码: $?"

file_exists "/nonexistent/file"
echo "退出状态码: $?"

echo -e "\n=== 使用echo返回数据 ==="

# 返回字符串数据
get_greeting() {
local name="$1"
echo "Hello, $name!"
}

# 捕获函数输出
message=$(get_greeting "Alice")
echo "捕获的消息: $message"

# 直接使用函数输出
echo "直接使用: $(get_greeting "Bob")"

echo -e "\n=== 返回数值计算结果 ==="

# 返回数值计算的结果
calculate_area() {
local radius="$1"
local pi=3.14159
local area=$(echo "$pi * $radius * $radius" | bc)
echo "$area"
}

circle_area=$(calculate_area 5)
echo "半径为5的圆面积: $circle_area"

echo -e "\n=== 返回多个值 ==="

# 方法1: 返回多个值(使用echo和分割)
get_user_info() {
local username="$1"
# 模拟从数据库获取信息
echo "Alice:25:Engineer:London"
}

# 处理多个返回值
user_data=$(get_user_info "Alice")
IFS=':' read -r name age job city <<< "$user_data"
echo "姓名: $name"
echo "年龄: $age"
echo "职业: $job"
echo "城市: $city"

# 方法2: 使用全局变量
global_result=""

set_global_result() {
global_result="这是全局结果"
}

set_global_result
echo "全局变量结果: $global_result"

echo -e "\n=== 返回数组 ==="

# 返回数组
get_fruits() {
local fruits=("apple" "banana" "orange" "grape")
printf "%s\n" "${fruits[@]}"
}

# 捕获数组
fruits_array=($(get_fruits))
echo "水果数组:"
for fruit in "${fruits_array[@]}"; do
echo "  - $fruit"
done

echo -e "\n=== 复杂的返回值处理 ==="

# 返回结构化的数据
process_data() {
local input="$1"

# 处理数据...
local processed="${input^^}"  # 转换为大写
local length=${#input}
local words=$(echo "$input" | wc -w)

# 返回多个值
echo "$processed"
echo "$length"
echo "$words"
}

# 读取多个返回值
{
read -r processed_data
read -r data_length
read -r word_count
} < <(process_data "hello world")

echo "处理后的数据: $processed_data"
echo "数据长度: $data_length"
echo "单词数: $word_count"

echo -e "\n=== 错误处理和返回值 ==="

# 带错误处理的函数
safe_division() {
local dividend="$1"
local divisor="$2"

# 参数验证
if ! [[ "$dividend" =~ ^-?[0-9]+$ ]] || ! [[ "$divisor" =~ ^-?[0-9]+$ ]]; then
    echo "错误: 参数必须是数字" >&2
    return 1
fi

if [ "$divisor" -eq 0 ]; then
    echo "错误: 除数不能为零" >&2
    return 1
fi

local result=$((dividend / divisor))
echo "$result"
return 0
}

echo "测试安全除法:"
division_result=$(safe_division 10 2)
if [ $? -eq 0 ]; then
echo "除法结果: $division_result"
else
echo "除法失败"
fi

error_result=$(safe_division 10 0)
if [ $? -eq 0 ]; then
echo "除法结果: $error_result"
else
echo "除法失败"
fi

echo -e "\n=== 使用函数链 ==="

# 函数链:一个函数的输出作为另一个函数的输入
double() {
local num="$1"
echo $((num * 2))
}

add_five() {
local num="$1"
echo $((num + 5))
}

# 函数组合
result=$(add_five $(double 10))
echo "double(10) + 5 = $result"

# 更复杂的链
result=$(double $(add_five $(double 5)))
echo "double(add_five(double(5))) = $result"

echo -e "\n=== 返回布尔值 ==="

# 返回布尔值(使用退出状态)
is_even() {
local number="$1"

if [ $((number % 2)) -eq 0 ]; then
    return 0  # true
else
    return 1  # false
fi
}

# 测试布尔函数
for i in {1..5}; do
if is_even "$i"; then
    echo "$i 是偶数"
else
    echo "$i 是奇数"
fi
done
返回值最佳实践:
  • 使用return 0表示成功,非0表示失败
  • 使用echo返回数据,使用return返回状态
  • 错误信息输出到标准错误>&2;
  • 为复杂数据使用合适的格式(如JSON、CSV)
  • 在调用函数后检查退出状态码
  • 使用描述性的返回值

变量作用域

理解Shell函数中的变量作用域规则。

变量作用域类型
作用域 定义方式 可见范围
全局变量 var=value 整个脚本
局部变量 local var=value 当前函数
环境变量 export var=value 当前进程及子进程
只读变量 readonly var=value 整个脚本(不可修改)

变量作用域示例

variable_scope.sh
#!/bin/bash

# 变量作用域示例

echo "=== 全局变量和局部变量 ==="

# 全局变量
global_var="我是全局变量"

function test_scope {
# 局部变量
local local_var="我是局部变量"

echo "在函数内部:"
echo "  全局变量: $global_var"
echo "  局部变量: $local_var"

# 修改全局变量(没有local关键字)
global_var="在函数内修改的全局变量"

# 创建新的全局变量(不推荐)
new_global="在函数内创建的新全局变量"
}

echo "函数调用前:"
echo "  全局变量: $global_var"
echo "  局部变量: ${local_var:-未定义}"
echo "  新全局变量: ${new_global:-未定义}"

test_scope

echo -e "\n函数调用后:"
echo "  全局变量: $global_var"
echo "  局部变量: ${local_var:-未定义}"
echo "  新全局变量: ${new_global:-未定义}"

echo -e "\n=== 局部变量覆盖 ==="

var="外部变量"

function shadow_test {
local var="局部变量"
echo "在函数内: var = $var"
}

echo "变量覆盖测试:"
echo "函数外部: var = $var"
shadow_test
echo "函数外部: var = $var"

echo -e "\n=== 数组作用域 ==="

# 全局数组
global_array=("全局1" "全局2")

function array_scope {
# 局部数组
local local_array=("局部1" "局部2" "局部3")

echo "在函数内部:"
echo "  全局数组: ${global_array[@]}"
echo "  局部数组: ${local_array[@]}"

# 修改全局数组
global_array+=("在函数内添加")
}

echo "数组测试:"
array_scope
echo -e "\n在函数外部:"
echo "  全局数组: ${global_array[@]}"
echo "  局部数组: ${local_array[@]}"

echo -e "\n=== 环境变量 ==="

# 设置环境变量
export ENV_VAR="我是环境变量"

function check_env {
echo "在函数内环境变量: $ENV_VAR"

# 修改环境变量(只在当前进程有效)
export ENV_VAR="在函数内修改的环境变量"
}

echo "环境变量测试:"
echo "函数调用前: $ENV_VAR"
check_env
echo "函数调用后: $ENV_VAR"

echo -e "\n=== 子shell中的变量 ==="

function subshell_test {
local local_var="局部变量"
echo "在函数内: local_var = $local_var"

# 在子shell中修改变量
(
    echo "在子shell中:"
    echo "  修改前的 local_var: $local_var"
    local_var="在子shell中修改"
    echo "  修改后的 local_var: $local_var"
)

echo "回到函数内: local_var = $local_var"
}

echo "子shell测试:"
subshell_test

echo -e "\n=== 只读变量 ==="

readonly READONLY_VAR="只读变量"

function readonly_test {
echo "只读变量: $READONLY_VAR"

# 尝试修改只读变量(会报错)
# READONLY_VAR="尝试修改"  # 取消注释会报错
}

readonly_test

echo -e "\n=== 变量引用 ==="

# 通过变量名引用另一个变量
name="Alice"
reference="name"

function reference_test {
local ref="$1"
echo "变量名: $ref"
echo "变量值: ${!ref}"  # 间接引用
}

echo "变量引用测试:"
reference_test "reference"

echo -e "\n=== 动态变量名 ==="

function create_variables {
local prefix="$1"
local count="$2"

for i in $(seq 1 "$count"); do
    local var_name="${prefix}_${i}"
    declare -g "$var_name"="值$i"  # 使用declare创建全局变量
done
}

echo "动态创建变量:"
create_variables "dynamic" 3
echo "dynamic_1 = $dynamic_1"
echo "dynamic_2 = $dynamic_2"
echo "dynamic_3 = $dynamic_3"

echo -e "\n=== 命名空间模拟 ==="

# 使用前缀模拟命名空间
function namespace_example {
local ns_prefix="mylib_"

# 在函数内定义"私有"变量
local "${ns_prefix}private_var"="私有数据"

# 定义"公有"变量(没有前缀)
local public_var="公有数据"

# 通过echo返回需要公开的数据
echo "$public_var"
}

echo "命名空间模拟:"
public_result=$(namespace_example)
echo "公有结果: $public_result"
echo "私有变量: ${mylib_private_var:-未定义}"

echo -e "\n=== 变量生命周期 ==="

function lifecycle_test {
local counter=0

# 使用子shell创建独立作用域
(
    local inner_var="内部变量"
    echo "在子shell内: inner_var = $inner_var"
)

# 子shell外的变量不受影响
echo "在函数内: inner_var = ${inner_var:-未定义}"

# 计数器示例
((counter++))
echo "计数器: $counter"
}

echo "变量生命周期测试:"
lifecycle_test
lifecycle_test
变量作用域最佳实践:
  • 总是在函数内使用local关键字定义变量
  • 避免在函数内修改全局变量
  • 使用有意义的变量名避免命名冲突
  • 对于需要在多个函数间共享的数据,考虑使用参数传递
  • 使用前缀或命名空间模式组织相关变量
  • 了解子shell对变量作用域的影响

递归函数

学习如何创建和使用递归函数。

递归函数的要素
  • 基本情况(Base Case):递归结束的条件
  • 递归情况(Recursive Case):函数调用自身的条件
  • 递归深度:函数调用自身的次数
  • 栈空间:每次递归调用都会占用栈空间

递归函数示例

recursive_functions.sh
#!/bin/bash

# 递归函数示例

echo "=== 阶乘计算 ==="

# 计算阶乘 n! = n * (n-1) * ... * 1
factorial() {
local n="$1"

# 基本情况
if [ "$n" -eq 0 ] || [ "$n" -eq 1 ]; then
    echo 1
    return
fi

# 递归情况
local prev=$(factorial $((n - 1)))
echo $((n * prev))
}

echo "阶乘测试:"
for i in {0..5}; do
result=$(factorial "$i")
echo "$i! = $result"
done

echo -e "\n=== 斐波那契数列 ==="

# 计算斐波那契数列
fibonacci() {
local n="$1"

# 基本情况
if [ "$n" -eq 0 ]; then
    echo 0
elif [ "$n" -eq 1 ]; then
    echo 1
else
    # 递归情况
    local fib1=$(fibonacci $((n - 1)))
    local fib2=$(fibonacci $((n - 2)))
    echo $((fib1 + fib2))
fi
}

echo "斐波那契数列:"
for i in {0..10}; do
result=$(fibonacci "$i")
echo "F($i) = $result"
done

echo -e "\n=== 目录树遍历 ==="

# 递归遍历目录
traverse_directory() {
local dir="$1"
local indent="$2"

# 基本情况:目录不存在
if [ ! -d "$dir" ]; then
    return
fi

# 处理当前目录
echo "${indent}📁 $(basename "$dir")/"

# 递归处理子目录和文件
for item in "$dir"/*; do
    if [ -d "$item" ]; then
        # 递归处理子目录
        traverse_directory "$item" "${indent}  "
    elif [ -f "$item" ]; then
        # 处理文件
        echo "${indent}  📄 $(basename "$item")"
    fi
done
}

echo "目录遍历测试:"
mkdir -p test_dir/{sub1,sub2,sub3/sub4}
touch test_dir/file1.txt test_dir/sub1/file2.txt test_dir/sub3/sub4/file3.txt

traverse_directory "test_dir" ""

echo -e "\n=== 二分查找 ==="

# 递归二分查找
binary_search() {
local arr=("$@")
local search_value="${arr[-1]}"
unset 'arr[${#arr[@]}-1]'  # 移除最后一个元素(搜索值)

local low=0
local high=$((${#arr[@]} - 1))

# 内部递归函数
_binary_search_recursive() {
    local low="$1"
    local high="$2"
    local search_value="$3"
    local arr=("${@:4}")

    # 基本情况:搜索范围无效
    if [ "$low" -gt "$high" ]; then
        echo "-1"  # 未找到
        return
    fi

    local mid=$(( (low + high) / 2 ))
    local mid_value="${arr[$mid]}"

    # 基本情况:找到值
    if [ "$mid_value" -eq "$search_value" ]; then
        echo "$mid"  # 返回索引
    elif [ "$mid_value" -lt "$search_value" ]; then
        # 在右半部分递归搜索
        _binary_search_recursive $((mid + 1)) "$high" "$search_value" "${arr[@]}"
    else
        # 在左半部分递归搜索
        _binary_search_recursive "$low" $((mid - 1)) "$search_value" "${arr[@]}"
    fi
}

# 调用内部递归函数
_binary_search_recursive "$low" "$high" "$search_value" "${arr[@]}"
}

echo "二分查找测试:"
sorted_array=(1 3 5 7 9 11 13 15)
echo "数组: ${sorted_array[*]}"

for target in 7 2 15 20; do
index=$(binary_search "${sorted_array[@]}" "$target")
if [ "$index" -eq "-1" ]; then
    echo "值 $target 未找到"
else
    echo "值 $target 在索引 $index 处"
fi
done

echo -e "\n=== 汉诺塔问题 ==="

# 汉诺塔递归解决方案
tower_of_hanoi() {
local n="$1"           # 盘子数量
local source="$2"      # 源柱
local destination="$3" # 目标柱
local auxiliary="$4"   # 辅助柱
local step=0

# 内部递归函数
_hanoi() {
    local n="$1"
    local source="$2"
    local destination="$3"
    local auxiliary="$4"

    # 基本情况:只有一个盘子
    if [ "$n" -eq 1 ]; then
        ((step++))
        echo "步骤 $step: 将盘子从 $source 移动到 $destination"
        return
    fi

    # 递归情况:
    # 1. 将n-1个盘子从源柱移动到辅助柱
    _hanoi $((n - 1)) "$source" "$auxiliary" "$destination"

    # 2. 将最大的盘子从源柱移动到目标柱
    ((step++))
    echo "步骤 $step: 将盘子从 $source 移动到 $destination"

    # 3. 将n-1个盘子从辅助柱移动到目标柱
    _hanoi $((n - 1)) "$auxiliary" "$destination" "$source"
}

echo "汉诺塔解决方案 (n=$n):"
_hanoi "$n" "$source" "$destination" "$auxiliary"
}

# 测试汉诺塔(小规模,避免过多输出)
tower_of_hanoi 3 "A" "C" "B"

echo -e "\n=== 递归深度控制 ==="

# 带深度控制的递归函数
limited_recursion() {
local current_depth="$1"
local max_depth="$2"

# 基本情况:达到最大深度
if [ "$current_depth" -ge "$max_depth" ]; then
    echo "达到最大深度: $max_depth"
    return
fi

echo "当前深度: $current_depth"

# 递归调用
limited_recursion $((current_depth + 1)) "$max_depth"
}

echo "递归深度控制测试:"
limited_recursion 0 5

echo -e "\n=== 尾递归优化 ==="

# 尾递归阶乘(使用累加器)
tail_recursive_factorial() {
local n="$1"
local accumulator="${2:-1}"

# 基本情况
if [ "$n" -eq 0 ] || [ "$n" -eq 1 ]; then
    echo "$accumulator"
    return
fi

# 尾递归调用
tail_recursive_factorial $((n - 1)) $((n * accumulator))
}

echo "尾递归阶乘测试:"
for i in {0..5}; do
result=$(tail_recursive_factorial "$i")
echo "$i! = $result"
done

echo -e "\n=== 递归文件搜索 ==="

# 递归搜索文件
find_files() {
local dir="$1"
local pattern="$2"

# 基本情况:目录不存在
if [ ! -d "$dir" ]; then
    return
fi

# 搜索当前目录
for file in "$dir"/*; do
    if [ -f "$file" ] && [[ "$(basename "$file")" == $pattern ]]; then
        echo "找到: $file"
    elif [ -d "$file" ]; then
        # 递归搜索子目录
        find_files "$file" "$pattern"
    fi
done
}

echo "递归文件搜索测试:"
find_files "test_dir" "*.txt"

# 清理测试文件
rm -rf test_dir
递归函数最佳实践:
  • 总是定义明确的基本情况
  • 确保每次递归调用都向基本情况前进
  • 注意递归深度,避免栈溢出
  • 对于深度递归,考虑使用迭代解决方案
  • 使用尾递归优化(如果Shell支持)
  • 为递归函数添加深度限制

综合示例

通过实际脚本示例展示Shell函数的综合应用。

系统管理工具包

使用函数创建模块化的系统管理工具。

system_toolkit.sh
#!/bin/bash

# 系统管理工具包 - 函数综合示例

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 配置
LOG_FILE="/var/log/system_toolkit.log"
BACKUP_DIR="/backup"
MAX_LOG_SIZE="10M"

# 初始化系统
init_system() {
mkdir -p "$BACKUP_DIR"
mkdir -p "$(dirname "$LOG_FILE")"
echo "$(date): 系统工具包初始化" >> "$LOG_FILE"
}

# 日志功能
log_message() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')

echo "[$timestamp] [$level] $message" >> "$LOG_FILE"

case $level in
    "ERROR") echo -e "${RED}[ERROR]${NC} $message" ;;
    "WARNING") echo -e "${YELLOW}[WARNING]${NC} $message" ;;
    "INFO") echo -e "${BLUE}[INFO]${NC} $message" ;;
    "SUCCESS") echo -e "${GREEN}[SUCCESS]${NC} $message" ;;
esac
}

# 备份管理功能
backup_system() {
local backup_name="$1"
local sources=("${@:2}")

if [ ${#sources[@]} -eq 0 ]; then
    log_message "ERROR" "没有指定备份源"
    return 1
fi

local timestamp=$(date '+%Y%m%d_%H%M%S')
local backup_path="$BACKUP_DIR/${backup_name}_${timestamp}.tar.gz"

log_message "INFO" "开始备份: $backup_name"
log_message "INFO" "备份源: ${sources[*]}"

# 创建备份
if tar -czf "$backup_path" "${sources[@]}" 2>/dev/null; then
    local size=$(du -h "$backup_path" | cut -f1)
    log_message "SUCCESS" "备份创建成功: $backup_path ($size)"
    echo "$backup_path"
else
    log_message "ERROR" "备份创建失败"
    return 1
fi
}

# 系统监控功能
system_monitor() {
local check_type="$1"

case "$check_type" in
    "cpu")
        _check_cpu_usage
        ;;
    "memory")
        _check_memory_usage
        ;;
    "disk")
        _check_disk_usage
        ;;
    "all")
        _check_cpu_usage
        _check_memory_usage
        _check_disk_usage
        ;;
    *)
        log_message "ERROR" "未知的检查类型: $check_type"
        return 1
        ;;
esac
}

_check_cpu_usage() {
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
cpu_usage=${cpu_usage%.*}

log_message "INFO" "CPU使用率: ${cpu_usage}%"

if [ "$cpu_usage" -gt 90 ]; then
    log_message "WARNING" "CPU使用率过高"
fi
}

_check_memory_usage() {
local memory_info=$(free | grep Mem)
local total_memory=$(echo $memory_info | awk '{print $2}')
local used_memory=$(echo $memory_info | awk '{print $3}')
local memory_usage=$((used_memory * 100 / total_memory))

log_message "INFO" "内存使用率: ${memory_usage}%"

if [ "$memory_usage" -gt 90 ]; then
    log_message "WARNING" "内存使用率过高"
fi
}

_check_disk_usage() {
local disk_info=$(df / | awk 'NR==2')
local total_disk=$(echo $disk_info | awk '{print $2}')
local used_disk=$(echo $disk_info | awk '{print $3}')
local disk_usage=$(echo $disk_info | awk '{print $5}' | sed 's/%//')

log_message "INFO" "磁盘使用率: ${disk_usage}%"

if [ "$disk_usage" -gt 90 ]; then
    log_message "WARNING" "磁盘使用率过高"
fi
}

# 用户管理功能
user_manager() {
local action="$1"
local username="$2"

case "$action" in
    "create")
        _create_user "$username"
        ;;
    "delete")
        _delete_user "$username"
        ;;
    "list")
        _list_users
        ;;
    *)
        log_message "ERROR" "未知的用户操作: $action"
        return 1
        ;;
esac
}

_create_user() {
local username="$1"

if [ -z "$username" ]; then
    log_message "ERROR" "请提供用户名"
    return 1
fi

if id "$username" &>/dev/null; then
    log_message "WARNING" "用户已存在: $username"
    return 1
fi

if useradd -m "$username" 2>/dev/null; then
    log_message "SUCCESS" "用户创建成功: $username"
else
    log_message "ERROR" "用户创建失败: $username"
    return 1
fi
}

_delete_user() {
local username="$1"

if [ -z "$username" ]; then
    log_message "ERROR" "请提供用户名"
    return 1
fi

if ! id "$username" &>/dev/null; then
    log_message "WARNING" "用户不存在: $username"
    return 1
fi

if userdel -r "$username" 2>/dev/null; then
    log_message "SUCCESS" "用户删除成功: $username"
else
    log_message "ERROR" "用户删除失败: $username"
    return 1
fi
}

_list_users() {
log_message "INFO" "系统用户列表:"
cut -d: -f1 /etc/passwd | sort | while read user; do
    echo "  - $user"
done
}

# 服务管理功能
service_manager() {
local service_name="$1"
local action="$2"

if [ -z "$service_name" ] || [ -z "$action" ]; then
    log_message "ERROR" "请提供服务名和操作"
    return 1
fi

case "$action" in
    "start")
        _service_action "$service_name" "start" "启动"
        ;;
    "stop")
        _service_action "$service_name" "stop" "停止"
        ;;
    "restart")
        _service_action "$service_name" "restart" "重启"
        ;;
    "status")
        _service_action "$service_name" "status" "状态"
        ;;
    *)
        log_message "ERROR" "未知的服务操作: $action"
        return 1
        ;;
esac
}

_service_action() {
local service_name="$1"
local action="$2"
local action_name="$3"

if systemctl "$action" "$service_name" &>/dev/null; then
    log_message "SUCCESS" "服务${action_name}成功: $service_name"
else
    log_message "ERROR" "服务${action_name}失败: $service_name"
    return 1
fi
}

# 网络诊断功能
network_diagnosis() {
local target="$1"

if [ -z "$target" ]; then
    target="8.8.8.8"
fi

log_message "INFO" "开始网络诊断: $target"

# Ping测试
if ping -c 3 "$target" &>/dev/null; then
    log_message "SUCCESS" "Ping测试成功: $target"
else
    log_message "ERROR" "Ping测试失败: $target"
fi

# DNS测试
if nslookup "google.com" &>/dev/null; then
    log_message "SUCCESS" "DNS解析正常"
else
    log_message "ERROR" "DNS解析失败"
fi

# 端口测试
if nc -z "google.com" 80 &>/dev/null; then
    log_message "SUCCESS" "HTTP端口(80)可达"
else
    log_message "ERROR" "HTTP端口(80)不可达"
fi
}

# 日志管理功能
log_manager() {
local action="$1"
local log_file="${2:-$LOG_FILE}"

case "$action" in
    "view")
        _view_log "$log_file"
        ;;
    "clean")
        _clean_log "$log_file"
        ;;
    "stats")
        _log_stats "$log_file"
        ;;
    *)
        log_message "ERROR" "未知的日志操作: $action"
        return 1
        ;;
esac
}

_view_log() {
local log_file="$1"

if [ ! -f "$log_file" ]; then
    log_message "ERROR" "日志文件不存在: $log_file"
    return 1
fi

echo "=== 日志内容 ==="
tail -20 "$log_file"
}

_clean_log() {
local log_file="$1"

if [ ! -f "$log_file" ]; then
    log_message "ERROR" "日志文件不存在: $log_file"
    return 1
fi

if : > "$log_file"; then
    log_message "SUCCESS" "日志文件已清空: $log_file"
else
    log_message "ERROR" "日志文件清空失败: $log_file"
    return 1
fi
}

_log_stats() {
local log_file="$1"

if [ ! -f "$log_file" ]; then
    log_message "ERROR" "日志文件不存在: $log_file"
    return 1
fi

local total_lines=$(wc -l < "$log_file")
local error_count=$(grep -c "ERROR" "$log_file")
local warning_count=$(grep -c "WARNING" "$log_file")
local info_count=$(grep -c "INFO" "$log_file")
local success_count=$(grep -c "SUCCESS" "$log_file")

echo "=== 日志统计 ==="
echo "总行数: $total_lines"
echo "错误数: $error_count"
echo "警告数: $warning_count"
echo "信息数: $info_count"
echo "成功数: $success_count"
}

# 主菜单系统
main_menu() {
while true; do
    echo -e "\n${BLUE}=== 系统管理工具包 ===${NC}"
    echo "1. 系统监控"
    echo "2. 备份管理"
    echo "3. 用户管理"
    echo "4. 服务管理"
    echo "5. 网络诊断"
    echo "6. 日志管理"
    echo "7. 退出"

    read -p "请选择操作 [1-7]: " choice

    case $choice in
        1)
            system_monitor_menu
            ;;
        2)
            backup_menu
            ;;
        3)
            user_menu
            ;;
        4)
            service_menu
            ;;
        5)
            network_menu
            ;;
        6)
            log_menu
            ;;
        7)
            echo -e "${GREEN}感谢使用系统管理工具包,再见!${NC}"
            break
            ;;
        *)
            echo -e "${RED}无效选择,请重新输入${NC}"
            ;;
    esac

    echo
    read -p "按回车键继续..."
done
}

# 子菜单函数
system_monitor_menu() {
echo -e "\n${YELLOW}=== 系统监控 ===${NC}"
echo "1. 检查CPU使用率"
echo "2. 检查内存使用率"
echo "3. 检查磁盘使用率"
echo "4. 检查所有"

read -p "请选择: " sub_choice

case $sub_choice in
    1) system_monitor "cpu" ;;
    2) system_monitor "memory" ;;
    3) system_monitor "disk" ;;
    4) system_monitor "all" ;;
    *) echo "无效选择" ;;
esac
}

backup_menu() {
echo -e "\n${YELLOW}=== 备份管理 ===${NC}"
read -p "请输入备份名称: " backup_name
read -p "请输入备份源(空格分隔): " -a sources

if backup_system "$backup_name" "${sources[@]}"; then
    echo "备份创建成功"
else
    echo "备份创建失败"
fi
}

user_menu() {
echo -e "\n${YELLOW}=== 用户管理 ===${NC}"
echo "1. 创建用户"
echo "2. 删除用户"
echo "3. 列出用户"

read -p "请选择: " sub_choice

case $sub_choice in
    1)
        read -p "请输入用户名: " username
        user_manager "create" "$username"
        ;;
    2)
        read -p "请输入用户名: " username
        user_manager "delete" "$username"
        ;;
    3)
        user_manager "list"
        ;;
    *)
        echo "无效选择"
        ;;
esac
}

service_menu() {
echo -e "\n${YELLOW}=== 服务管理 ===${NC}"
read -p "请输入服务名: " service_name
echo "1. 启动服务"
echo "2. 停止服务"
echo "3. 重启服务"
echo "4. 查看状态"

read -p "请选择操作: " sub_choice

case $sub_choice in
    1) service_manager "$service_name" "start" ;;
    2) service_manager "$service_name" "stop" ;;
    3) service_manager "$service_name" "restart" ;;
    4) service_manager "$service_name" "status" ;;
    *) echo "无效选择" ;;
esac
}

network_menu() {
echo -e "\n${YELLOW}=== 网络诊断 ===${NC}"
read -p "请输入诊断目标(默认: 8.8.8.8): " target
network_diagnosis "$target"
}

log_menu() {
echo -e "\n${YELLOW}=== 日志管理 ===${NC}"
read -p "请输入日志文件(默认: $LOG_FILE): " log_file
echo "1. 查看日志"
echo "2. 清空日志"
echo "3. 日志统计"

read -p "请选择操作: " sub_choice

case $sub_choice in
    1) log_manager "view" "$log_file" ;;
    2) log_manager "clean" "$log_file" ;;
    3) log_manager "stats" "$log_file" ;;
    *) echo "无效选择" ;;
esac
}

# 命令行参数处理
handle_arguments() {
case "$1" in
    "monitor")
        system_monitor "${2:-all}"
        ;;
    "backup")
        backup_system "$2" "${@:3}"
        ;;
    "user")
        user_manager "$2" "$3"
        ;;
    "service")
        service_manager "$2" "$3"
        ;;
    "network")
        network_diagnosis "$2"
        ;;
    "log")
        log_manager "$2" "$3"
        ;;
    "help"|"-h"|"--help")
        show_help
        ;;
    *)
        echo "未知命令: $1"
        show_help
        return 1
        ;;
esac
}

show_help() {
cat << EOF
用法: $0 [命令] [参数...]

命令:
monitor [类型]     系统监控 (cpu|memory|disk|all)
backup [名称] [源...] 创建备份
user [操作] [用户] 用户管理 (create|delete|list)
service [服务] [操作] 服务管理 (start|stop|restart|status)
network [目标]     网络诊断
log [操作] [文件]  日志管理 (view|clean|stats)
help               显示此帮助信息

示例:
$0 monitor cpu
$0 backup home /home/user
$0 user create alice
$0 service nginx restart
$0 network 8.8.8.8
$0 log view

如果没有提供命令,将启动交互式菜单。
EOF
}

# 主函数
main() {
init_system

# 如果有命令行参数,直接处理
if [ $# -gt 0 ]; then
    handle_arguments "$@"
else
    # 否则显示交互式菜单
    echo -e "${GREEN}系统管理工具包已启动${NC}"
    log_message "INFO" "应用程序启动(交互模式)"
    main_menu
    log_message "INFO" "应用程序退出"
fi
}

# 脚本入口
main "$@"
使用示例:
./system_toolkit.sh - 启动交互式菜单
./system_toolkit.sh monitor cpu - 检查CPU使用率
./system_toolkit.sh backup home /home/user - 备份home目录
./system_toolkit.sh user create alice - 创建用户alice
./system_toolkit.sh help - 显示帮助信息

函数使用最佳实践

推荐做法
  • 使用有意义的函数名,反映函数的功能
  • 保持函数功能单一,每个函数只做一件事
  • 使用local关键字定义局部变量
  • 为函数添加详细的注释说明
  • 验证函数参数并提供有用的错误信息
  • 使用一致的命名约定
避免的做法
  • 避免创建过长的函数(建议不超过50行)
  • 不要使用全局变量进行函数间通信
  • 避免函数有副作用(修改外部状态)
  • 不要忽略错误处理
  • 避免过度复杂的嵌套函数调用
  • 不要忘记在函数结束时清理资源
良好实践示例:
# 良好的函数设计
# 功能: 安全地创建目录
# 参数: $1 - 目录路径
# 返回: 0-成功, 1-失败
create_directory_safe() {
  local dir_path="$1"
  
  # 参数验证
  if [ -z "$dir_path" ]; then
    echo "错误: 目录路径不能为空" >&2
    return 1
  fi
  
  # 检查目录是否已存在
  if [ -d "$dir_path" ]; then
    echo "警告: 目录已存在: $dir_path" >&2
    return 0
  fi
  
  # 创建目录
  if mkdir -p "$dir_path"; then
    echo "目录创建成功: $dir_path"
    return 0
  else
    echo "错误: 目录创建失败: $dir_path" >&2
    return 1
  fi
}
调试技巧

使用 set -x 开启调试模式查看函数执行过程。在函数关键位置添加 echo 语句输出调试信息。使用 declare -f function_name 查看函数定义。对于复杂函数,可以单独测试每个函数。