Shell编程从入门到精通-第六章 函数

函数是组织代码、实现代码重用的重要手段。本章将详细介绍Shell脚本中函数的定义、调用、参数传递、返回值以及变量作用域,同时深入讲解函数库的设计、回调函数、递归函数等高级用法,帮助你全面掌握Shell函数的编写技巧。


6.1 函数的定义与调用

6.1.1 函数的基本语法

Shell脚本中定义函数有两种语法格式:

bash 复制代码
# 语法1:使用 function 关键字(推荐,兼容性好)
function 函数名 {
    命令
}

# 语法2:直接使用函数名(更简洁)
函数名() {
    命令
}

6.1.2 函数的定义与调用示例

bash 复制代码
#!/bin/bash

# 定义函数(语法1)
function greet {
    echo "你好,欢迎学习Shell编程!"
}

# 定义函数(语法2)
say_hello() {
    echo "Hello, World!"
}

# 调用函数
greet
say_hello

6.1.3 函数的返回值

Shell函数支持两种返回值方式:

bash 复制代码
#!/bin/bash

# 方式1:使用 return 返回状态码(0-255)
# return 用于返回函数的退出状态,不是返回值本身
function check_file {
    if [ -f "$1" ]; then
        return 0  # 文件存在,返回成功
    else
        return 1  # 文件不存在,返回失败
    fi
}

# 调用并检查返回值
check_file "/etc/passwd"
if [ $? -eq 0 ]; then
    echo "文件存在"
else
    echo "文件不存在"
fi

# 方式2:使用 echo 返回数据
# 通过捕获函数的输出来获取返回值
function get_date {
    echo $(date +"%Y-%m-%d %H:%M:%S")
}

# 捕获函数输出
current_date=$(get_date)
echo "当前日期: $current_date"

# 方式3:返回计算结果
function add {
    result=$(($1 + $2))
    echo $result
}

sum=$(add 10 20)
echo "10 + 20 = $sum"

6.1.4 函数的递归调用

Shell支持函数递归调用:

bash 复制代码
#!/bin/bash

# 递归计算阶乘
function 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)"  # 输出:5! = 120

# 递归计算斐波那契数列
function fibonacci {
    local n=$1
    if [ $n -le 0 ]; then
        echo 0
    elif [ $n -eq 1 ]; then
        echo 1
    else
        local a=$(fibonacci $((n - 1)))
        local b=$(fibonacci $((n - 2)))
        echo $((a + b))
    fi
}

# 输出前10个斐波那契数
echo "斐波那契数列前10项:"
for i in {0..9}; do
    echo -n "$(fibonacci $i) "
done
echo

6.2 函数参数传递

6.2.1 位置参数

Shell函数使用特殊变量来访问参数:

变量 描述
$0 脚本名称(函数中仍是脚本名)
$1, $2, ... 第1、2、...个参数
${10}, ${11}, ... 第10、11、...个参数
$# 参数个数
$* 所有参数(作为单个字符串)
$@ 所有参数(作为独立字符串)
$? 上一个命令的退出状态
bash 复制代码
#!/bin/bash

# 定义带参数的函数
function greet_user {
    echo "你好,$1!"
    echo "今天是 $2"
    echo "你一共传入了 $# 个参数"
}

# 调用函数并传递参数
greet_user "张三" "2024-01-01"

# 参数编号超过9时需要使用 ${n}
function print_args {
    echo "参数1: $1"
    echo "参数2: $2"
    echo "参数10: ${10}"
}

# 传递多个参数
print_args a b c d e f g h i j k l

6.2.2 $*$@ 的区别

这两个变量在不加引号时没有区别,但在加引号时有重大区别:

bash 复制代码
#!/bin/bash

function show_args {
    echo "=== \$* 测试 ==="
    for arg in "$*"; do
        echo "参数: '$arg'"
    done
    
    echo ""
    echo "=== \$@ 测试 ==="
    for arg in "$@"; do
        echo "参数: '$arg'"
    done
}

# 调用函数
show_args "hello world" "foo bar" "test"

输出

复制代码
=== $* 测试 ===
参数: 'hello world foo bar test'

=== $@ 测试 ===
参数: 'hello world'
参数: 'foo bar'
参数: 'test'

结论

  • "$*": 将所有参数合并为一个字符串
  • "$@": 保留每个参数的独立性(推荐使用)

6.2.3 使用 shift 处理参数

