// Linux · 2025-03-12

Shell编程:深入了解 Bash 数组操作

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 数组虽然语法简洁,但暗藏诸多陷阱与强大能力。掌握以下优先级:

  1. 始终使用 "${array[@]}" 遍历和传递
  2. 优先使用 readarray / mapfile 安全读入列表
  3. 需要键值对或用户输入时,优先使用关联数组 declare -A
  4. 永远不要让未过滤的用户输入进入索引数组下标或算术上下文(-eq -gt (( )) 等)

熟练运用数组,能让你的 shell 脚本从“能跑”升级到“优雅、可维护、高性能”。多写、多测、多用 shellcheck,你会越来越得心应手。

继续深挖的话,建议阅读:

  • man bash 中的 “Arrays” 章节
  • ShellCheck 的数组相关警告规则
  • 现代脚本实践:结合 jq 处理复杂数据结构

原文 https://blog.csdn.net/2301_79518550/article/details/145880811