Appearance
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"
donemapfile -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+=() 追加——这几个操作覆盖了大部分数组用法。
下一篇讲字符串处理——截取、替换、路径操作。