Linux Shell 数组完全指南:从基础用法到实战技巧
概述
在 Shell 脚本中,普通变量一次只能存储一个值(如 name="roger"),而 数组(Array) 能存储多个值(如 myArr=(zero one two)),支持按索引或键名快速访问、修改元素,是处理批量数据的核心工具。
日常开发中,数组常用于:
- 批量管理同类数据(如服务器 IP 列表、文件路径集合);
- 循环遍历多个元素(如批量执行命令、处理日志文件);
- 关联式存储(如键值对形式的配置信息,类似 Python 字典)。
Shell 数组分为两种:普通数组(按数字索引访问,索引从 0 开始)和 关联数组(按自定义键名访问,如 key="name"),下文将详细讲解两种数组的完整用法。
一、普通数组:基础用法(按数字索引访问)
普通数组是 Shell 中最常用的数组类型,通过「数字索引」定位元素,索引从 0 开始(即第一个元素索引为 0,第二个为 1,以此类推)。
1. 定义普通数组(3 种常见方式)
方式 1:直接初始化(推荐,简洁高效)
将多个值用圆括号包裹,值之间用空格分隔(不能用逗号):
# 定义数组:索引 0=zero,1=one,2=two,3=three,4=four
myArr=(zero one two three four)
方式 2:通过 declare -a 声明后赋值
先用 declare -a 显式声明数组(-a 表示「普通数组」),再逐个或批量赋值:
# 声明普通数组
declare -a myArr
# 单个赋值(索引可跳号,如直接赋值索引 2)
myArr[0]=zero
myArr[2]=two
# 批量赋值(补充索引 1、3、4)
myArr+=(one three four)
方式 3:从变量或命令结果初始化
将字符串按空格分割为数组,或通过命令输出结果构建数组:
# 从字符串初始化(字符串中的空格会分割为数组元素)
str="zero one two"
myArr=($str)
# 从命令结果初始化(如获取当前目录下的所有 .sh 文件)
myArr=($(ls *.sh))
2. 访问数组元素(单个 / 多个 / 全部)
访问单个元素:${数组名[索引]}
通过「数组名 + 数字索引」访问指定元素,索引从 0 开始:
# 定义数组
myArr=(zero one two three four)
# 访问索引 2 的元素(输出:two)
echo ${myArr[2]}
# 访问索引 0 的元素(输出:zero)
echo ${myArr[0]}
访问全部元素:${数组名[*]} 或 ${数组名[@]}
两种写法都能获取数组所有元素,但在双引号包裹时行为不同(重点区别):
- ${myArr[*]}:将所有元素合并为「单个字符串」,元素间用空格分隔;
- ${myArr[@]}:保持元素独立性,返回「多个独立字符串」(循环遍历推荐用这种)。
示例:
myArr=(zero one two)
# 输出:zero one two(合并为单个字符串)
echo ${myArr[*]}
# 输出:zero one two(外观相同,但本质是独立元素)
echo ${myArr[@]}
# 双引号包裹时的区别(循环中体现)
echo "用 * 遍历:"
for elem in "${myArr[*]}"; do
echo "- $elem" # 输出 1 行:- zero one two(视为单个元素)
done
echo "用 @ 遍历:"
for elem in "${myArr[@]}"; do
echo "- $elem" # 输出 3 行:分别打印 zero、one、two(视为独立元素)
done
3. 修改、添加、删除数组元素
修改元素:直接重新赋值索引
myArr=(zero one two)
# 将索引 2 的元素从 two 改为 2
myArr[2]=2
# 输出:zero one 2(修改生效)
echo ${myArr[@]}
添加元素:数组名+=(元素 1 元素 2 ...)
用 += 符号批量添加元素,无需指定索引(自动追加到数组末尾):
myArr=(zero one two)
# 追加元素 three、four、five
myArr+=(three four five)
# 输出:zero one two three four five(添加生效)
echo ${myArr[@]}
删除元素:unset 数组名[索引]
用 unset 命令删除指定索引的元素,注意:删除后数组长度不会缩减,被删除的位置会变为空值(而非元素前移):
myArr=(zero one two three)
# 删除索引 2 的元素(two)
unset myArr[2]
# 输出:zero one three(索引 2 为空,用空格占位)
echo ${myArr[@]}
# 查看数组所有索引(验证:索引 2 仍存在但值为空)
echo ${!myArr[@]} # 输出:0 1 3
4. 获取数组长度:${#数组名[@]} 或 ${#数组名[*]}
用 # 符号获取数组长度(即元素个数,空值元素不计入长度):
myArr=(zero one two three four)
# 输出:5(数组有 5 个元素)
echo ${#myArr[@]}
# 删除索引 2 后,长度变为 4(空值不计入)
unset myArr[2]
echo ${#myArr[@]} # 输出:4
二、关联数组:键值对存储(按自定义键名访问)
普通数组依赖数字索引,而 关联数组(Associative Array) 支持用「自定义键名」(如字符串、数字)存储数据,类似 Python 的字典、JavaScript 的对象,适合存储键值对形式的配置(如用户信息、系统参数)。
1. 定义关联数组(必须先声明)
关联数组不能直接初始化,必须先用 declare -A 显式声明(-A 表示「关联数组」),再赋值:
# 声明关联数组(关键:必须先执行这一步)
declare -A myObj
# 方式 1:单个赋值(键名无需引号,值有空格时需加引号)
myObj[name]="roger"
myObj[age]=18
myObj[city]="Beijing"
# 方式 2:批量初始化(键值对用括号包裹,键名可加引号)
declare -A myObj=(
[name]="roger"
[age]=18
[city]="Beijing"
)
2. 访问关联数组(按键名 / 所有键 / 所有值)
访问单个值:${数组名[键名]}
通过自定义键名访问对应值,键名区分大小写(如 name 和 Name 是两个不同键):
declare -A myObj=(
[name]="roger"
[age]=18
)
# 输出:roger(访问 name 键的值)
echo ${myObj[name]}
# 输出:18(访问 age 键的值)
echo ${myObj[age]}
访问所有键名:${!数组名[@]}
用 ! 符号获取关联数组的所有键名(普通数组也可用此方式获取所有索引):
declare -A myObj=(
[name]="roger"
[age]=18
[city]="Beijing"
)
# 输出:name age city(所有键名,顺序不固定)
echo ${!myObj[@]}
访问所有值:${数组名[@]} 或 ${数组名[*]}
与普通数组一致,@ 保持元素独立性,* 合并为单个字符串:
# 输出:roger 18 Beijing(所有值)
echo ${myObj[@]}
3. 修改、添加、删除关联数组元素
修改元素:重新赋值键名
declare -A myObj=(
[name]="roger"
[age]=18
)
# 将 age 键的值从 18 改为 20
myObj[age]=20
# 输出:20(修改生效)
echo ${myObj[age]}
添加元素:直接赋值新键名
关联数组添加元素无需特殊语法,直接给新键名赋值即可:
declare -A myObj=(
[name]="roger"
[age]=18
)
# 添加新键 hobby,值为 reading
myObj[hobby]="reading"
# 输出:roger 18 reading(添加生效)
echo ${myObj[@]}
删除元素:unset 数组名[键名]
declare -A myObj=(
[name]="roger"
[age]=18
[city]="Beijing"
)
# 删除 city 键
unset myObj[city]
# 输出:roger 18(city 已删除)
echo ${myObj[@]}
三、Shell 数组实战:3 个常用场景示例
掌握基础用法后,通过实战案例理解数组的实际价值:
场景 1:批量管理服务器 IP,执行远程命令
需求:对多个服务器执行 uptime 命令(查看系统运行时间),用数组存储服务器 IP:
#!/bin/bash
# 定义服务器 IP 数组
servers=(
"192.168.1.100"
"192.168.1.101"
"192.168.1.102"
)
# 遍历数组,批量执行远程命令
for ip in "${servers[@]}"; do
echo "=== 服务器 $ip 的运行状态 ==="
ssh user@$ip "uptime" # 替换 user 为实际用户名
echo -e "\n" # 空行分隔结果
done
场景 2:用关联数组存储用户信息,批量打印
需求:存储 3 个用户的姓名、年龄、城市,循环打印每个用户的完整信息:
#!/bin/bash
# 声明关联数组(3 个用户,用后缀区分)
declare -A user1=(
[name]="Alice"
[age]=25
[city]="Shanghai"
)
declare -A user2=(
[name]="Bob"
[age]=30
[city]="Guangzhou"
)
declare -A user3=(
[name]="Charlie"
[age]=28
[city]="Shenzhen"
)
# 定义用户数组(存储关联数组名)
users=(user1 user2 user3)
# 遍历用户,打印信息
for user in "${users[@]}"; do
# 用间接引用(${!user[name]})访问关联数组的值
echo "姓名:${!user[name]}"
echo "年龄:${!user[age]}"
echo "城市:${!user[city]}"
echo -e "----------------\n"
done
场景 3:统计日志文件中关键词出现次数
需求:统计 access.log 中 3 个关键词(200、404、500)的出现次数,用关联数组存储结果:
#!/bin/bash
# 声明关联数组,存储关键词和次数
declare -A count=(
[200]=0
[404]=0
[500]=0
)
# 遍历关键词,统计次数
for code in "${!count[@]}"; do
# 用 grep -c 统计关键词出现次数,赋值给关联数组
count[$code]=$(grep -c "$code" access.log)
done
# 打印统计结果
echo "日志关键词统计:"
for code in "${!count[@]}"; do
echo "$code 状态码出现次数:${count[$code]}"
done
四、常见问题与避坑指南
问题现象 | 可能原因 | 解决方案 |
---|---|---|
关联数组赋值报错 “invalid arithmetic operator” | 未用 declare -A 声明关联数组 | 必须先执行 declare -A 数组名,再赋值(关联数组不能直接初始化)。 |
数组元素含空格时,遍历出现错乱 | 用 ${数组名[*]} 遍历,或未加双引号 | 遍历数组必须用 ${数组名[@]} 且加双引号(如 for elem in "${myArr[@]}"),确保含空格的元素被视为整体。 |
普通数组索引跳号后,长度计算错误 | 空值元素不计入数组长度 | 用 echo ${!myArr[@]} 查看所有有效索引,或删除空值后重新构建数组(如 myArr=("${myArr[@]}"))。 |
关联数组键名区分大小写,导致访问不到值 | 键名大小写不一致(如 Name 和 name) | 统一键名大小写规范(如全部小写),或访问时严格匹配定义的键名。 |
五、总结:Shell 数组核心知识点
数组类型 | 定义方式 | 访问方式 | 核心操作(修改 / 添加 / 删除) | 适用场景 |
---|---|---|---|---|
普通数组 | myArr=(a b c) 或 declare -a | ${myArr[索引]}、${myArr[@]} | 修改:myArr[0]=x;添加:myArr+=(d);删除:unset myArr[0] | 批量有序数据(如 IP 列表) |
关联数组 | 必须 declare -A 后赋值 | ${myArr[键名]}、${!myArr[@]} | 修改:myArr[key]=x;添加:myArr[newKey]=y;删除:unset myArr[key] | 键值对数据(如配置信息) |
Shell 数组虽不如 Python、Java 的数组功能丰富,但足以满足 Linux 运维中的批量处理需求。掌握「普通数组遍历」和「关联数组键值对存储」,能大幅提升 Shell 脚本的效率,减少重复代码。