Shell 数组(保姆级教程)

Shell 数组:掌握命令行编程的利器

你有没有在写 Shell 脚本时,遇到需要处理多个文件名、配置项或日志记录的情况?如果一个个变量去定义,代码会变得冗长难维护。这时候,Shell 数组就是你最得力的助手。它就像一个可伸缩的“小抽屉”,能一次装下多个数据,还能按顺序快速取出,特别适合处理批量任务。

Shell 数组是 Bash(Bourne-Again SHell)中非常实用的功能,它允许你将一组相关的值组织在一起,用一个名字统一管理。无论是备份多个目录、遍历日志文件,还是批量修改配置,Shell 数组都能让你的脚本更简洁、更高效。

接下来,我们从基础到进阶,一步步带你掌握 Shell 数组的核心用法。


创建数组与初始化

在 Shell 中,创建数组非常简单,只需要使用圆括号 (),并用空格分隔元素即可。注意,数组的索引从 0 开始,这一点和很多编程语言一致。

fruits=(apple banana orange grape mango)

你也可以在定义时指定某个索引的值,比如:

numbers[0]=10
numbers[2]=30
numbers[3]=40

这种写法适合你只想给某些特定位置赋值的场景,比如统计某个日志的错误码分布。

💡 小贴士:Shell 数组是“稀疏”的,也就是说,你不需要连续赋值,中间可以跳过。但访问时要小心,空索引可能引发意外行为。


访问数组元素

要读取数组中的某个值,使用 ${数组名[索引]} 的语法。索引必须是数字。

echo ${fruits[0]}    # 输出:apple

echo ${fruits[2]}    # 输出:orange

last_index=$(( ${#fruits[@]} - 1 ))
echo ${fruits[last_index]}   # 输出:mango

这里 ${#fruits[@]} 是一个非常有用的表达式,它返回数组中元素的总数。

📌 提示:@ 表示所有元素,* 也表示所有元素,但两者在某些场景下行为不同。建议使用 @ 更安全。


遍历数组

遍历数组是处理数据最常见的操作之一。Shell 提供了 for 循环的简化语法,让你轻松遍历每个元素。

for fruit in "${fruits[@]}"; do
    echo "我喜欢的水果是:$fruit"
done

这里的 "{fruits[@]}" 是关键。必须加双引号,否则当元素中包含空格时会出错。比如如果某个水果是 "red apple",不加引号就会被当作两个元素。

for i in "${!fruits[@]}"; do
    echo "索引 $i 的水果是:${fruits[i]}"
done

${!fruits[@]} 表示获取数组的所有索引。这个语法在你需要知道元素位置时特别有用。


数组操作:增删改查

Shell 数组支持动态操作,就像你用 Python 或 JavaScript 一样灵活。

添加元素

fruits+=("kiwi")

echo ${fruits[@]}   # 输出:apple banana orange grape mango kiwi

+= 操作符可以将新元素追加到数组末尾,非常方便。

修改元素

fruits[1]="strawberry"

echo ${fruits[@]}   # 输出:apple strawberry orange grape mango kiwi

删除元素

删除某个元素,直接赋值为空即可:

fruits[2]=""

echo ${fruits[@]}   # 输出:apple strawberry  grape mango kiwi

如果你想真正“移除”一个元素,需要借助子shell或重新赋值:

temp_array=()
for i in "${!fruits[@]}"; do
    if [ "$i" -ne 2 ]; then
        temp_array+=("${fruits[i]}")
    fi
done
fruits=("${temp_array[@]}")

虽然略复杂,但这是 Shell 中删除元素的标准做法。


常见实战案例

案例一:批量备份多个目录

假设你要备份 /home/user/docs/var/log/etc/config 三个目录,可以用数组管理路径。

backup_dirs=(
    "/home/user/docs"
    "/var/log"
    "/etc/config"
)

for dir in "${backup_dirs[@]}"; do
    if [ -d "$dir" ]; then
        echo "正在备份:$dir"
        tar -czf "${dir##*/}.tar.gz" "$dir"
        echo "备份完成:${dir##*/}.tar.gz"
    else
        echo "警告:目录 $dir 不存在,跳过。"
    fi
done

这里 ${dir##*/} 是一个 Bash 特性,用于提取路径的文件名部分,比如 /etc/config 会变成 config

案例二:读取配置文件并加载到数组

假设你有一个配置文件 config.txt,每行一个键值对,格式为 key=value

declare -a config_array
while IFS='=' read -r key value; do
    # 跳过空行和注释
    [[ -z "$key" || "$key" =~ ^# ]] && continue
    config_array+=("$key=$value")
done < config.txt

for item in "${config_array[@]}"; do
    echo "$item"
done

这个例子展示了如何结合文件读取和数组,构建灵活的配置加载逻辑。


数组的常见陷阱与最佳实践

陷阱一:忘记加双引号

for fruit in ${fruits[@]}; do
    echo "$fruit"
done

for fruit in "${fruits[@]}"; do
    echo "$fruit"
done

如果不加双引号,当元素中有空格时,Shell 会错误地将一个元素拆成多个。比如 red apple 会被当成两个元素。

陷阱二:索引越界

echo ${fruits[5]}   # 输出为空,不会报错,但结果不可控

建议在访问前检查索引范围:

array_length=${#fruits[@]}
if [ $index -lt $array_length ]; then
    echo "值是:${fruits[index]}"
else
    echo "索引超出范围"
fi

最佳实践

  • declare -a 显式声明数组,增强可读性。
  • 所有数组操作都用 " 包裹,尤其是遍历。
  • 使用 ${#array[@]} 获取长度,而不是手动计算。
  • 避免在循环中频繁修改数组,影响性能。

数组与字符串的转换

有时你需要把数组转成字符串,或反过来。

str="${fruits[*]}"
echo "$str"   # 输出:apple banana orange grape mango kiwi

IFS=' ' read -ra words <<< "$str"
echo ${words[@]}   # 输出:apple banana orange grape mango kiwi

IFS(Internal Field Separator)是 Shell 的字段分隔符,通过修改它,可以控制字符串如何分割。


总结

Shell 数组是提升脚本效率的核心工具。它不仅让代码更整洁,还能应对复杂的批量处理任务。从创建、访问到遍历、修改,掌握这些操作后,你写脚本时不再需要为“一堆变量”头疼。

无论是备份、配置管理,还是日志分析,Shell 数组都能帮你快速构建可维护的逻辑。关键在于理解它的索引机制、注意引号问题、善用循环与条件判断

记住:数组不是“一次性”的,它是动态的、可扩展的。像搭积木一样,你随时可以添加、修改或删除元素。

当你能熟练运用 Shell 数组时,你会发现,命令行编程也可以如此优雅而强大。

最后提醒一句:别忘了在脚本开头加上 #!/bin/bash,并确保运行权限正确,让每一次脚本执行都稳定可靠。

祝你写脚本越来越顺手!