shift 命令用于移动参数位置:

bash 复制代码
#!/bin/bash

# 逐个处理参数
function process_args {
    while [ $# -gt 0 ]; do
        echo "处理参数: $1"
        shift  # 向左移动参数
    done
}

# 调用函数
process_args arg1 arg2 arg3 arg4

# shift指定步长
function process_pairs {
    while [ $# -ge 2 ]; do
        echo "键: $1, 值: $2"
        shift 2  # 一次移动两个位置
    done
}

echo ""
echo "=== 处理键值对 ==="
process_pairs name=张三 age=25 city=北京

6.2.4 参数默认值与校验

bash 复制代码
#!/bin/bash

# 参数默认值
function greet {
    local name=${1:-"陌生人"}  # 如果$1为空,使用默认值
    echo "你好,$name!"
}

greet "李四"      # 输出:你好,李四!
greet             # 输出:你好,陌生人!

# 参数个数校验
function require_two_args {
    if [ $# -lt 2 ]; then
        echo "错误:需要至少2个参数,当前只有 $# 个"
        return 1
    fi
    echo "参数1: $1"
    echo "参数2: $2"
}

echo ""
require_two_args a     # 输出:错误:需要至少2个参数,当前只有1个
require_two_args a b  # 输出:参数1: a 参数2: b

# 参数类型校验
function require_number {
    if ! [[ $1 =~ ^[0-9]+$ ]]; then
        echo "错误:$1 不是有效数字"
        return 1
    fi
    echo "数字验证通过: $1"
}

echo ""
require_number "123"   # 输出:数字验证通过: 123
require_number "abc"   # 输出:错误:abc 不是有效数字

6.3 函数返回值

6.3.1 return 与 echo 的选择

bash 复制代码
#!/bin/bash

# return:返回状态码(0表示成功,1-255表示失败)
# 适合用于判断执行是否成功
function is_even {
    if [ $(($1 % 2)) -eq 0 ]; then
        return 0  # 是偶数,返回成功
    else
        return 1  # 是奇数,返回失败
    fi
}

# 调用
number=4
is_even $number
if [ $? -eq 0 ]; then
    echo "$number 是偶数"
else
    echo "$number 是奇数"
fi

# echo:返回具体数据
# 适合用于返回计算结果或其他数据
function get_square {
    echo $(($1 * $1))
}

square=$(get_square 5)
echo "5的平方是: $square"

6.3.2 综合示例:返回多种状态

bash 复制代码
#!/bin/bash

# 函数返回多个值的技巧

# 方法1:使用全局变量
function get_user_info {
    name="张三"
    age=25
    city="北京"
}

get_user_info
echo "姓名: $name, 年龄: $age, 城市: $city"

# 方法2:使用echo输出多个值,通过数组接收
function get_info {
    echo "张三|25|北京"
}

# 方式2a:使用IFS分割
IFS='|' read -r name age city <<< $(get_info)
echo "方式2a - 姓名: $name, 年龄: $age, 城市: $city"

# 方式2b:直接读取命令输出
read name age city < <(get_info)
echo "方式2b - 姓名: $name, 年龄: $age, 城市: $city"

# 方法3:使用关联数组
function get_user_array {
    declare -A info
    info[name]="李四"
    info[age]=30
    info[city]="上海"
    echo "${info[@]}"  # 或者返回数组
}

# 调用并转换为关联数组
data=($(get_user_array))
echo "方式3 - 姓名: ${data[0]}, 年龄: ${data[1]}, 城市: ${data[2]}"

# 方法4:使用nameref( Bash 4.3+)
function fill_info {
    local -n ref=$1  # nameref,引用传递
    ref[name]="王五"
    ref[age]=35
    ref[city]="广州"
}

declare -A user_info
fill_info user_info
echo "方式4 - 姓名: ${user_info[name]}, 年龄: ${user_info[age]}, 城市: ${user_info[city]}"

6.4 变量作用域

6.4.1 全局变量与局部变量

bash 复制代码
#!/bin/bash

# 全局变量(在函数外部定义,函数内部可直接访问)
global_var="我是全局变量"

# 局部变量(使用 local 关键字声明)
function test_local {
    local local_var="我是局部变量"
    echo "函数内部 - 全局变量: $global_var"
    echo "函数内部 - 局部变量: $local_var"
}

# 调用函数
test_local

echo ""
echo "函数外部 - 全局变量: $global_var"
echo "函数外部 - 局部变量: $local_var"  # 这里无法访问,会输出空

输出

复制代码
函数内部 - 全局变量: 我是全局变量
函数内部 - 局部变量: 我是局部变量

函数外部 - 全局变量: 我是全局变量
函数外部 - 局部变量: 

6.4.2 使用局部变量的重要性

bash 复制代码
#!/bin/bash

# 不使用局部变量的问题
counter=0

function increment_bad {
    counter=$((counter + 1))
}

function increment_good {
    local temp=$counter
    temp=$((temp + 1))
    counter=$temp
}

echo "初始值: $counter"

increment_bad
echo "increment_bad后: $counter"

counter=0  # 重置

increment_good
echo "increment_good后: $counter"

# 嵌套函数中的变量问题
function outer {
    local x=10
    
    function inner {
        local x=20
        echo "inner中x=$x"
    }
    
    inner
    echo "outer中x=$x"
}

echo ""
outer

6.4.3 只读变量

使用 readonly 定义只读变量,防止被修改:

bash 复制代码
#!/bin/bash

# 定义只读变量
readonly PI=3.14159
readonly MAX_COUNT=100

# 定义只读函数
function readonly_function {
    echo "这是一个只读函数"
}

# 将函数设为只读(不可修改)
readonly -f readonly_function

# 查看所有只读变量
echo "=== 只读变量 ==="
readonly

# 查看只读函数
echo ""
echo "=== 只读函数 ==="
readonly -f

6.5 函数库与脚本引用

6.5.1 加载外部函数库

bash 复制代码
#!/bin/bash

# 创建函数库文件 lib.sh
# 文件名:lib.sh
cat > lib.sh << 'EOF'
#!/bin/bash

# 常用函数库

# 打印带颜色的信息
print_info() {
    echo -e "\033[32m[信息]\033[0m $1"
}

print_error() {
    echo -e "\033[31m[错误]\033[0m $1"
}

print_warning() {
    echo -e "\033[33m[警告]\033[0m $1"
}

# 检查命令是否存在
command_exists() {
    command -v "$1" >/dev/null 2>&1
}

# 获取脚本目录
get_script_dir() {
    local source="${BASH_SOURCE[0]}"
    while [ -h "$source" ]; do
        local dir="$(cd -P "$(dirname "$source")" && pwd)"
        source="$(readlink "$source")"
        [[ $source != /* ]] && source="$dir/$source"
    done
    echo "$(cd -P "$(dirname "$source")" && pwd)"
}
EOF

# 在主脚本中加载函数库
source ./lib.sh

# 使用函数库中的函数
print_info "程序开始执行"
print_error "发生错误"
print_warning "警告信息"

if command_exists ls; then
    print_info "ls命令存在"
fi

echo "脚本目录: $(get_script_dir)"

6.5.2 条件加载函数库

bash 复制代码
#!/bin/bash

# 条件加载函数库
if [ -f "./common.sh" ]; then
    source "./common.sh"
fi

# 或者指定默认路径
LIB_DIR="${HOME}/lib"
if [ -d "$LIB_DIR" ]; then
    for lib in "$LIB_DIR"/*.sh; do
        if [ -f "$lib" ]; then
            source "$lib"
        fi
    done
fi

6.5.3 函数库的设计模式

bash 复制代码
#!/bin/bash

# 函数库设计模式 - 防止重复加载

# 方法1:使用变量检测
[ -z "$_COMMON_LIB_LOADED" ] && {

# 定义库变量
_COMMON_LIB_LOADED=1

# 常用函数定义
function log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}

function die() {
    echo "[ERROR] $*" >&2
    exit 1
}

function assert() {
    if [ -z "$1" ]; then
        die "断言失败: $2"
    fi
}

}  # 结束条件加载块

6.6 函数高级用法

6.6.1 回调函数

Shell不支持真正的回调函数,但可以通过函数指针模拟:

bash 复制代码
#!/bin/bash

# 使用函数名作为参数实现回调
function execute_callback() {
    local callback=$1
    shift
    $callback "$@"
}

function my_callback() {
    echo "回调执行: $*"
}

execute_callback my_callback "参数1" "参数2"

# 更复杂的回调示例
function process_items() {
    local callback=$1
    shift
    local items=("$@")
    
    for item in "${items[@]}"; do
        $callback "$item"
    done
}

function print_item() {
    echo "处理: $1"
}

function uppercase_item() {
    echo "$1" | tr '[:lower:]' '[:upper:]'
}

echo "=== 回调示例1 ==="
process_items print_item apple banana cherry

echo ""
echo "=== 回调示例2 ==="
process_items uppercase_item hello world

6.6.2 匿名函数

bash 复制代码
#!/bin/bash

# 使用 eval 创建匿名函数
eval "$(cat << 'EOF'
function anonymous {
    echo "我是匿名函数: $*"
}
EOF
)"

anonymous "测试"

# 临时函数
(
    function temp {
        echo "临时函数,只在子shell中有效"
    }
    temp
)

echo "子shell外无法访问temp函数"

6.6.3 函数柯里化

bash 复制代码
#!/bin/bash

# 柯里化示例 - 创建带预设参数的函数
function add() {
    local a=$1
    shift
    local b=$1
    echo $((a + b))
}

function multiply() {
    local a=$1
    shift
    echo "local b=\$1; echo \$((a * b))"
}

# 使用柯里化
add5() {
    local a=5
    echo $(($1 + a))
}

multiply_by_3() {
    echo $(($1 * 3))
}

echo "add5 10 = $(add5 10)"
echo "multiply_by_3 7 = $(multiply_by_3 7)"

6.6.4 动态函数定义

bash 复制代码
#!/bin/bash

# 动态创建函数
create_greeting() {
    local name=$1
    eval "function hello_$name {
        echo '你好,$name!'
    }"
}

create_greeting "张三"
create_greeting "李四"

hello_张三
hello_李四

# 动态函数调用
function dispatcher() {
    local cmd=$1
    shift
    
    if declare -f $cmd > /dev/null; then
        $cmd "$@"
    else
        echo "未知命令: $cmd"
        return 1
    fi
}

function cmd1() {
    echo "执行命令1: $*"
}

function cmd2() {
    echo "执行命令2: $*"
}

dispatcher cmd1 "参数"
dispatcher cmd2 "参数"

6.7 综合示例

6.7.1 完整的工具函数库

bash 复制代码
#!/bin/bash

#=========================================
# 常用Shell工具函数库
#=========================================

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

# 日志函数
log_info() {
    echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2
}

log_warning() {
    echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}

log_debug() {
    if [ "${DEBUG:-0}" = "1" ]; then
        echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
    fi
}

# 确认函数
confirm() {
    local prompt=$1
    local default=${2:-"n"}
    
    if [ "$default" = "y" ]; then
        read -p "$prompt [Y/n]: " response
        case $response in
            [nN][oN]|[nN]) return 1 ;;
            *) return 0 ;;
        esac
    else
        read -p "$prompt [y/N]: " response
        case $response in
            [yY][eE][sS]|[yY]) return 0 ;;
            *) return 1 ;;
        esac
    fi
}

# 创建备份
backup_file() {
    local file=$1
    if [ -f "$file" ]; then
        local backup="${file}.$(date +%Y%m%d%H%M%S).bak"
        cp "$file" "$backup"
        log_info "已创建备份: $backup"
        echo "$backup"
    else
        log_error "文件不存在: $file"
        return 1
    fi
}

# 检查依赖
check_dependency() {
    local cmd=$1
    if ! command -v "$cmd" >/dev/null 2>&1; then
        log_error "缺少依赖: $cmd"
        return 1
    fi
    return 0
}

# 进度显示
progress() {
    local current=$1
    local total=$2
    local width=50
    local percent=$((current * 100 / total))
    local completed=$((width * current / total))
    local remaining=$((width - completed))
    
    printf "\r进度: ["
    printf "%${completed}s" | tr ' ' '='
    printf "%${remaining}s" | tr ' ' '-'
    printf "] %3d%%" $percent
    
    if [ $current -eq $total ]; then
        echo ""
    fi
}

# 输入验证
validate_number() {
    [[ $1 =~ ^[0-9]+$ ]]
}

validate_integer() {
    [[ $1 =~ ^-?[0-9]+$ ]]
}

validate_email() {
    [[ $1 =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]
}

# 字符串处理
trim() {
    local var="$*"
    echo "${var#"${var%%[![:space:]]*}"}"
    echo "${var%"${var##*[![:space:]]}"}"
}

to_upper() {
    echo "$1" | tr '[:lower:]' '[:upper:]'
}

to_lower() {
    echo "$1" | tr '[:upper:]' '[:lower:]'
}

# 数组操作
array_contains() {
    local item=$1
    shift
    local arr=("$@")
    for i in "${arr[@]}"; do
        [ "$i" = "$item" ] && return 0
    done
    return 1
}

array_join() {
    local delimiter=$1
    shift
    local arr=("$@")
    local result=""
    for i in "${!arr[@]}"; do
        [ $i -eq 0 ] && result="${arr[$i]}" || result="$result$delimiter${arr[$i]}"
    done
    echo "$result"
}

# 文件操作
file_extension() {
    local file=$1
    echo "${file##*.}"
}

file_basename() {
    local file=$1
    echo "${file##*/}"
}

file_dirname() {
    local file=$1
    echo "${file%/*}"
}

# 测试工具函数库
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
    log_info "这是工具函数库,请作为库引用使用"
    
    # 测试confirm
    echo "=== 测试confirm ==="
    if confirm "确认继续?"; then
        log_info "用户选择了确认"
    else
        log_info "用户选择了取消"
    fi
    
    # 测试依赖检查
    echo ""
    echo "=== 测试依赖检查 ==="
    check_dependency "bash" && log_info "bash已安装"
    check_dependency "nonexistent" || log_error "缺少依赖"
    
    # 测试数组
    echo ""
    echo "=== 测试数组操作 ==="
    arr=(apple banana cherry)
    array_contains "banana" "${arr[@]}" && log_info "数组包含banana"
    
    log_info "工具函数库测试完成"
fi

6.7.2 计算器函数

bash 复制代码
#!/bin/bash

#=========================================
# 简单计算器函数
#=========================================

# 加法
add() {
    local a=$1
    local b=$2
    echo $((a + b))
}

# 减法
subtract() {
    local a=$1
    local b=$2
    echo $((a - b))
}

# 乘法
multiply() {
    local a=$1
    local b=$2
    echo $((a * b))
}

# 除法
divide() {
    local a=$1
    local b=$2
    if [ $b -eq 0 ]; then
        echo "错误:除数不能为0" >&2
        return 1
    fi
    echo $((a / b))
}

# 模运算
modulo() {
    local a=$1
    local b=$2
    if [ $b -eq 0 ]; then
        echo "错误:模数不能为0" >&2
        return 1
    fi
    echo $((a % b))
}

# 乘方
power() {
    local base=$1
    local exp=$2
    echo $((base ** exp))
}

# 平方根
sqrt() {
    local n=$1
    if [ $n -lt 0 ]; then
        echo "错误:不能对负数求平方根" >&2
        return 1
    fi
    local result=1
    while true; do
        result=$(((result + n / result) / 2))
        if [ $((result * result)) -le $n ] && [ $(($((result + 1) * (result + 1)))) -gt $n ]; then
            echo $result
            return 0
        fi
    done
}

# 阶乘
factorial() {
    local n=$1
    if [ $n -lt 0 ]; then
        echo "错误:不能对负数求阶乘" >&2
        return 1
    fi
    local result=1
    for ((i=2; i<=n; i++)); do
        result=$((result * i))
    done
    echo $result
}

# 最大公约数
gcd() {
    local a=$1
    local b=$2
    while [ $b -ne 0 ]; do
        local temp=$b
        b=$((a % b))
        a=$temp
    done
    echo $a
}

# 最小公倍数
lcm() {
    local a=$1
    local b=$2
    echo $((a * b / $(gcd $a $b)))
}

# 交互式计算器
calculator() {
    while true; do
        echo ""
        echo "========== 计算器 =========="
        echo "支持的运算: + - * / % ^"
        echo "特殊功能: sqrt, factorial, gcd, lcm"
        echo "输入 'quit' 退出"
        echo "=============================="
        read -p "请输入表达式: " expr
        
        if [ "$expr" = "q" ] || [ "$expr" = "quit" ]; then
            echo "退出计算器"
            break
        fi
        
        # 解析表达式
        local num1=$(echo "$expr" | awk '{print $1}')
        local op=$(echo "$expr" | awk '{print $2}')
        local num2=$(echo "$expr" | awk '{print $3}')
        
        # 特殊函数
        case $op in
            sqrt)
                result=$(sqrt $num1)
                echo "sqrt($num1) = $result"
                ;;
            factorial)
                result=$(factorial $num1)
                echo "$num1! = $result"
                ;;
            gcd)
                result=$(gcd $num1 $num2)
                echo "gcd($num1, $num2) = $result"
                ;;
            lcm)
                result=$(lcm $num1 $num2)
                echo "lcm($num1, $num2) = $result"
                ;;
            +) result=$(add $num1 $num2) ;;
            -) result=$(subtract $num1 $num2) ;;
            x|X|\*) result=$(multiply $num1 $num2) ;;
            /) result=$(divide $num1 $num2) ;;
            %) result=$(modulo $num1 $num2) ;;
            ^) result=$(power $num1 $num2) ;;
            *) echo "未知运算符: $op" ;;
        esac
        
        [ -n "$result" ] && echo "结果: $result"
    done
}

