Shell 数组详解(新手详细版)
本文档面向零基础新手,从“Shell 里数组是什么、怎么用”讲起,详细说明定义、赋值、访问、长度、遍历、切片、关联数组等,并配有大量示例。
一、数组是什么?哪些 Shell 支持?
1.1 一句话理解
数组就是“一串有顺序的变量”,用一个名字加下标来访问第 1 个、第 2 个、… 个元素。
适合存一批同类数据(如多个文件名、多个主机名),再配合循环处理。
1.2 重要说明:Bash 才有数组
- Bash(以及 zsh、ksh 等)支持数组;标准 sh(如 /bin/sh)没有数组语法。
- 脚本若写 #!/bin/bash,可以使用下面全部内容;若写 #!/bin/sh,不要用数组,否则会报错。
- 本文以 Bash 为准。
1.3 两种数组
- 索引数组:下标是 0、1、2、… 的整数,最常用。
- 关联数组:下标是任意字符串(类似“键”),像其他语言里的 map/字典;需 declare -A,Bash 4+ 支持。
下面先讲索引数组,再讲关联数组。
二、索引数组:定义与赋值
2.1 直接赋值一整个数组
用 括号 () 包起来,空格分隔每个元素:
arr=(a b c d e)
- 下标从 0 开始:第 1 个元素是 arr[0],第 2 个是 arr[1],依此类推。
- 元素可以是任意字符串;若含空格,用引号包起来。
names=("张三" "李四" "王五")
files=("file1.txt" "file2.txt" "my file.txt")
2.2 逐个元素赋值
arr[0]="第一"
arr[1]="第二"
arr[2]="第三"
可以不连续赋值,中间空着的下标相当于“空位”(稀疏数组):
arr[0]="a"
arr[2]="c"
# arr[1] 未赋值,访问时为空
2.3 指定下标赋值
arr=([0]=zero [1]=one [3]=three)
# arr[0]=zero, arr[1]=one, arr[2] 空, arr[3]=three
2.4 从命令输出生成数组
把命令输出的每一行当作一个元素,用 mapfile 或 readarray(Bash 4+):
# 把 ls 的每行结果放进数组(不推荐含空格的文件名)
mapfile -t arr < <(ls)
# 或
readarray -t arr < <(ls)
# 从文件每行读入
mapfile -t lines < file.txt
-t 表示去掉每行末尾的换行符。
< <(命令) 是“进程替换”,把命令输出当文件读入。
三、访问数组元素
3.1 访问单个元素:${数组名[下标]}
arr=(hello world shell)
echo ${arr[0]}
# hello
echo ${arr[1]}
# world
echo ${arr[2]}
# shell
注意:必须写 ${arr[0]},不能写成 $arr[0](会被当成 $arr 再拼上 [0])。
3.2 访问“所有元素”:${arr[@]} 与 ${arr[*]}
- ${arr[@]}:每个元素各自一个“词”;建议用双引号 “${arr[@]}”,这样含空格的元素也不会被拆开。
- *${arr[]}:所有元素拼成一个字符串,用 IFS** 的第一个字符(默认空格)连接。
示例:
arr=("a b" "c")
echo "用 @: ${arr[@]}"
# 用 @: a b c(看起来像 3 个词)
printf ">[%s]n" "${arr[@]}"
# >[a b]
# >[c]
printf ">[%s]n" "${arr[*]}"
# >[a b c] (一整句)
结论:遍历或当多个参数传递时,用 “${arr[@]}”。
3.3 数组长度(元素个数):${#arr[@]} 或 ${#arr[*]}
arr=(a b c d)
echo ${#arr[@]}
# 4
注意:${#arr} 是 arr[0] 的字符串长度,不是数组长度;数组长度要用 ${#arr[@]}。
arr=(hello world)
echo ${#arr}
# 5(即 "hello" 的长度)
echo ${#arr[@]}
# 2(元素个数)
3.4 单个元素的长度:${#arr[下标]}
arr=("hello" "world")
echo ${#arr[0]}
# 5
四、下标列表:${!arr[@]} 或 ${!arr[*]}
${!数组名[@]} 得到所有有值的下标(稀疏数组时有用):
arr=([0]=a [2]=c [5]=f)
echo "${!arr[@]}"
# 0 2 5
遍历“有值的下标”可以写:
for i in "${!arr[@]}"; do
echo "下标 $i => ${arr[$i]}"
done
五、切片:取连续一段元素
${arr[@]:起始:长度} 表示从“起始”下标开始,取“长度”个元素;省略长度则取到末尾。
arr=(a b c d e)
echo "${arr[@]:0:3}"
# a b c
echo "${arr[@]:1:2}"
# b c
echo "${arr[@]:2}"
# c d e
六、追加元素:arr+=(…)
数组名+=(元素1 元素2 …) 在数组末尾追加:
arr=(a b c)
arr+=(d e)
echo "${arr[@]}"
# a b c d e
一次可追加多个;若元素含空格,用引号:
arr+=("new item")
七、遍历数组
7.1 只关心“值”:for i in “${arr[@]}”
arr=("one" "two" "three")
for item in "${arr[@]}"; do
echo "$item"
done
# one
# two
# three
务必用 “${arr[@]}”,这样每个元素保持完整(含空格也没问题)。
7.2 需要“下标”:for i in “${!arr[@]}”
arr=(a b c)
for i in "${!arr[@]}"; do
echo "下标 $i: ${arr[$i]}"
done
# 下标 0: a
# 下标 1: b
# 下标 2: c
7.3 C 风格循环(按数字下标)
arr=(a b c)
for (( i=0; i<${#arr[@]}; i++ )); do
echo "${arr[$i]}"
done
7.4 稀疏数组的遍历
只有 “${!arr[@]}” 会列出“有值的下标”,不会遍历到空位:
arr=([0]=x [2]=z)
for i in "${!arr[@]}"; do
echo "$i => ${arr[$i]}"
done
# 0 => x
# 2 => z
八、数组与函数(传参、返回)
8.1 把数组“展开”传给函数
函数无法直接“接收一个数组变量”,只能把元素展开成多个参数,在函数里用 $@ 或 $1、$2… 接收;若要在函数里再当数组用,可重新赋值:
my_func() {
local my_arr=("$@")
echo "收到 ${#my_arr[@]} 个元素"
echo "${my_arr[@]}"
}
arr=(a b c)
my_func "${arr[@]}"
# 收到 3 个元素
# a b c
关键:调用时写 “${arr[@]}”,这样每个元素是一个参数。
8.2 “返回”数组(用 echo + 读入)
Shell 不能直接 return 一个数组,可以约定用一行一个元素 echo 出来,调用方用 mapfile 读成数组:
get_list() {
echo "one"
echo "two"
echo "three"
}
mapfile -t result < <(get_list)
echo "${result[@]}"
# one two three
九、关联数组(键值对,Bash 4+)
9.1 声明与赋值
必须先 declare -A 名字,再赋值;下标是字符串(键),值是字符串。
declare -A map
map["name"]="张三"
map["age"]="25"
map["city"]="北京"
或一次写多个:
declare -A map=(["a"]=1 ["b"]=2 ["c"]=3)
9.2 访问
echo ${map["name"]}
# 张三
echo ${map["age"]}
# 25
9.3 遍历键:${!map[@]}
for key in "${!map[@]}"; do
echo "$key => ${map[$key]}"
done
9.4 遍历值:${map[@]}
for val in "${map[@]}"; do
echo "$val"
done
9.5 键的个数
echo ${#map[@]}
注意:若系统 Bash 版本低于 4(如 macOS 自带 bash),没有 declare -A,关联数组不可用;可用 awk 或外部语言代替。
十、declare -a(显式声明索引数组)
declare -a 名字 表示“这是索引数组”;通常可省略,直接 arr=(…) 即可。
显式声明有时用于在函数里建局部数组:
func() {
declare -a local_arr=(1 2 3)
echo "${local_arr[@]}"
}
十一、实用示例汇总
11.1 存一批文件名并逐个处理
files=(*.txt)
for f in "${files[@]}"; do
echo "处理: $f"
cp "$f" "$f.bak"
done
11.2 从一行或多行读入数组
# 一行,空格分隔
line="a b c"
arr=($line)
# 注意:若元素含空格会拆坏,此时可用 read -a
read -a arr <<< "a b c"
11.3 判断某值是否在数组中
arr=(a b c)
target="b"
for x in "${arr[@]}"; do
if [ "$x" = "$target" ]; then
echo "找到了"
break
fi
done
11.4 数组合并(追加)
a=(1 2)
b=(3 4)
a+=("${b[@]}")
echo "${a[@]}"
# 1 2 3 4
11.5 用关联数组统计词频(Bash 4+)
declare -A count
for word in a b a c b a; do
(( count[$word]++ ))
done
for k in "${!count[@]}"; do
echo "$k: ${count[$k]}"
done
十二、常见坑与建议
- 访问元素必须 ${arr[i]}:不能写 $arr[i]。
- 遍历用 “${arr[@]}”:不要用 ${arr[*]} 或 不加引号的 ${arr[@]},否则含空格元素会断成多个。
- 数组长度用 ${#arr[@]}:${#arr} 是第一个元素的长度。
- Bash 才支持数组:#!/bin/sh 下不要用;关联数组要 Bash 4+。
- 稀疏数组:用 “${!arr[@]}” 遍历有值的下标,避免空位。
- 传数组给函数:用 “${arr[@]}”;函数里用 local arr=(“$@”) 再当数组用。
十三、语法速查表
| 操作 | 写法 |
|---|---|
| 定义 | arr=(a b c)、arr=([0]=a [2]=c) |
| 单元素赋值 | arr[0]=x |
| 访问元素 | ${arr[0]}、${arr[i]} |
| 所有元素(遍历/传参) | "${arr[@]}" |
| 所有元素(一个字符串) | "${arr[*]}" |
| 元素个数 | ${#arr[@]} |
| 单元素长度 | ${#arr[i]} |
| 下标列表 | "${!arr[@]}" |
| 切片 | ${arr[@]:起:长}、${arr[@]:起} |
| 追加 | arr+=(x y) |
| 关联数组 | declare -A m;m[k]=v;${m[k]};"${!m[@]}" |
十四、小结
- 索引数组:arr=(…)、${arr[i]}、“${arr[@]}”、${#arr[@]}、${!arr[@]}、切片、+= 追加;遍历用 “${arr[@]}” 或 “${!arr[@]}”。
- 关联数组:declare -A、map[key]=value、${map[key]}、“${!map[@]}”;需 Bash 4+。
- 传参/返回:传数组用 “${arr[@]}”;返回“数组”可用 echo 每行一个元素 + mapfile。
- 注意 Bash 与 sh、引号、${#arr} 与 ${#arr[@]} 的区别。
多写几段脚本:定义数组、遍历、切片、追加、从命令读入、在函数里用 “$@” 当数组,再结合 Shell流程控制.md、Shell函数.md 一起练,数组就能用熟。
文档以 Bash 为准;关联数组需 Bash 4.0+;/bin/sh 不支持数组。