Shell脚本函数#

函数是Shell脚本中组织代码、提高复用性和可维护性的重要机制。本教程将详细介绍Shell中的函数定义、参数传递、返回值处理、作用域管理等内容,从基础的函数定义到高级的函数库和模块化设计。

入门#

基本函数定义#

在Shell中,函数可以通过两种方式定义:使用function关键字或直接定义函数名。

# 使用function关键字定义函数
function hello {
    echo "Hello, World!"
}

# 直接定义函数名
hello() {
    echo "Hello, World!"
}

# 调用函数
hello

# 带参数的函数
greet() {
    echo "Hello, $1!"
}

# 调用带参数的函数
greet "John"

函数参数#

Shell函数通过位置参数($1, $2, 等)接收参数,与脚本参数类似。

# 带多个参数的函数
add() {
    echo "参数1: $1"
    echo "参数2: $2"
    sum=$(( $1 + $2 ))
    echo "和: $sum"
}

# 调用带多个参数的函数
add 10 20

# 处理任意数量的参数
sum_all() {
    local sum=0
    for num in "$@"; do
        sum=$(( sum + num ))
    done
    echo "总和: $sum"
}

# 调用带任意数量参数的函数
sum_all 1 2 3 4 5

函数返回值#

Shell函数通过return语句返回退出状态码(0-255),或通过输出返回数据。

# 使用return返回退出状态码
is_positive() {
    if [ $1 -gt 0 ]; then
        return 0  # 成功
    else
        return 1  # 失败
    fi
}

# 检查返回值
is_positive 5
if [ $? -eq 0 ]; then
    echo "是正数"
else
    echo "不是正数"
fi

# 通过输出返回数据
calculate_sum() {
    local sum=0
    for num in "$@"; do
        sum=$(( sum + num ))
    done
    echo $sum  # 输出结果
}

# 捕获函数输出
result=$(calculate_sum 1 2 3 4 5)
echo "计算结果: $result"

中级#

函数作用域#

Shell中的变量默认是全局的,使用local关键字可以定义局部变量。

# 全局变量
GLOBAL_VAR="全局变量"

# 函数中的局部变量
my_function() {
    local LOCAL_VAR="局部变量"
    GLOBAL_VAR="修改后的全局变量"
    echo "函数内 - 全局变量: $GLOBAL_VAR"
    echo "函数内 - 局部变量: $LOCAL_VAR"
}

# 调用函数
my_function

# 函数外访问
 echo "函数外 - 全局变量: $GLOBAL_VAR"
echo "函数外 - 局部变量: $LOCAL_VAR"  # 不会输出,因为局部变量只在函数内可见

函数库#

将多个相关函数组织到一个文件中,形成函数库,便于在多个脚本中复用。

# 创建函数库文件: utils.sh

#!/bin/bash

# 输出带时间戳的日志
log() {
    local level=$1
    shift
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $@"
}

# 检查文件是否存在且可读
check_file() {
    local file=$1
    if [ -f "$file" ] && [ -r "$file" ]; then
        return 0
    else
        return 1
    fi
}

# 计算文件大小(字节)
file_size() {
    local file=$1
    if check_file "$file"; then
        stat -c "%s" "$file"
    else
        echo 0
    fi
}
# 在脚本中使用函数库

#!/bin/bash

# 导入函数库
source utils.sh

# 使用函数库中的函数
log "INFO" "开始执行脚本"

if check_file "data.txt"; then
    size=$(file_size "data.txt")
    log "INFO" "文件大小: $size 字节"
else
    log "ERROR" "文件不存在或不可读"
fi

log "INFO" "脚本执行完成"

高级#

高级函数特性#

递归函数#

# 递归计算阶乘
factorial() {
    local n=$1
    if [ $n -le 1 ]; then
        echo 1
    else
        local prev=$(factorial $((n-1)))
        echo $((n * prev))
    fi
}

# 调用递归函数
echo "5! = $(factorial 5)"

