Shell 脚本数组:@() 数组与索引
Shell 脚本编程中,数组是一个极其强大但常被忽视的工具。掌握数组的用法,能让你的脚本从"单行命令"进化成"自动化利器"。本文将深入讲解 Bash 中 @() 数组的创建、索引机制以及常用操作手法。
一、认识 Shell 数组
在 Bash 中,数组是一组按顺序排列的数据集合。与其他编程语言不同,Bash 数组不需要预先声明大小,可以在运行时动态扩展。数组中的每个元素都有一个唯一的编号——称为索引,通过索引可以快速定位和操作具体元素。
Shell 数组的核心特点是索引从 0 开始,这一点与其他主流语言一致。理解索引机制是掌握数组的关键,后续所有的操作都围绕索引展开。
二、创建数组的基本方法
1. 直接赋值法
创建数组最简单的方式是直接给变量名加中括号并赋值。每个元素之间用空格分隔:
# 创建包含三个元素的数组
fruits[0]="apple"
fruits[1]="banana"
fruits[2]="orange"
2. @() 快捷语法
@() 是 Bash 提供的数组字面量语法,一行代码即可完成数组创建。这种方式更加简洁直观,是日常使用的主流方法:
# 使用 @() 创建数组
fruits=("apple" "banana" "orange")
执行后,fruits 数组自动包含三个元素:索引 0 对应 "apple",索引 1 对应 "banana",索引 2 对应 "orange"。
3. 混合初始化
可以混用直接赋值和 @() 语法,实现灵活的数组构建:
# 先用 @() 创建基础数组,再用单独赋值追加
servers=("web01" "web02" "db01")
servers[3]="cache01"
servers[4]="backup01"
三、数组索引的核心规则
1. 索引的基本特性
Shell 数组的索引规则简单明确,理解这些规则能避免大多数常见错误:
| 特性 | 说明 |
|---|---|
| 起始索引 | 数组索引从 0 开始,而非 1 |
| 连续性 | 索引必须为整数,可以不连续 |
| 最大索引 | 受限于系统内存和整数范围 |
| 负数索引 | Bash 4.3+ 支持负数索引,从末尾倒数 |
2. 查看数组长度与索引范围
获取数组信息是调试和编程中的常见需求,以下命令可以快速获取关键信息:
# 获取数组元素个数
echo ${#fruits[@]} # 输出:3
# 获取某个元素的字符长度
echo ${#fruits[0]} # 输出:5 (apple 的长度)
# 列出所有索引
echo ${!fruits[@]} # 输出:0 1 2
# 获取所有元素值
echo ${fruits[@]} # 输出:apple banana orange
3. 访问单个元素
通过 `${数组名[索引]}` 语法访问指定元素,不指定索引时默认访问索引 0: ```bash # 访问索引 1 的元素 echo ${fruits[1]} # 输出:banana
访问索引 0(默认行为)
echo ${fruits} # 输出:apple # 访问不存在的索引返回空字符串 echo ${fruits[99]} # 输出:(空)
### 4. 访问多个元素(切片操作)
Bash 支持使用通配符语法进行数组切片,一次性获取多个连续元素:
```bash
fruits=("apple" "banana" "orange" "grape" "melon")
# 从索引 1 开始,取 2 个元素
echo ${fruits[@]:1:2} # 输出:banana orange
# 从索引 0 开始,取到末尾
echo ${fruits[@]:0} # 输出全部元素
# 从倒数第 2 个开始,取 1 个
echo ${fruits[@]: -2:1} # 输出:grape
```
注意切片语法中的空格:`:` 后面有一个空格,`-2` 与 `:` 之间也有空格,这些空格是语法的必要组成部分。
---
## 四、数组的增删改操作
### 1. 修改元素值
直接对指定索引赋值即可修改元素,数组会自动更新:
```bash
fruits=("apple" "banana" "orange")
# 修改索引 1 的元素
fruits[1]="pear"
echo ${fruits[@]} # 输出:apple pear orange
2. 追加新元素
在数组末尾添加元素有两种常用方式,推荐使用 += 运算符:
fruits=("apple" "banana")
# 方式一:使用 += 运算符(推荐)
fruits+=("cherry" "date")
# 方式二:使用索引赋值
fruits[${#fruits[@]}]="elderberry"
echo ${fruits[@]} # 输出:apple banana cherry date elderberry
3. 删除元素
使用 unset 命令删除指定索引的元素,删除后数组会出现"空洞"——索引不再连续:
fruits=("apple" "banana" "orange" "grape")
# 删除索引 2 的元素
unset fruits[2]
# 查看结果
echo ${fruits[@]} # 输出:apple banana grape
echo ${!fruits[@]} # 输出:0 1 3 (索引 2 消失了)
# 删除整个数组
unset fruits
删除元素后,原有索引会被释放,后续插入新元素时可能复用这些索引。
4. 插入元素到指定位置
在中间位置插入元素需要重新构建数组:
fruits=("apple" "banana" "orange")
# 在索引 1 位置插入 "mango"
fruits=("${fruits[0]}" "mango" "${fruits[@]:1}")
echo ${fruits[@]} # 输出:apple mango banana orange
```
---
## 五、数组遍历方法
遍历数组是脚本中最常见的操作之一,Bash 提供了多种遍历语法。
### 1. for 循环遍历(基础用法)
直接遍历数组元素是最简单的方式:
```bash
fruits=("apple" "banana" "orange")
for fruit in "${fruits[@]}"; do
echo "水果: $fruit"
done
```
输出结果逐行显示每个元素值。
### 2. for 循环遍历(索引版)
需要知道当前元素索引时,使用索引遍历:
```bash
fruits=("apple" "banana" "orange")
for i in "${!fruits[@]}"; do
echo "索引 $i 的值是: ${fruits[$i]}"
done
```
这种方式的优点是可以同时获取索引和值,适用于需要按索引处理数据的场景。
### 3. C 风格 for 循环
对于需要计数器控制的场景,可以使用 C 语言风格的循环:
```bash
fruits=("apple" "banana" "orange")
for ((i=0; i<${#fruits[@]}; i++)); do
echo "fruits[$i] = ${fruits[$i]}"
done
```
这种方式在处理复杂逻辑时更加灵活。
### 4. while 循环遍历
结合索引自增实现 while 循环遍历:
```bash
fruits=("apple" "banana" "orange")
i=0
while [ $i -lt ${#fruits[@]} ]; do
echo "fruits[$i] = ${fruits[$i]}"
((i++))
done
六、实战场景与技巧
1. 批量处理文件
数组非常适合批量管理文件路径或名称:
# 收集指定类型的文件
logs=(/var/log/*.log)
# 检查每个日志文件大小
for log in "${logs[@]}"; do
if [ -f "$log" ]; then
size=$(wc -c < "$log")
echo "$log: $size bytes"
fi
done
2. 字符串转数组
将字符串按指定分隔符拆分为数组,是处理日志、CSV 等数据的常用技巧:
# 定义逗号分隔的字符串
data="server01,server02,server03"
# 按逗号转换为数组
IFS=',' read -ra servers <<< "$data"
for server in "${servers[@]}"; do
echo "服务器: $server"
done
```
关键在于设置 `IFS`(内部字段分隔符),使其与分隔符匹配,然后用 `read -ra` 将字符串读取到数组中。
### 3. 数组去重
从数组中提取唯一值可用于数据清洗:
```bash
# 原始数组含重复值
items=("a" "b" "a" "c" "b" "d")
# 使用关联数组去重
declare -A seen
unique=()
for item in "${items[@]}"; do
if [[ -z "${seen[$item]}" ]]; then
seen[$item]=1
unique+=("$item")
fi
done
echo "${unique[@]}" # 输出:a b c d
```
### 4. 数组排序
对数组元素进行排序是常见需求,可以使用 `sort` 命令配合数组操作:
```bash
# 待排序数组
numbers=(5 2 8 1 9 3)
# 排序并转换回数组
sorted=($(printf '%s\n' "${numbers[@]}" | sort -n))
echo "${sorted[@]}" # 输出:1 2 3 5 8 9
5. 数组交集与差集
处理集合运算时,数组可以派上用场:
arr1=("a" "b" "c" "d")
arr2=("c" "d" "e" "f")
# 计算交集
commom=()
for item in "${arr1[@]}"; do
for target in "${arr2[@]}"; do
if [ "$item" = "$target" ]; then
common+=("$item")
break
fi
done
done
echo "交集: ${common[@]}" # 输出:c d
七、常见陷阱与注意事项
1. 索引越界
访问不存在的索引不会报错,而是返回空字符串,这可能导致逻辑错误:
fruits=("apple" "banana")
echo ${fruits[10]} # 空输出,无错误提示
# 安全做法:先检查索引是否存在
if [ $index -lt ${#fruits[@]} ]; then
echo ${fruits[$index]}
else
echo "索引超出范围"
fi
```
### 2. 引用与不加引号的区别
数组展开时是否加引号会导致截然不同的结果:
```bash
fruits=("apple banana" "orange")
# 加引号:保留元素内的空格
echo "${fruits[@]}" # 输出:apple banana orange (两个独立元素)
# 不加引号:空格会分割元素
echo ${fruits[@]} # 输出:apple banana orange (三个元素)
```
**始终在数组引用时使用引号**,这是防止因空格导致分割错误的关键原则。
### 3. 稀疏数组的索引问题
删除中间元素后,数组索引不再连续,遍历时需要注意:
```bash
fruits=("a" "b" "c" "d")
unset fruits[1] # 删除索引 1
# 直接遍历会跳过空洞
for fruit in "${fruits[@]}"; do
echo $fruit # 输出:a c d
done
# 遍历索引才能看到全部(包括空洞位置)
for i in "${!fruits[@]}"; do
echo "索引 $i: ${fruits[$i]}"
done
# 输出:索引 0: a,索引 2: c,索引 3: d
```
---
## 八、关联数组(补充知识点)
标准数组使用整数索引,Bash 4.0+ 支持关联数组,可以使用字符串作为键:
```bash
# 声明关联数组
declare -A user_info
# 使用字符串键赋值
user_info["name"]="张三"
user_info["age"]="30"
user_info["city"]="北京"
# 访问元素
echo "姓名: ${user_info[name]}"
echo "年龄: ${user_info[age]}"
# 列出所有键
echo "所有键: ${!user_info[@]}"
关联数组在需要键值映射的场景下非常实用,如配置管理、缓存映射等。
总结
Shell 数组的核心在于索引机制。掌握 @() 语法创建数组、理解从 0 开始的索引规则、熟悉增删改查操作,就能高效处理批量数据。记住三个要点:遍历时加引号保护空格、访问前检查索引边界、善用切片和追加操作提升代码简洁性。

暂无评论,快来抢沙发吧!