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