Bash 数组是 shell 脚本中最强大、最灵活的数据结构之一。它允许你以索引方式存储和操作一组值,广泛应用于参数解析、文件列表处理、配置管理、日志分析等领域。
1. Bash 数组的核心概念
1.1 数组的本质
Bash 中的数组是一个键-值映射容器,可以存储任意类型的字符串(包括空字符串)。与许多编程语言不同,Bash 数组有两大显著特点:
- 索引可以不连续(稀疏数组,sparse array)
- 索引默认从 0 开始,但可以是任意非负整数,甚至负数(负数从末尾倒数)
1.2 两大数组类型
| 类型 | 声明方式 | 索引类型 | 是否进行算术求值 | 典型场景 |
|---|---|---|---|---|
| 索引数组 | 无需声明或 declare -a |
非负整数 | 会(下标会被算术求值) | 顺序列表、文件列表 |
| 关联数组 | declare -A |
任意字符串 | 不会 | 配置键值对、计数、去重映射 |
重要安全提醒:索引数组的下标表达式会在算术上下文中被提前求值,支持 $() 命令替换,因此绝不能直接把用户输入用作索引数组的下标,否则极易造成命令注入(RCE)。
1.3 稀疏数组与非连续索引
Bash 天然支持稀疏数组,索引可以跳跃:
# 索引 0、10、100 都有值,中间为空
declare -a sparse=([0]="first" [10]="tenth" [100]="hundredth")
访问不存在的索引返回空字符串,不会报错。
2. 创建数组
2.1 推荐方式:列表式赋值(compound assignment)
# 最常用、最清晰
fruits=(apple banana "cherry pie" "dragon fruit")
2.2 通过索引逐个赋值(支持稀疏)
colors[0]=red
colors[5]=green # 中间索引 1-4 为空
colors[42]="deep blue"
2.3 从命令输出或变量创建
files=( $(ls -1) ) # 不推荐:有词分割风险
files=($(find . -maxdepth 1 -type f)) # 更安全写法见后文
推荐安全写法(防止词分割和通配符展开):
mapfile -t files < <(find . -maxdepth 1 -type f)
# 或
readarray -t files < <(find . -maxdepth 1 -type f)
3. 访问数组元素
3.1 单个元素
echo "${fruits[0]}" # apple
echo "${fruits[2]}" # cherry pie
最佳实践:几乎总是使用花括号 ${...},防止变量名与后续字符混淆。
3.2 所有元素(最常用两种写法对比)
| 写法 | 展开行为 | 推荐场景 |
|---|---|---|
${array[@]} |
每个元素作为独立单词(保持空格) | for 循环、传递给命令(强烈推荐) |
${array[*]} |
所有元素合并成一个字符串(用 IFS 分隔) | 打印、存入变量(较少用) |
for f in "${fruits[@]}"; do # 正确:每个元素独立
echo "Fruit: $f"
done
3.3 获取长度
echo ${#fruits[@]} # 元素个数
echo ${#fruits[2]} # 第3个元素的字符串长度
4. 修改与添加元素
4.1 修改指定位置
fruits[1]="mango"
4.2 追加元素(两种常用方式)
# 方式1:追加单个或多个
fruits+=("kiwi" "lemon")
# 方式2:追加数组
extra=(papaya starfruit)
fruits+=("${extra[@]}")
5. 删除元素与数组
unset fruits[1] # 删除索引 1 的元素(留下空位)
unset fruits # 删除整个数组
注意:unset fruits[1] 后,${fruits[@]} 仍然包含原来的元素个数(稀疏),但该位置为空。
6. 遍历数组(推荐写法)
6.1 遍历值(最常用)
for item in "${array[@]}"; do
echo "$item"
done
6.2 遍历索引 + 值
for idx in "${!fruits[@]}"; do
printf "Index %d → %s\n" "$idx" "${fruits[$idx]}"
done
7. 数组切片(Substring Expansion)
# 语法:${array[@]:start:length}
echo "${fruits[@]:1:3}" # 从第1个元素开始,取3个
echo "${fruits[@]: -2}" # 最后两个元素(注意空格)
8. 关联数组(Associative Array)快速入门
declare -A config
config["host"]="127.0.0.1"
config["port"]="8080"
config["user"]="admin"
echo "Host: ${config[host]}"
echo "All keys: ${!config[@]}"
echo "All values: ${config[@]}"
优势:键是字符串,不会被算术求值,适合安全地处理用户输入。
9. 常见高级技巧与陷阱
9.1 数组排序
# 按字典序排序
sorted=($(printf '%s\n' "${fruits[@]}" | sort))
# 数值排序
numbers=(3 1 42 15 7)
sorted_nums=($(printf '%s\n' "${numbers[@]}" | sort -n))
9.2 数组去重
unique=($(printf '%s\n' "${array[@]}" | sort -u))
9.3 随机抽取元素
echo "${array[$((RANDOM % ${#array[@]}))]}"
9.4 常见陷阱
- 不要写
for f in ${array[@]}(丢失空格) - 不要直接
${files=($(ls))}(词分割+通配符展开) - 不要把用户输入直接用作索引数组下标(如
${arr[$user]})
10. 小结与进阶建议
Bash 数组虽然语法简洁,但暗藏诸多陷阱与强大能力。掌握以下优先级:
- 始终使用
"${array[@]}"遍历和传递 - 优先使用
readarray/mapfile安全读入列表 - 需要键值对或用户输入时,优先使用关联数组
declare -A - 永远不要让未过滤的用户输入进入索引数组下标或算术上下文(
-eq -gt (( ))等)
熟练运用数组,能让你的 shell 脚本从“能跑”升级到“优雅、可维护、高性能”。多写、多测、多用 shellcheck,你会越来越得心应手。
继续深挖的话,建议阅读:
man bash中的 “Arrays” 章节- ShellCheck 的数组相关警告规则
- 现代脚本实践:结合
jq处理复杂数据结构
原文 https://blog.csdn.net/2301_79518550/article/details/145880811