在Shell编程中,当需要处理一系列相关数据(如多个文件名、成绩、IP地址等)时,使用单个变量会非常繁琐。数组(Array)正是为解决这类问题而设计的------它可以将多个相关数据存储在一个变量中,实现批量管理和操作。本章将详细介绍Shell数组的定义、使用及实用技巧,让你轻松应对批量数据处理场景。
3.1 认识数组:什么是数组及为什么需要数组
3.1.1 数组的基本概念
数组是一种可以存储多个值的变量,这些值称为"元素",每个元素通过"索引"(位置编号)来访问。
可以用生活中的例子理解数组:
- 数组 = 一排储物柜(多个存储单元的集合)
- 元素 = 储物柜中的物品(每个位置存放的数据)
- 索引 = 储物柜编号(从0开始,用于定位元素)
例如,存储3个学生姓名的数组:
plain
索引:0 1 2
元素:张三 李四 王五
3.1.2 为什么需要数组
假设需要处理5个成绩,如果用单个变量:
bash
score1=90
score2=85
score3=95
score4=78
score5=88
如果要计算平均分,需要逐个引用变量,代码冗长且易错。
而用数组存储:
bash
scores=(90 85 95 78 88)
通过循环可以轻松遍历所有元素,大幅简化代码。
数组的核心优势:
- 集中管理多个相关数据,避免变量名泛滥
- 结合循环可高效批量处理元素
- 便于通过索引快速定位和操作元素
3.2 数组的定义与初始化:创建你的第一个数组
Shell数组的定义非常灵活,不需要声明长度,元素类型也可以混合(字符串、数字等)。
3.2.1 基本定义方式
语法:
bash
数组名=(元素1 元素2 元素3 ...)
示例:
bash
# 定义存储姓名的数组
names=("张三" "李四" "王五" "赵六")
# 定义存储数字的数组
ages=(20 22 21 19)
# 混合类型数组(不推荐,但允许)
mixed=("apple" 3.14 100 "banana")
注意:
- 元素之间用空格分隔
- 字符串元素如果包含空格,需要用双引号括起来(如
("张 三" "李 四")) - 数组名的命名规则与变量相同(字母、数字、下划线,不能以数字开头)
3.2.2 单独定义数组元素
可以先声明数组,再为每个元素赋值:
bash
# 声明空数组
fruits=()
# 为指定索引赋值(索引从0开始)
fruits[0]="苹果"
fruits[1]="香蕉"
fruits[2]="橙子"
# 索引可以不连续(中间跳过的索引会被视为空元素)
fruits[5]="葡萄" # 索引2和5之间的3、4为空
3.2.3 从命令输出创建数组
通过命令替换,可以将命令的输出结果直接作为数组元素:
bash
# 将当前目录下的所有.sh文件作为数组元素
scripts=(*.sh)
# 将ls命令的输出(按行)作为数组元素
files=($(ls))
# 查看数组内容(后续会讲解)
echo "${scripts[@]}"
3.3 数组元素的访问与修改:操作数组中的数据
定义数组后,需要掌握如何访问、修改和删除元素,这是数组操作的基础。
3.3.1 访问单个元素
语法:
bash
${数组名[索引]}
示例:
bash
names=("张三" "李四" "王五")
# 访问第一个元素(索引0)
echo "第一个人:${names[0]}" # 输出:第一个人:张三
# 访问第二个元素(索引1)
echo "第二个人:${names[1]}" # 输出:第二个人:李四
注意:Shell数组的索引从0开始(即第一个元素的索引是0,第二个是1,以此类推)。
3.3.2 访问所有元素
有两种方式可以获取数组的所有元素:
${数组名[@]}:将每个元素作为独立的字符串(推荐,处理含空格的元素更安全)${数组名[*]}:将所有元素合并为一个字符串(元素之间用空格分隔)
示例:
bash
fruits=("苹果" "香蕉" "草莓")
# 方式1:@符号(推荐)
echo "所有水果:${fruits[@]}"
# 输出:所有水果:苹果 香蕉 草莓
# 方式2:*符号
echo "所有水果:${fruits[*]}"
# 输出:所有水果:苹果 香蕉 草莓(表面看与@相同,但本质不同)
# 区别演示(循环中体现)
echo "使用@遍历:"
for f in "${fruits[@]}"; do
echo "- $f"
done
echo "使用*遍历:"
for f in "${fruits[*]}"; do
echo "- $f"
done
输出结果:
plain
使用@遍历:
- 苹果
- 香蕉
- 草莓
使用*遍历:
- 苹果 香蕉 草莓 # 被当作单个元素处理
结论 :遍历数组时优先使用${数组名[@]},尤其是元素包含空格时。
3.3.3 修改数组元素
通过索引可以直接修改数组中的元素:
bash
scores=(90 85 78)
# 修改第二个元素(索引1)
scores[1]=88
echo "修改后的值:${scores[1]}" # 输出:修改后的值:88
# 输出所有元素验证
echo "所有成绩:${scores[@]}" # 输出:所有成绩:90 88 78
3.3.4 删除数组元素
使用unset命令可以删除数组中的指定元素或整个数组:
bash
names=("张三" "李四" "王五")
# 删除第二个元素(索引1)
unset names[1]
echo "删除后:${names[@]}" # 输出:删除后:张三 王五(注意:索引1被移除,但后续元素索引不变)
# 删除整个数组
unset names
echo "删除数组后:${names[@]}" # 输出空(数组已不存在)
注意:删除元素后,数组的长度会减小,但剩余元素的索引不会自动重新排列(即索引可能出现断层)。
3.4 数组的长度与切片:获取数组的大小和部分元素
在处理数组时,经常需要知道数组包含多少元素(长度),或获取其中的一部分元素(切片)。
3.4.1 获取数组长度
语法:
bash
${#数组名[@]} # 或 ${#数组名[*]}
示例:
bash
students=("张三" "李四" "王五" "赵六")
# 获取数组长度
len=${#students[@]}
echo "学生人数:$len" # 输出:学生人数:4
# 结合索引遍历数组(常用写法)
for ((i=0; i<${#students[@]}; i++)); do
echo "第$((i+1))个学生:${students[i]}"
done
输出结果:
plain
第1个学生:张三
第2个学生:李四
第3个学生:王五
第4个学生:赵六
3.4.2 数组切片:获取部分元素
语法:
bash
${数组名[@]:起始索引:长度}
- 起始索引:从哪个位置开始(从0开始)
- 长度:要获取的元素个数(可选,默认获取到数组末尾)
示例:
bash
nums=(10 20 30 40 50 60)
# 获取从索引1开始的3个元素
echo "${nums[@]:1:3}" # 输出:20 30 40
# 获取从索引2开始到末尾的元素
echo "${nums[@]:2}" # 输出:30 40 50 60
# 获取最后2个元素(起始索引为负数表示从末尾开始)
echo "${nums[@]: -2}" # 输出:50 60(注意:-2前有空格)
应用场景:
- 分页显示数据(如每次显示10个元素)
- 处理数组的前N个或后N个元素
- 截取数组的中间部分进行分析
3.5 数组的遍历与常用操作:批量处理数组元素
数组的核心价值在于批量处理元素,结合循环可以高效操作数组中的所有数据。
3.5.1 用for循环遍历数组
方式1:通过索引遍历(推荐,可获取索引)
bash
colors=("红" "绿" "蓝" "黄")
# 获取数组长度
len=${#colors[@]}
# 从索引0循环到最后一个索引
for ((i=0; i<len; i++)); do
echo "索引$i:${colors[i]}"
done
输出结果:
plain
索引0:红
索引1:绿
索引2:蓝
索引3:黄
方式2:直接遍历元素(简洁,无需关心索引)
bash
fruits=("苹果" "香蕉" "橙子")
# 直接遍历所有元素
for fruit in "${fruits[@]}"; do
echo "水果:$fruit"
done
输出结果:
plain
水果:苹果
水果:香蕉
水果:橙子
3.5.2 向数组添加元素
数组没有固定长度,可以随时添加新元素:
方式1:在数组末尾添加
bash
animals=("狗" "猫")
# 末尾添加元素(索引自动递增)
animals+=("鸡")
animals+=("鸭" "鹅") # 一次添加多个
echo "所有动物:${animals[@]}" # 输出:所有动物:狗 猫 鸡 鸭 鹅
方式2:指定索引添加(可能导致索引断层)
bash
nums=(1 2 3)
nums[5]=6 # 在索引5添加元素(索引3、4为空)
echo "${nums[@]}" # 输出:1 2 3 6(空元素不显示)
3.5.3 数组元素的拼接与合并
将两个数组合并为一个新数组:
bash
# 定义两个数组
arr1=(1 2 3)
arr2=(4 5 6)
# 合并数组(将arr2的元素添加到arr1)
arr3=("${arr1[@]}" "${arr2[@]}")
echo "合并结果:${arr3[@]}" # 输出:合并结果:1 2 3 4 5 6
3.5.4 数组元素的查找
判断某个值是否在数组中:
bash
students=("张三" "李四" "王五")
target="李四"
found=0 # 标记是否找到(0:未找到,1:找到)
# 遍历数组查找目标
for student in "${students[@]}"; do
if [ "$student" = "$target" ]; then
found=1
break # 找到后跳出循环
fi
done
if [ $found -eq 1 ]; then
echo "$target 在数组中"
else
echo "$target 不在数组中"
fi
输出结果:
plain
李四 在数组中
3.6 多维数组简介:处理更复杂的数据结构
Shell(bash 4.3+)支持简单的多维数组(数组中的元素也是数组),适合存储表格类数据(如矩阵、二维表格)。
3.6.1 定义多维数组
语法:
bash
declare -A 二维数组名 # 声明二维数组(注意:需要用declare -A)
二维数组名[行索引,列索引]=值
示例:定义一个2行3列的成绩表
bash
# 声明二维数组
declare -A scores
# 赋值(行索引从0开始,列索引从0开始)
scores[0,0]="张三"
scores[0,1]=90
scores[0,2]=85
scores[1,0]="李四"
scores[1,1]=95
scores[1,2]=88
3.6.2 访问多维数组元素
bash
# 访问张三的数学成绩(第0行第2列)
echo "张三的数学成绩:${scores[0,2]}" # 输出:张三的数学成绩:85
# 遍历二维数组(需要知道行数和列数)
rows=2
cols=3
for ((i=0; i<rows; i++)); do
echo -n "第$i行:"
for ((j=0; j<cols; j++)); do
echo -n "${scores[$i,$j]} "
done
echo # 换行
done
输出结果:
plain
第0行:张三 90 85
第1行:李四 95 88
注意:Shell的多维数组功能有限,不支持自动获取行列数,复杂场景建议使用其他语言(如Python)。实际Shell编程中,一维数组更为常用。
3.7 实战示例:学生成绩管理脚本
综合运用数组知识,编写一个学生成绩管理脚本,实现以下功能:
- 录入多个学生的成绩
- 计算平均分、最高分、最低分
- 显示所有学生的成绩和统计结果
bash
#!/bin/bash
# 学生成绩管理脚本
# 初始化存储姓名和成绩的数组
names=()
scores=()
# 录入学生信息
echo "===== 学生成绩录入 ====="
while true; do
read -p "请输入学生姓名(输入q结束):" name
if [ "$name" = "q" ]; then
break # 输入q则结束录入
fi
# 验证成绩是否有效
while true; do
read -p "请输入${name}的成绩(0-100):" score
if [ $score -ge 0 ] && [ $score -le 100 ]; then
break # 成绩有效,跳出循环
else
echo "错误:成绩必须在0-100之间,请重新输入"
fi
done
# 将姓名和成绩添加到数组
names+=("$name")
scores+=("$score")
done
# 检查是否有数据
if [ ${#names[@]} -eq 0 ]; then
echo "未录入任何学生信息"
exit 0
fi
# 计算统计数据
total=0
max=${scores[0]}
min=${scores[0]}
# 遍历成绩数组
for ((i=0; i<${#scores[@]}; i++)); do
total=$((total + scores[i])) # 累加总分
# 更新最高分
if [ ${scores[i]} -gt $max ]; then
max=${scores[i]}
fi
# 更新最低分
if [ ${scores[i]} -lt $min ]; then
min=${scores[i]}
fi
done
# 计算平均分
avg=$((total / ${#scores[@]}))
# 显示结果
echo -e "\n===== 成绩统计结果 ====="
echo "学生名单及成绩:"
for ((i=0; i<${#names[@]}; i++)); do
echo "${names[i]}:${scores[i]}"
done
echo -e "\n统计信息:"
echo "总人数:${#names[@]}"
echo "总分:$total"
echo "平均分:$avg"
echo "最高分:$max(${names[scores[@]=$max]})" # 查找最高分对应的姓名
echo "最低分:$min(${names[scores[@]=$min]})"
echo "======================"
脚本说明:
- 使用两个数组
names和scores分别存储学生姓名和成绩 - 通过
while循环实现批量录入(下一章会详细讲解while循环) - 遍历成绩数组计算总分、最高分、最低分和平均分
- 最后格式化输出所有学生的成绩和统计结果
运行示例:
plain
===== 学生成绩录入 =====
请输入学生姓名(输入q结束):张三
请输入张三的成绩(0-100):90
请输入学生姓名(输入q结束):李四
请输入李四的成绩(0-100):85
请输入学生姓名(输入q结束):王五
请输入王五的成绩(0-100):95
请输入学生姓名(输入q结束):q
===== 成绩统计结果 =====
学生名单及成绩:
张三:90
李四:85
王五:95
统计信息:
总人数:3
总分:270
平均分:90
最高分:95(王五)
最低分:85(李四)
======================
小结
本章详细介绍了Shell数组的使用,主要内容包括:
- 数组的基本概念:数组是存储多个元素的变量,通过索引访问,适合批量数据管理
- 数组的定义与初始化:直接赋值、单独定义元素、从命令输出创建数组
- 元素操作:访问单个/所有元素、修改元素、删除元素
- 数组属性:获取长度、切片(获取部分元素)
- 常用操作:遍历数组(for循环)、添加元素、合并数组、查找元素
- 多维数组:简单介绍二维数组的定义和访问(适合表格类数据)
数组是Shell处理批量数据的核心工具,结合下一章将要学习的循环结构,可以实现高效的批量操作(如文件批量处理、数据统计等)。
掌握数组的关键是多练习遍历和批量操作,特别是在实际场景中(如处理日志中的多个IP、统计多个文件的大小等)灵活运用数组简化代码。下一章我们将学习循环结构,它与数组结合能发挥更大的威力。