Shell数组

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 从命令输出生成数组

把命令输出的每一行当作一个元素,用 mapfilereadarray(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

十二、常见坑与建议

  1. 访问元素必须 ${arr[i]}:不能写 $arr[i]。
  2. 遍历用 “${arr[@]}”:不要用 ${arr[*]} 或 不加引号的 ${arr[@]},否则含空格元素会断成多个。
  3. 数组长度用 ${#arr[@]}:${#arr} 是第一个元素的长度。
  4. Bash 才支持数组:#!/bin/sh 下不要用;关联数组要 Bash 4+。
  5. 稀疏数组:用 “${!arr[@]}” 遍历有值的下标,避免空位。
  6. 传数组给函数:用 “${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 mm[k]=v${m[k]}"${!m[@]}"

十四、小结

  • 索引数组arr=(…)${arr[i]}“${arr[@]}”${#arr[@]}${!arr[@]}切片+= 追加;遍历用 “${arr[@]}”“${!arr[@]}”
  • 关联数组declare -Amap[key]=value${map[key]}“${!map[@]}”;需 Bash 4+。
  • 传参/返回:传数组用 “${arr[@]}”;返回“数组”可用 echo 每行一个元素 + mapfile
  • 注意 Bash 与 sh引号${#arr} 与 ${#arr[@]} 的区别。

多写几段脚本:定义数组、遍历、切片、追加、从命令读入、在函数里用 “$@” 当数组,再结合 Shell流程控制.mdShell函数.md 一起练,数组就能用熟。


文档以 Bash 为准;关联数组需 Bash 4.0+;/bin/sh 不支持数组。

发表评论