# 递归遍历目录
list_dir() {
    local dir=$1
    local indent=$2
    
    for item in "$dir"/*; do
        if [ -d "$item" ]; then
            echo "${indent}$(basename "$item")/"
            list_dir "$item" "${indent}  "
        elif [ -f "$item" ]; then
            echo "${indent}$(basename "$item")"
        fi
    done
}

# 调用目录遍历函数
list_dir "." ""

函数参数处理#

# 处理选项和参数
parse_args() {
    local verbose=0
    local output="output.txt"
    
    # 解析选项
    while getopts "vo:" opt; do
        case $opt in
            v)
                verbose=1
                ;;
            o)
                output=$OPTARG
                ;;
            *)
                echo "无效选项: $opt"
                return 1
                ;;
        esac
    done
    
    # 移除选项,剩下的是位置参数
    shift $((OPTIND - 1))
    
    echo "verbose: $verbose"
    echo "output: $output"
    echo "位置参数: $@"
}

# 调用带选项的函数
parse_args -v -o result.txt file1.txt file2.txt

函数返回值高级处理#

# 返回复杂数据(使用数组)
get_files() {
    local dir=$1
    local pattern=$2
    local files=()
    
    for file in "$dir"/$pattern; do
        if [ -f "$file" ]; then
            files+=("$file")
        fi
    done
    
    # 通过输出返回数组元素
    printf "%s\n" "${files[@]}"
}

# 捕获返回的数组
files=($(get_files "." "*.txt"))
echo "找到的文件:"
for file in "${files[@]}"; do
    echo "- $file"
done

# 使用关联数组返回键值对
get_system_info() {
    local info=()
    info["os"]=$(uname -s)
    info["version"]=$(uname -r)
    info["arch"]=$(uname -m)
    
    # 通过输出返回键值对
    for key in "${!info[@]}"; do
        echo "$key=${info[$key]}"
    done
}

# 捕获返回的键值对
declare -A system_info
while IFS="=" read -r key value; do
    system_info["$key"]="$value"
done < <(get_system_info)

echo "系统信息:"
for key in "${!system_info[@]}"; do
    echo "$key: ${system_info[$key]}"
done

函数作用域和变量管理#

# 全局变量和局部变量
global_var="全局"

my_func() {
    local local_var="局部"
    global_var="修改后的全局"
    
    echo "函数内: global_var=$global_var, local_var=$local_var"
}

my_func

echo "函数外: global_var=$global_var, local_var=$local_var"

# 静态变量(使用局部变量模拟)
counter() {
    # 使用local -r确保变量只初始化一次
    local -r init=0
    
    # 使用文件或环境变量存储状态
    if [ -z "$COUNTER" ]; then
        export COUNTER=$init
    fi
    
    COUNTER=$((COUNTER + 1))
    echo $COUNTER
}

# 调用计数器函数
echo "计数: $(counter)"
echo "计数: $(counter)"
echo "计数: $(counter)"

# 清除计数器
unset COUNTER

大师#

函数库和模块化设计#

创建可重用的函数库#

# 创建完整的函数库: lib.sh

#!/bin/bash

# 颜色定义
readonly COLOR_RESET="\033[0m"
readonly COLOR_RED="\033[31m"
readonly COLOR_GREEN="\033[32m"
readonly COLOR_YELLOW="\033[33m"
readonly COLOR_BLUE="\033[34m"

# 日志函数
log_info() {
    echo -e "${COLOR_BLUE}[INFO]${COLOR_RESET} $@"
}

log_success() {
    echo -e "${COLOR_GREEN}[SUCCESS]${COLOR_RESET} $@"
}

log_warning() {
    echo -e "${COLOR_YELLOW}[WARNING]${COLOR_RESET} $@"
}

log_error() {
    echo -e "${COLOR_RED}[ERROR]${COLOR_RESET} $@"
}

# 文件操作函数
file_exists() {
    [ -f "$1" ]
}

dir_exists() {
    [ -d "$1" ]
}

ensure_dir() {
    local dir=$1
    if ! dir_exists "$dir"; then
        log_info "创建目录: $dir"
        mkdir -p "$dir"
    fi
}

# 系统函数
is_root() {
    [ $(id -u) -eq 0 ]
}

get_os() {
    uname -s
}

# 网络函数
is_connected() {
    ping -c 1 -W 2 google.com > /dev/null 2>&1
}

# 字符串函数
trim() {
    local var="$1"
    var="${var#"${var%%[![:space:]]*}"}"  # 去除前导空格
    var="${var%"${var##*[![:space:]]}"}"  # 去除尾随空格
    echo "$var"
}

# 数组函数
array_contains() {
    local item=$1
    shift
    local array=($@)
    
    for element in "${array[@]}"; do
        if [ "$element" == "$item" ]; then
            return 0
        fi
    done
    
    return 1
}

模块化导入#

# 主脚本: main.sh

#!/bin/bash

# 导入函数库
source lib.sh

# 使用函数库中的函数
log_info "开始执行主脚本"

# 检查权限
if ! is_root; then
    log_warning "非root用户执行"
fi

# 检查网络连接
if is_connected; then
    log_success "网络连接正常"
else
    log_error "网络连接失败"
fi

# 检查文件
if file_exists "data.txt"; then
    log_info "数据文件存在"
else
    log_warning "数据文件不存在"
fi

# 确保输出目录存在
ensure_dir "output"

log_success "脚本执行完成"

高级函数技巧#

函数柯里化#

# 柯里化函数示例
create_greeter() {
    local greeting=$1
    
    # 返回一个函数
    greeter() {
        echo "$greeting, $1!"
    }
    
    # 通过输出返回函数定义
    declare -f greeter
}

# 创建不同的问候函数
greet_hello=$(create_greeter "Hello")
greet_hi=$(create_greeter "Hi")

# 执行返回的函数
eval "$greet_hello" "John"
eval "$greet_hi" "Jane"

函数装饰器#

# 函数装饰器示例 - 计时
with_timer() {
    local func=$1
    
    # 返回装饰后的函数
    decorated() {
        local start=$(date +%s.%N)
        "$func" "$@"
        local end=$(date +%s.%N)
        local duration=$(echo "$end - $start" | bc)
        echo "执行时间: $duration 秒"
    }
    
    declare -f decorated
}

# 原始函数
slow_func() {
    echo "执行缓慢操作..."
    sleep 1
    echo "操作完成"
}

# 装饰函数
timed_func=$(with_timer slow_func)

# 执行装饰后的函数
eval "$timed_func"

惰性加载函数#

# 惰性加载函数
lazy_load() {
    local func_name=$1
    local loader=$2
    
    # 临时函数,用于加载实际函数
    "$func_name"() {
        # 加载实际函数
        eval "$loader"
        
        # 移除临时函数定义
        unset -f "$func_name"
        
        # 重新调用函数(现在是实际函数)
        "$func_name" "$@"
    }
}

# 定义惰性加载的函数
lazy_load expensive_func "
expensive_func() {
    echo "这是实际的昂贵函数"
    # 这里可以包含复杂的初始化代码
}
"

# 首次调用时会加载
 echo "首次调用:"
expensive_func

# 后续调用直接使用加载后的函数
 echo "后续调用:"
expensive_func

无敌#

函数库设计模式#

命名空间#

# 使用前缀创建命名空间

# 网络相关函数
net_ping() {
    ping -c 1 "$1"
}

net_traceroute() {
    traceroute "$1"
}

# 文件相关函数
file_copy() {
    cp "$1" "$2"
}

file_move() {
    mv "$1" "$2"
}

# 调用带命名空间的函数
net_ping google.com
file_copy source.txt dest.txt

插件系统#

# 插件系统示例

# 插件目录
PLUGIN_DIR="./plugins"

# 加载插件
load_plugins() {
    for plugin in "$PLUGIN_DIR"/*.sh; do
        if [ -f "$plugin" ]; then
            echo "加载插件: $(basename "$plugin")"
            source "$plugin"
        fi
    done
}

# 定义插件接口
register_command() {
    local name=$1
    local func=$2
    local description=$3
    
    commands["$name"]="$func"
    command_descriptions["$name"]="$description"
}

# 初始化命令数组
declare -A commands
declare -A command_descriptions

# 加载插件
load_plugins

# 显示可用命令
echo "可用命令:"
for cmd in "${!commands[@]}"; do
    echo "- $cmd: ${command_descriptions[$cmd]}"
done

# 执行命令
if [ -n "$1" ] && [ -n "${commands[$1]}" ]; then
    shift
    "${commands[$1]}" "$@"
else
    echo "无效命令"
fi

插件示例#

# 插件: plugins/hello.sh

register_command "hello" "plugin_hello" "问候命令"

plugin_hello() {
    echo "Hello, $1!"
}

# 插件: plugins/system.sh

register_command "system" "plugin_system" "系统信息命令"

plugin_system() {
    uname -a
}

实用脚本示例#

系统管理工具#

#!/bin/bash

# 系统管理工具函数库

# 检查服务状态
check_service() {
    local service=$1
    if systemctl is-active --quiet "$service"; then
        echo "服务 $service 运行正常"
        return 0
    else
        echo "服务 $service 未运行"
        return 1
    fi
}

# 管理服务
manage_service() {
    local action=$1
    local service=$2
    
    case $action in
        start)
            sudo systemctl start "$service"
            ;;
        stop)
            sudo systemctl stop "$service"
            ;;
        restart)
            sudo systemctl restart "$service"
            ;;
        status)
            check_service "$service"
            ;;
        *)
            echo "无效操作: $action"
            return 1
            ;;
    esac
}

# 磁盘管理
check_disk() {
    local mount_point=${1:-/}
    local usage=$(df -h "$mount_point" | tail -n 1 | awk '{print $5}' | sed 's/%//')
    
    echo "挂载点 $mount_point 使用率: $usage%"
    
    if [ $usage -gt 90 ]; then
        echo "警告: 磁盘空间不足"
        return 1
    elif [ $usage -gt 70 ]; then
        echo "注意: 磁盘空间紧张"
        return 0
    else
        echo "磁盘空间正常"
        return 0
    fi
}

# 内存管理
check_memory() {
    local total=$(free -m | grep Mem | awk '{print $2}')
    local used=$(free -m | grep Mem | awk '{print $3}')
    local usage=$(( used * 100 / total ))
    
    echo "内存总量: ${total}MB"
    echo "已用内存: ${used}MB"
    echo "内存使用率: ${usage}%"
    
    if [ $usage -gt 80 ]; then
        echo "警告: 内存使用率过高"
        return 1
    else
        echo "内存使用正常"
        return 0
    fi
}

# 主菜单
show_menu() {
    echo "===== 系统管理工具 ====="
    echo "1. 检查服务状态"
    echo "2. 管理服务"
    echo "3. 检查磁盘空间"
    echo "4. 检查内存使用"
    echo "5. 退出"
    echo "===================="
}

# 主循环
while true; do
    show_menu
    read -p "请选择: " choice
    
    case $choice in
        1)
            read -p "输入服务名: " service
            check_service "$service"
            ;;
        2)
            read -p "输入操作(start/stop/restart/status): " action
            read -p "输入服务名: " service
            manage_service "$action" "$service"
            ;;
        3)
            read -p "输入挂载点(默认/): " mount_point
            check_disk "${mount_point:-/}"
            ;;
        4)
            check_memory
            ;;
        5)
            echo "再见!"
            exit 0
            ;;
        *)
            echo "无效选择"
            ;;
    esac
    
    echo "按Enter键继续..."
    read
done

总结#

函数是Shell脚本中组织代码、提高复用性和可维护性的核心机制。通过掌握函数定义、参数传递、返回值处理、作用域管理等技巧,您可以创建更加模块化、可维护的Shell脚本。从简单的工具函数到复杂的函数库和插件系统,函数为Shell脚本提供了强大的组织能力。

随着您对Shell函数的理解不断深入,您将能够编写更加专业、高效的Shell脚本,处理各种复杂的系统管理和自动化任务。记住,良好的函数设计是构建高质量Shell脚本的基础。