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 脚本的效率能提一大截,重复代码也能少写不少。