Skip to content

07|数组

上一篇批量执行的例子里,主机列表用 "$@" 传进来。但如果要在脚本里自己存一组值——比如要监控的服务列表、要备份的多个目录——靠位置参数不够,得用数组。

本篇讲 Shell 的数组:索引数组和关联数组。

一、索引数组

一组按顺序排列的值,用括号定义:

bash
hosts=(web-01 web-02 db-01)

访问单个元素(下标从 0 开始):

bash
echo "${hosts[0]}"    # web-01
echo "${hosts[1]}"    # web-02
echo "${#hosts[@]}"   # 3,元素个数

遍历数组:

bash
for h in "${hosts[@]}"; do
    echo "主机: $h"
done

"${hosts[@]}" 把数组展开成每个元素独立的词,类似 "$@"。加引号是为了保留元素里可能的空格。

二、追加和修改元素

bash
hosts=(web-01 web-02)
hosts+=(db-01)        # 追加
hosts[0]=web-00       # 修改第一个
echo "${hosts[@]}"    # web-00 web-02 db-01

删除元素用 unset,但要注意它不会自动挪后面的下标:

bash
unset 'hosts[1]'
echo "${hosts[@]}"    # web-00 db-01
echo "${#hosts[@]}"   # 2
echo "${hosts[1]}"    # 空(这个位置被删了,不是后面补上)

三、数组切片

取数组的一部分,和 Python 切片类似:

bash
hosts=(web-01 web-02 web-03 db-01 db-02)
echo "${hosts[@]:1:3}"    # web-02 web-03 db-01(从下标1取3个)

四、关联数组(Bash 4+)

普通数组的下标是数字。关联数组的下标是字符串,类似 Python 的字典。需要 Bash 4.0 以上:

bash
declare -A ports
ports[nginx]=80
ports[mysql]=3306
ports[redis]=6379

echo "${ports[nginx]}"    # 80

遍历关联数组:

bash
for service in "${!ports[@]}"; do
    echo "服务 $service 端口 ${ports[$service]}"
done

${!ports[@]} 取的是键(key),不是值。${ports[@]} 取的是值。

关联数组在做映射时很有用——服务名到端口、目录到清理策略、主机到角色:

bash
declare -A role
role[web-01]=frontend
role[db-01]=database
role[cache-01]=cache

for host in "${!role[@]}"; do
    echo "$host 的角色是 ${role[$host]}"
done

五、从命令输出填数组

把命令的多行输出存进数组,每行一个元素:

bash
mapfile -t logs < <(ls /var/log/*.log)
echo "找到 ${#logs[@]} 个日志文件"
for f in "${logs[@]}"; do
    echo "  $f"
done

mapfile -t 把输入按行存进数组,-t 去掉行尾换行符。< <(...) 是进程替换,把命令输出当文件喂给 mapfile

老版本 bash 没有 mapfile,用循环替代:

bash
logs=()
while IFS= read -r line; do
    logs+=("$line")
done < <(ls /var/log/*.log)

六、数组 vs 位置参数

上一篇的批量脚本用 "$@" 接收主机。如果想把脚本里写死的服务列表传给一个函数或子脚本,用数组更清晰:

bash
services=(nginx mysql redis)

for svc in "${services[@]}"; do
    systemctl status "$svc"
done

数组能动态增删、能切片、能遍历,比位置参数灵活。位置参数适合"外部传入",数组适合"脚本内部组织一组值"。

七、一个监控脚本例子

把数组用起来——监控一组服务的状态:

bash
#!/usr/bin/env bash
set -euo pipefail

declare -A checks=(
    [nginx]="systemctl is-active nginx"
    [mysql]="systemctl is-active mysqld"
    [redis]="systemctl is-active redis"
)

for name in "${!checks[@]}"; do
    cmd="${checks[$name]}"
    if $cmd --quiet 2>/dev/null; then
        echo "$name: 正常"
    else
        echo "$name: 异常" >&2
    fi
done

关联数组把"服务名"和"检查命令"对应起来,遍历时能同时拿到两者。加一个服务只要在数组里加一行,不用改循环逻辑。

学会了之后

数组让脚本能一次管理一组值。普通数组管有序列表,关联数组管键值映射。"${arr[@]}" 遍历、${#arr[@]} 数个数、arr+=() 追加——这几个操作覆盖了大部分数组用法。

下一篇讲字符串处理——截取、替换、路径操作。