linux shell编程实战 03 数组:批量处理数据

在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 访问所有元素

有两种方式可以获取数组的所有元素:

  1. ${数组名[@]}:将每个元素作为独立的字符串(推荐,处理含空格的元素更安全)
  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 "======================"

脚本说明

  1. 使用两个数组namesscores分别存储学生姓名和成绩
  2. 通过while循环实现批量录入(下一章会详细讲解while循环)
  3. 遍历成绩数组计算总分、最高分、最低分和平均分
  4. 最后格式化输出所有学生的成绩和统计结果

运行示例

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、统计多个文件的大小等)灵活运用数组简化代码。下一章我们将学习循环结构,它与数组结合能发挥更大的威力。

相关推荐
屁股割了还要学4 小时前
【Linux入门】常用工具:yum、vim
linux·运维·服务器·c语言·c++·学习·考研
王道长服务器 | 亚马逊云4 小时前
AWS Elemental MediaConvert:视频转码不再难
linux·服务器·网络·云计算·音视频·aws
Jm_洋洋4 小时前
【Linux系统编程】程序替换:execve(execl、execlp、execle、execv、execvp、execvpe)
linux·运维·c语言·开发语言·程序人生
qq_479875434 小时前
TCP网络编程本质
服务器·网络·tcp/ip
HIT_Weston4 小时前
14、【Ubuntu】【VSCode】VSCode 断联问题分析:hostname(二)
linux·vscode·ubuntu
冲上云霄的Jayden5 小时前
bash执行脚本 CondaError: Run ‘conda init‘ before ‘conda activate‘
linux·ubuntu·conda·bash·init·activate
驱动探索者5 小时前
影石Insta360发展史:从深圳公寓到全球影像创新标杆
linux
Wang's Blog5 小时前
Linux小课堂: SSH 免密登录原理与实现之基于公钥认证的安全连接机制
linux·安全·ssh
戴草帽的大z6 小时前
交叉编译FFmpeg:从x264到RK3588部署实战
linux·ffmpeg·rk3588·h.264·aarch64