Shell脚本精读 · S03-04 | Bash 数组与 `declare`:列表数据的存取

模块 :S03 变量与参数

篇号 :S03-04 / 42

预计阅读 :40 分钟

主线:Bash


本篇目标

掌握 Bash 索引数组 的创建、读取、遍历与传参;会用 "${arr[@]}" 保留每个元素,用 ${#arr[@]} 取长度。了解 declare 的常见用法(-a 数组、-i 整数)。学完本篇,S03 模块收尾 ;下一阶段 S04 讲退出状态与布尔逻辑。


30 秒速览

  • 定义:arr=(a b c)arr[0]=a;下标从 0 开始。
  • 全部元素:"${arr[@]}" (传参、循环首选);"${arr[*]}" 合成一个参数(少用)。
  • 长度:${#arr[@]} ;单个元素:${arr[i]}
  • 保存脚本参数:arr=("$@")
  • declare -a 显式声明数组;declare -i 声明整数变量(可选)。
  • 关联数组 declare -A 本专栏点到为止(键值表,需时用再查手册)。

正文

1. 为什么需要数组

位置参数 "$@" 适合原样传递 ;若要在脚本里增删、按下标改、反复遍历一组数据,用数组更清晰。

bash 复制代码
files=("$@")              # 把命令行参数拷进数组
echo "共 ${#files[@]} 个文件"

2. 创建与赋值

bash 复制代码
# 一行定义
nums=(10 20 30)

# 按下标赋值
arr[0]="first"
arr[1]="second"
arr[10]="jump"           # 中间未赋值的元素为空

# 从命令输出填充(命令替换)
mapfile -t lines < file.txt    # 按行读入(Bash 4+)
bash 复制代码
# 空数组
empty=()

带空格的元素要加引号:

bash 复制代码
paths=("/tmp/my dir" "/var/log")

3. 读取元素

写法 含义
${arr[0]} 下标 0 的元素
${arr[@]} 全部元素,每个独立(展开后多个词)
${arr[*]} 全部元素,合成一个词(类似 $*
${#arr[@]} 元素个数
${#arr[0]} 下标 0 那一项的字符串长度
bash 复制代码
tags=(web api db)
echo "${tags[1]}"         # api
echo "个数: ${#tags[@]}"   # 3

4. 遍历数组

bash 复制代码
hosts=(db1 db2 db3)

# 推荐:按元素遍历
for h in "${hosts[@]}"; do
  echo "ping $h"
done

# 按下标遍历
for (( i = 0; i < ${#hosts[@]}; i++ )); do
  echo "$i -> ${hosts[i]}"
done

不要用未引号的 "${hosts[*]}"for 循环来源(会合成一项再被拆开)。


5. 数组与 "$@"

bash 复制代码
#!/usr/bin/env bash
set -euo pipefail

args=("$@")               # 快照一份参数

process_all() {
  printf ' <%s>' "$@"
  echo
}

process_all "${args[@]}"   # 把数组当多个参数传入函数

函数内 "$@" 来自调用时传入的 "${args[@]}",边界与脚本参数一致。

追加元素

bash 复制代码
list=(a b)
list+=("c d" e)          # 追加两个元素:"c d" 与 e

6. 切片与部分元素(了解)

bash 复制代码
arr=(a b c d e)
echo "${arr[@]:1:3}"     # b c d(从下标 1 起取 3 个元素)

这是数组切片 展开,与字符串 ${var:1:3} 语法相似,但作用在数组上。


7. declare 常用选项

bash 复制代码
declare -a indexed       # 显式索引数组
declare -i n=0           # 整数变量(算术更一致)
(( n++ ))

declare -r CONST=100     # 只读(同 readonly)
declare -x VAR=1         # 导出(同 export)
bash 复制代码
declare -a files
files=(report.log access.log)

日常 arr=(...) 已足够;declare -a 多用于可读性或动态属性。

关联数组(键值,下标为字符串):

bash 复制代码
declare -A user_email=(
  [alice]="a@example.com"
  [bob]="b@example.com"
)
echo "${user_email[alice]}"

脚本里若只需「列表」,用普通索引数组即可;declare -A 需要时再查 help declare


8. 读脚本时的数组写法

bash 复制代码
CONFIGS=("${CONFIGS[@]}" "extra.conf")   # 在原有配置上追加
cmd "${ARR[@]}"                          # 展开为多个参数
length=${#ARR[@]}

检查清单式阅读:

  • 定义是否用了 () 与引号保护含空格项。
  • 传参、循环是否 "${arr[@]}"
  • 长度是否 ${#arr[@]} 而不是 ${#arr}(后者是下标 0 字符串的长度,易混)。

9. 对照表

需求 写法
定义 arr=(a b c)
保存 $@ arr=("$@")
遍历 for x in "${arr[@]}"; do
传给命令/函数 cmd "${arr[@]}"
个数 ${#arr[@]}
追加 arr+=(item)

读脚本检查清单

  • 循环、传参是否 "${arr[@]}"
  • 含空格元素定义时是否加了引号?
  • 取长度是否 ${#arr[@]}
  • 下标 10 以上是否用 ${arr[10]}
  • 是否把关联数组 declare -A 当成普通数组用?

练习

判断题

  1. arr=(a b c) 中元素下标从 1 开始。
  2. "${arr[@]}""$@" 一样,适合原样传给子命令。
  3. ${#arr} 一定等于数组元素个数。
  4. arr=("$@") 可以在脚本开头保存命令行参数。

参考答案

  1. 错(从 0 开始)。
  2. 对。
  3. 错(${#arr} 是下标 0 项的字符串长度;个数用 ${#arr[@]})。
  4. 对。

实操题

bash 复制代码
#!/usr/bin/env bash
set -euo pipefail
words=(alpha beta gamma)
echo "个数=${#words[@]}"
for w in "${words[@]}"; do
  echo "|$w|"
done
words+=("delta echo")
echo "追加后=${#words[@]}"
for w in "${words[@]}"; do
  echo "|$w|"
done

观察追加后是否仍为两个新元素(含空格的一项算一个)。

改错题

bash 复制代码
#!/usr/bin/env bash
set -euo pipefail
items=(one "two three" four)
for x in ${items[*]}; do
  echo "$x"
done

参考

bash 复制代码
for x in "${items[@]}"; do
  echo "$x"
done

"${items[*]}"for 里会先合成再拆分,破坏 "two three" 边界。


S03 模块小结

篇号 能力
S03-01 赋值、export、readonly
S03-02 参数展开、子串、替换、大小写
S03-03 $@$#shift
S03-04 数组与 declare

下一篇预告

S04-01 :《退出状态 $?:命令成功与失败》--- 进入「判断与流程」阶段,先理解每条命令的退出码,再学条件表达式。