# 测试
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
    echo "=== 计算器测试 ==="
    echo "10 + 5 = $(add 10 5)"
    echo "10 - 5 = $(subtract 10 5)"
    echo "10 * 5 = $(multiply 10 5)"
    echo "10 / 5 = $(divide 10 5)"
    echo "10 % 3 = $(modulo 10 3)"
    echo "2 ^ 8 = $(power 2 8)"
    echo "sqrt(16) = $(sqrt 16)"
    echo "5! = $(factorial 5)"
    echo "gcd(48, 18) = $(gcd 48 18)"
    echo "lcm(4, 6) = $(lcm 4 6)"
    
    echo ""
    echo "=== 启动交互式计算器 ==="
    calculator
fi

6.7.3 数据处理函数库

bash 复制代码
#!/bin/bash

#=========================================
# 数据处理函数库
#=========================================

# 数组去重
array_unique() {
    local arr=("$@")
    echo "${arr[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '
}

# 数组反转
array_reverse() {
    local arr=("$@")
    local reversed=()
    for ((i=${#arr[@]}-1; i>=0; i--)); do
        reversed+=("${arr[$i]}")
    done
    echo "${reversed[@]}"
}

# 数组排序
array_sort() {
    local arr=("$@")
    echo "${arr[@]}" | tr ' ' '\n' | sort | tr '\n' ' '
}

# 数组最大值
array_max() {
    local arr=("$@")
    local max=${arr[0]}
    for item in "${arr[@]}"; do
        ((item > max)) && max=$item
    done
    echo $max
}

# 数组最小值
array_min() {
    local arr=("$@")
    local min=${arr[0]}
    for item in "${arr[@]}"; do
        ((item < min)) && min=$item
    done
    echo $min
}

# 数组求和
array_sum() {
    local arr=("$@")
    local sum=0
    for item in "${arr[@]}"; do
        sum=$((sum + item))
    done
    echo $sum
}

# 数组平均值
array_avg() {
    local arr=("$@")
    local sum=$(array_sum "${arr[@]}")
    echo $((sum / ${#arr[@]}))
}

# 字符串包含判断
string_contains() {
    local haystack=$1
    local needle=$2
    [[ "$haystack" == *"$needle"* ]]
}

# 字符串开头判断
string_starts_with() {
    local str=$1
    local prefix=$2
    [[ "$str" == "$prefix"* ]]
}

# 字符串结尾判断
string_ends_with() {
    local str=$1
    local suffix=$2
    [[ "$str" == *"$suffix" ]]
}

# 字符串替换(全局)
string_replace() {
    local str=$1
    local old=$2
    local new=$3
    echo "${str//$old/$new}"
}

# 文件读取为数组
file_to_array() {
    local file=$1
    local arr=()
    while IFS= read -r line; do
        arr+=("$line")
    done < "$file"
    echo "${arr[@]}"
}

# 数组写入文件
array_to_file() {
    local file=$1
    shift
    local arr=("$@")
    printf '%s\n' "${arr[@]}" > "$file"
}

# CSV解析
csv_get_field() {
    local line=$1
    local field=$2
    local delimiter=${3:-","}
    echo "$line" | cut -d"$delimiter" -f"$field"
}

# JSON解析(简单)
json_get_value() {
    local json=$1
    local key=$2
    echo "$json" | grep -o "\"$key\":[^,}]*" | cut -d':' -f2 | tr -d ' "'
}

# URL编码
url_encode() {
    local string=$1
    echo -n "$string" | sed 's/ /%20/g; s/!/%21/g; s/#/%23/g; s/\$/%24/g; s/&/%26/g; s/+/%2B/g; s/,/%2C/g; s/\//%2F/g; s/:/%3A/g; s/;/%3B/g; s/=/%3D/g; s/?/%3F/g; s/@/%40/g; s/\[/%5B/g; s/\]/%5D/g'
}

# URL解码
url_decode() {
    local string=$1
    echo -n "$string" | sed 's/%20/ /g; s/%21/!/g; s/%23/#/g; s/%24/$/g; s/%26/\&/g; s/%2B/+/g; s/%2C/,/g; s/%2F/\//g; s/%3A/:/g; s/%3B/;/g; s/%3D/=/g; s/%3F/?/g; s/%40/@/g; s/%5B/[/g; s/%5D/]/g'
}

# 测试
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
    echo "=== 数组函数测试 ==="
    arr=(5 2 8 1 9 3)
    echo "原数组: ${arr[@]}"
    echo "去重后: $(array_unique 1 2 3 2 1 4)"
    echo "反转后: $(array_reverse 1 2 3 4 5)"
    echo "排序后: $(array_sort 5 2 8 1 9)"
    echo "最大值: $(array_max 5 2 8 1 9)"
    echo "最小值: $(array_min 5 2 8 1 9)"
    echo "求和: $(array_sum 1 2 3 4 5)"
    echo "平均值: $(array_avg 1 2 3 4 5)"
    
    echo ""
    echo "=== 字符串函数测试 ==="
    str="Hello World"
    string_contains "$str" "World" && echo "包含World"
    string_starts_with "$str" "Hello" && echo "以Hello开头"
    string_ends_with "$str" "World" && echo "以World结尾"
    echo "替换后: $(string_replace "Hello" "Hi" "$str")"
    
    echo ""
    echo "=== URL编码测试 ==="
    echo "编码: $(url_encode "hello world!")"
fi

6.8 本章小结

本章详细介绍了Shell脚本中函数的用法:

  • 函数的定义与调用

    • 两种定义语法(function 关键字和直接函数名)
    • 函数的返回值(return vs echo
    • 函数的递归调用
  • 函数参数传递

    • 位置参数($1, $2, ...)
    • $*$@ 的区别(重点)
    • 使用 shift 处理参数
    • 参数默认值与校验
  • 函数返回值

    • return:返回状态码(0-255)
    • echo:返回具体数据
    • 返回多个值的方法
  • 变量作用域

    • 全局变量与局部变量的区别
    • 使用 local 声明局部变量
    • 只读变量(readonly
  • 函数库与脚本引用

    • 使用 source 加载函数库
    • 条件加载函数库
    • 函数库设计模式
  • 函数高级用法

    • 回调函数模拟
    • 匿名函数
    • 函数柯里化
    • 动态函数定义
  • 综合示例

    • 完整的工具函数库(日志、确认、备份、输入验证、字符串处理、数组操作)
    • 计算器函数(支持加减乘除模乘方阶乘最大公约数最小公倍数)
    • 数据处理函数库(数组操作、字符串处理、文件操作、URL编解码)

掌握函数的使用可以大大提高脚本的模块化和可维护性,是编写大型Shell脚本的基础。

更多内容,欢迎访问南徽玉的个人博客

相关推荐
Nanhuiyu1 天前
Shell编程从入门到精通-第四章 条件判断
shell编程
Nanhuiyu2 天前
Shell编程从入门到精通-第一章 Shell简介与环境配置
shell编程
予枫的编程笔记12 天前
【Linux进阶篇】从基础到实战:grep高亮、sed流编辑、awk分析,全场景覆盖
linux·sed·grep·awk·shell编程·文本处理三剑客·管道命令
vortex512 天前
解密UUOC:Shell编程中“无用的cat使用”详解
linux·shell编程
燃于AC之乐17 天前
【Linux系统编程】Shell解释器完全实现:从命令解析、环境变量管理到内建命令的全面解析
linux·操作系统·命令行工具·进程控制·shell编程
PyHaVolask1 个月前
Linux零基础入门:输入输出重定向完全指南
linux运维·shell编程·输入输出流·linux重定向·命令行技巧
牛奶咖啡131 个月前
shell脚本编程(二)
linux·正则表达式·shell编程·正则表达式扩展·shell通配符·shell的变量·shell的引用
云计算练习生4 个月前
linux shell编程实战 03 数组:批量处理数据
linux·运维·服务器·数组·shell编程
云计算练习生4 个月前
linux shell编程实战 04 条件判断与流程控制
linux·运维·流程控制·shell编程·条件判断