⚙️ Day 4 of 7

函数、数组与算术运算

📖 预计时长:2.5 小时 🎯 难度:中级
函数让代码可复用,数组让数据有组织,算术运算让脚本能计算。这三项能力组合起来,可以写出结构完整、逻辑清晰的复杂脚本。
函数定义与调用 参数传递与返回值 local 作用域 索引数组操作 关联数组(字典) $(()) 算术扩展

1️⃣ 函数基础

函数定义与调用

#!/bin/bash

# 定义方式一(推荐,兼容性好)
greet() {
    echo "你好!"
}

# 定义方式二(function 关键字)
function hello() {
    echo "Hello, World!"
}

# 调用函数(不加括号!)
greet
hello

# 函数必须在调用前定义(Shell 顺序执行)
bash

函数参数

#!/bin/bash

check_service() {
    local svc_name="$1"  # 函数内的位置参数
    
    if systemctl is-active "$svc_name" >/dev/null 2>&1; then
        echo "✓ $svc_name 运行中"
    else
        echo "✗ $svc_name 已停止"
    fi
}

# 调用时传参
check_service "sshd"
check_service "nginx"

# 函数内 $1 $2 $@ $# 表示函数的参数,不是脚本的参数
bash

函数返回值

#!/bin/bash

# return 只能返回数字(0-255),0=成功,非0=失败
is_root() {
    [[ $EUID -eq 0 ]] && return 0 || return 1
}

if is_root; then
    echo "当前是 root 用户"
else
    echo "非 root 用户"
fi

# 返回字符串:通过 echo 输出 + 命令替换捕获
get_ip() {
    hostname -I | awk '{print $1}'
}
my_ip=$( get_ip )
echo "本机IP:$my_ip"

# 获取上一个函数的返回码
some_function
echo "返回码:$?"
bash

local 局部变量

#!/bin/bash
name="全局变量"

test_scope() {
    local name="局部变量"  # local 声明只在函数内有效
    echo "函数内:$name"     # 局部变量
}

test_scope
echo "函数外:$name"       # 全局变量,不受影响
bash
💡
最佳实践在函数内所有变量都用 local 声明,避免污染全局变量空间,提高脚本健壮性。

2️⃣ 索引数组

#!/bin/bash

# 定义数组(索引从0开始)
fruits=("苹果" "香蕉" "橙子" "葡萄")

# 访问元素
echo ${fruits[0]}         # 苹果
echo ${fruits[2]}         # 橙子
echo ${fruits[-1]}        # 葡萄(最后一个)

# 所有元素
echo ${fruits[@]}         # 苹果 香蕉 橙子 葡萄

# 数组长度
echo ${#fruits[@]}        # 4

# 所有索引
echo ${!fruits[@]}        # 0 1 2 3

# 修改元素
fruits[1]="芒果"

# 追加元素
fruits+=("草莓")
fruits[${#fruits[@]}]="西瓜"

# 删除元素
unset fruits[1]

# 切片:${数组名[@]:起始:长度}
echo ${fruits[@]:1:2}     # 取第1到第2个元素

# 遍历数组
for fruit in "${fruits[@]}"; do
    echo "- $fruit"
done
bash

3️⃣ 关联数组(字典/哈希表)

#!/bin/bash

# 需要 Bash 4+,必须先声明
declare -A config

# 赋值
config[host]="192.168.1.100"
config[port]="5432"
config[user]="postgres"
config[db]="myapp"

# 也可以直接初始化
declare -A server=([ip]="10.0.0.1" [name]="web01" [cpu]="8核")

# 访问
echo "DB 主机:${config[host]}"
echo "DB 端口:${config[port]}"

# 所有 key
echo "Keys: ${!config[@]}"

# 所有 value
echo "Values: ${config[@]}"

# 遍历 key-value
for key in "${!config[@]}"; do
    printf "  %-10s = %s\n" "$key" "${config[$key]}"
done

# 判断 key 是否存在
[[ -v config[host] ]] && echo "host 键存在"
bash

4️⃣ 算术运算

方式语法说明
$(( ))result=$((a+b))算术扩展,最推荐
(( ))((a++))算术命令,可修改变量
letlet "a=b+c"旧式写法
exprexpr $a + $b外部命令,最慢,不推荐
bcecho "1.5+2.3" | bc浮点运算
#!/bin/bash
a=10
b=3

# 基本运算
echo "加法:$((a + b))"      # 13
echo "减法:$((a - b))"      # 7
echo "乘法:$((a * b))"      # 30
echo "除法:$((a / b))"      # 3(整除)
echo "取余:$((a % b))"      # 1
echo "幂运算:$((2 ** 10))"  # 1024

# 自增/自减
(( a++ ))     # a = 11
(( a-- ))     # a = 10
(( a += 5 ))  # a = 15
(( a *= 2 ))  # a = 30

# 浮点运算(需要 bc)
result=$(echo "scale=2; 10 / 3" | bc)   # 3.33
echo "10 / 3 = $result"

# 随机数
echo "1-100 随机数:$((RANDOM % 100 + 1))"

# 进制转换
echo "十进制 255 = 十六进制 $(printf '%x\n' 255)"
echo "十六进制 ff = 十进制 $((16#ff))"
bash

5️⃣ 函数库模式

将常用函数封装到库文件,供多个脚本复用:

#!/bin/bash
# 文件:lib/common.sh  函数库

# 彩色输出函数
log_info()    { echo "\e[32m[INFO]\e[0m $*"; }
log_warn()    { echo "\e[33m[WARN]\e[0m $*"; }
log_error()   { echo "\e[31m[ERROR]\e[0m $*" >&2; }
log_success() { echo "\e[32m[OK]\e[0m $*"; }

# 检查命令是否存在
require_cmd() {
    if ! command -v "$1" >/dev/null 2>&1; then
        log_error "命令 '$1' 未安装"
        exit 1
    fi
}

# 检查是否为 root
require_root() {
    [[ $EUID -ne 0 ]] && { log_error "需要 root 权限"; exit 1; }
}

# 在主脚本中引用库
# source "$(dirname "$0")/lib/common.sh"
# 或
# . "$(dirname "$0")/lib/common.sh"
bash

🏋️ 今日练习

  1. 创建一个函数库 utils.sh,包含:颜色输出、记录日志到文件、检查命令存在、获取IP地址等函数
  2. 写一个函数,接收数组名作为参数,统计数组中最大值、最小值和平均值
  3. 用关联数组实现一个简单的配置文件解析器,读取 key=value 格式的配置文件到关联数组
  4. 写一个计算器脚本,支持加减乘除和幂运算,参数格式:calc.sh 10 + 3
  5. 用递归函数计算斐波那契数列第 N 项(注意 N 不要太大)