一、简介:为什么必须学会关联数组?
在日常脚本开发中,我们经常会遇到“键-值”型数据:
根据主机名找 IP
根据用户名查邮箱
统计每个 URL 的出现次数
使用普通数值索引数组(arr[0]=alice)需要手工维护映射表,既麻烦又容易出错。
Bash 4.0 起内置关联数组(Associative Array),功能等价于其他语言的“字典(Dictionary)”或“Map”:
键可以是任意字符串,不再局限于 0 1 2 …
读写速度 O(1),脚本依然轻量
无需额外安装软件,比外部调用 awk/sed 更快速、可读性更高
掌握关联数组,让你的脚本从“玩具级”跃升到“工程级”。
二、核心概念:一张表看懂关联数组
| 术语 | 说明 | 示例 |
|---|---|---|
| 关联数组 | 键→值 的映射表 | declare -A site=( [google]="216.58.200.46" ) |
| 键(key) | 唯一字符串 | google |
| 值(value) | 任意字符串 | 216.58.200.46 |
| 声明 | 告诉 Bash 这是关联数组 | declare -A arr |
| 遍历 | 逐个访问键/值 | for key in "${!arr[@]}" |
注意: Bash 3 及以下不支持,macOS 默认 Bash 3 需
brew install bash。
三、环境准备:2 分钟搞定
检查版本
bash --version | head -1 # ≥ 4.0 即可若 < 4.0(CentOS 7、macOS 默认)
# CentOS sudo yum install -y bash # macOS brew install bash进入实验目录
mkdir -p ~/bash-dict && cd ~/bash-dict
四、命令与示例:从定义到遍历,一站式 copy & run
以下脚本均保存为
demo.sh,直接chmod +x demo.sh && ./demo.sh即可看到结果。
4.1 声明与赋值
#!/usr/bin/env bash # demo1.sh declare -A config # ① 声明关联数组 config[host]="192.168.1.10" # ② 赋值 config[user]="root" config[port]="22" echo "主机=${config[host]}" # ③ 读取 echo "用户=${config[user]}"输出:
主机=192.168.1.10 用户=root4.2 一次批量赋值(花括号法)
#!/usr/bin/env bash # demo2.sh declare -A color=( [red]="#FF0000" [green]="#00FF00" [blue]="#0000FF" ) echo 红色代码=${color[red]}4.3 安全读取:防未定义键
#!/usr/bin/env bash # demo3.sh declare -A cfg cfg[timeout]=30 # 如果键不存在返回默认值 timeout=${cfg[timeout]:-60} # ← 60 是默认值 echo "超时时间=$timeout"4.4 遍历键与值
#!/usr/bin/env bash # demo4.sh declare -A score=( [alice]=90 [bob]=80 [carol]=85 ) # 遍历键 for name in "${!score[@]}"; do echo "$name 分数是 ${score[$name]}" done技巧:
"${!score[@]}"取全部键"${score[@]}"取全部值顺序与插入顺序无关(哈希实现)
4.5 计数器:统计单词出现次数
#!/usr/bin/env bash # demo5.sh declare -A counter while read -r word; do ((counter[$word]++)) # 不存在时自动初始为 0 done < <(echo "apple apple banana apple") for w in "${!counter[@]}"; do echo "$w 出现了 ${counter[$w]} 次" done输出:
apple 出现了 3 次 banana 出现了 1 次4.6 修改与删除
#!/usr/bin/env bash # demo6.sh declare -A site site[google]="216.58.200.46" # 修改 site[google]="142.250.185.78" echo "新 IP=${site[google]}" # 删除 unset site[google] [[ -v site[google] ]] && echo "仍存在" || echo "已删除"4.7 长度与判空
echo "键数量=${#config[@]}" [[ ${#config[@]} -eq 0 ]] && echo "数组为空"4.8 嵌套模拟(二维键)
Bash 不支持真正的二维数组,可用拼接键技巧:
declare -A matrix matrix[1,1]=10 matrix[1,2]=20 echo "${matrix[1,2]}" # 输出 204.9 与配置文件联动:一行导入
# config.txt 内容: # host=192.168.1.10 # user=root # port=22 declare -A cfg while IFS='=' read -r key value; do cfg[$key]=$value done < config.txt echo "导入完成,主机=${cfg[host]}"4.10 函数返回值:返回字典
# 返回多个值的最佳实践 function get_dbinfo() { local -A db db[host]="localhost" db[port]=3306 db[user]="admin" # 打印键值对,调用者用 eval 重建 for k in "${!db[@]}"; do echo "$k=${db[$k]}" done } # 调用端 declare -A dbinfo while IFS='=' read -r k v; do dbinfo[$k]=$v done < <(get_dbinfo) echo "数据库端口=${dbinfo[port]}"五、常见问题与解答(FAQ)
| 问题 | 现象 | 解决 | |
|---|---|---|---|
declare -A报错“invalid option” | Bash < 4.0 | 升级 Bash,brew install bash/yum install bash | |
| 键顺序乱 | 与插入顺序不同 | 关联数组底层哈希,排序用 `for k in (printf′{!arr[@]}" | sort)` |
| 空格被截断 | arr[key]=val ue | 双引号包裹:arr[key]="val ue" | |
| 变量未定义却返回 0 | ((${counter[$word]}++)) | 这是预期行为;若需严格判空先用[[ -v counter[$word] ]] | |
| macOS 默认 sh 不支持 | #!/bin/sh失败 | shebang 写#!/usr/bin/env bash且确保新 bash 路径在/etc/shells |
六、实践建议与最佳实践
统一封装:把常用读配置写成函数,避免全局变量污染。
默认安全:读取加
:-默认值,脚本在 CI 里更健壮。大文件提速:>10 万键时,用外部 SQLite;Bash 数组仍轻量,但别滥用。
命名规范:全大写表示常量,例
declare -A CONFIG=()。调试技巧:
declare -p arr # 打印完整键值对,排错神器兼容考虑:若脚本需支持 Bash 3,回退到
awk关联数组,但放弃 Bash 原生。
七、总结:一张脑图带走全部要点
Bash 关联数组 ├─ 声明:declare -A arr ├─ 赋值:arr[key]=val / 批量 ( [k]=v ) ├─ 读取:${arr[key]} / ${arr[key]:-default} ├─ 遍历:for k in "${!arr[@]}" ├─ 计数:((arr[key]++)) └─ 技巧:config 文件一键导入、函数返回字典掌握关联数组,你的脚本就能:
用30 行写完过去需要
awk + grep100 行的配置解析让 CI 流水线、自动部署脚本易读、易维护、易扩展
在无外部依赖的嵌入式环境,也能享受“字典”级数据结构
现在就打开终端,复制第 4.5 节的计数器脚本跑一遍——Bash 也有现代数据结构,只是你还没开始用!