🔍搜索⌘K

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 脚本的效率,减少重复代码。