跳过正文

使用Shell数组轻松管理数据集合

Shell Arrays
目录

在Shell脚本的世界里,数组就像是一个装满东西的盒子,你可以把很多相关的数据放进去,然后方便地拿出来使用。无论你是初学者还是有一定经验的Shell脚本编写者,掌握数组的使用都会让你的脚本更加灵活和强大。下面,我们就来详细了解一下Shell数组的基础知识和实用技巧。

Shell数组的基础知识
#

  1. 数组是什么?

数组是存储一系列值(类型可以不同)的数据结构。在Shell中,数组可以包含文本(字符串)或数字,而且每个元素都有自己的位置编号,称为索引。索引通常从0开始。

  1. 索引数组 vs 关联数组

索引数组:这是最传统的数组形式,每个元素按照顺序排列,并且通过数字索引来访问。

关联数组:也称为字典或哈希表,这种类型的数组允许使用非数字的键(如字符串)来标识元素,提供了更灵活的方式存储和检索信息。

创建和初始化数组
#

  1. 直接赋值创建数组
# 创建一个简单的索引数组
fruits=("apple" "banana" "orange")
# 创建一个有1-5共5个数字的索引数组
numbers=($(seq 5))
  1. 使用declare声明数组

可以使用declare -a命令创建一个索引数组,这也是直接赋值创建数组的默认行为:

declare -a colors=("red" "green" "blue")

关联数组让我们能够以更加直观的方式组织数据,使用declare -A创建关联数组。例如,我们可以把学生的姓名作为键,成绩作为值:

declare -A grades
grades=(["Zhang San"]=90 ["Li Si"]=85 ["Wang Wu"]=92)

访问数组元素
#

  1. 访问单个元素

要查看索引数组中的某一项,只需要知道它的索引即可:

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

使用键可以访问关联数组中的项,键区分大小写:

echo ${grades["Zhang San"]}
# 输出:90
  1. 获取数组长度

想知道数组中有多少项?可以使用${#array[@]}语法。例如:

echo ${#fruits[@]}
# 输出: 3

这里的${#fruits[@]}会给出数组中元素的数量。此方法也可以获取到关联数组的元素数量。

  1. 遍历数组

如果想依次处理索引数组中每个元素,可以使用循环:

for fruit in "${fruits[@]}"; do
    echo "$fruit"
done
# 每行输出一个元素

这段代码会遍历fruits数组,并打印出每种水果的名字。

对于关联数组,Bash提供了一种特殊的方式来迭代关联数组的键,即使用${!array[@]}语法。查看关联数组所有的键:

echo "${!grades[@]}"
# 输出:Zhang San Li Si Wang Wu

使用for key in "${!array[@]}"语法可以遍历每一个键,再通过键获取值,就可以遍历关联数组。

修改数组
#

  1. 添加新元素

向索引数组追加新成员非常简单:

fruits+=("grape")
# 在最后添加一个新元素

向关联数组添加新成员也非常简单:

grades+=(["Zhao Liu"]=90)
# 添加的位置不确定,取决于键的哈希值
  1. 更新现有元素

也可以更改已有元素的值:

fruits[1]="peach"
# 将第二个元素改为peach

同样,使用键可以定位并修改元素值:

grades["Zhao Liu"]=91
# 将Zhao Liu的成绩改为91

你可以试试使用grades["Zhao Liu"]+=1是否符合预期?如果希望将成绩加1应该怎样做?

  1. 删除元素

若要移除某个特定元素:

unset fruits[2]
# 删除第三个元素

删除元素后,数组后面的元素不会前移,${fruits[2]}为空。对于整个数组的清理,只需unset fruits即可。

unset命令同样适用于关联数组。

数组操作高级特性
#

  1. 对数组进行排序

虽然Shell本身没有内置的数组排序功能,但我们可以通过其他命令实现:

sorted_fruits=($(printf "%s\n" "${fruits[@]}" | sort))

printf会将数组中的元素按行输出,sort命令可以对这些行进行排序。如果是数值数组,使用sort -n可以按数值进行排序,或使用sort -nr按数值倒序排序。

  1. 搜索特定元素

查找数组中是否存在某项:

if [[ " ${fruits[@]} " =~ " apple " ]]; then
    echo "apple在数组中"
fi

=~是一个用于正则表达式匹配的操作符,用来测试字符串是否与给定的正则表达式模式相匹配。

在使用正则表达式匹配时,需要注意字符串和正则表达式两端的空格,以确保进行完整的匹配。如果数组元素本身包含空格,则直接使用正则表达式可能会导致部分匹配的问题,从而产生不准确的结果。为了避免这种情况,建议采用遍历数组的方式逐个检查元素,以确保匹配的准确性。

  1. 字符串转数组

对于一串由分隔符分开的文字,可以用这个方法转换成数组:

string="apple,banana,orange"
IFS=',' read -r -a array <<< "$string"

这里IFS变量定义了分隔符,read命令将字符串分割并存入数组array中。

  1. 数组切片

有时候,你可能只想处理数组中的一部分元素。这时,可以使用数组切片的功能。例如,要获取fruits数组中的前两个元素,可以这样做:

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

这里的0:2表示从索引0开始,获取2个元素。

  1. 数组连接与合并

可以轻松地将两个或多个数组连接起来。例如:

vegetables=("carrot" "potato" "beetroot")
all_foods=("${fruits[@]}" "${vegetables[@]}")

现在,all_foods数组包含了fruits和vegetables两个数组的所有元素。

实用示例
#

示例1:统计文件夹内不同扩展名文件的数量

#!/bin/bash

declare -A ext_counts
for file in *; do
    ext="${file##*.}"
    [[ "$file" == "$ext" ]] && ext="no_ext"
    ((ext_counts[$ext]++))
done

for ext in "${!ext_counts[@]}"; do
    echo "$ext: ${ext_counts[$ext]}"
done

你可以试试用本文介绍的方法,将上述结果按键(扩展名)排序后输出。

示例2:从用户输入生成菜单

#!/bin/bash

options=("Option 1" "Option 2" "Exit")
PS3="Choose an option: "
select opt in "${options[@]}"; do
    case $opt in
        "Option 1")
            echo "You chose Option 1"
            ;;
        "Option 2")
            echo "You chose Option 2"
            ;;
        "Exit")
            break
            ;;
        *) echo "Invalid option $REPLY"
            ;;
    esac
done

常见问题和注意事项
#

空格敏感:在赋值时,确保等号两边没有空格。

索引越界:尝试访问不存在的索引不会出现错误,会返回空值,所以应注意索引的范围和值的有效性,尤其是删除索引元素后。

使用括号:数组排序后的结果赋值给数组,应避免遗漏最外层的半角括号。

使用引号:在进行数组遍历、连接等操作时,获取所有数组元素的表达式应当用双引号括起来,以确保包含空格的元素值能够保持其完整性。如果不使用双引号包裹这些表达式,含有空格的元素将被视为多个独立的、由空格分隔的元素,从而可能导致意外的行为或错误的结果。

明确定义关联数组:使用直接赋值的方式会创建索引数组,虽然值是关联数组的形式,此时的赋值结果不符合预期,应使用declare -A进行明确定义。

Bash中很多地方的双引号可以省略,例如以下语句都能获取预期的结果:

declare -a colors=(red green blue)
printf "%s\n" ${colors[@]} # 每行打印一个颜色

declare -A grades=([John]=90 [Mike]=85)
printf "%s\n" ${!grades[@]} # 每行打印一个姓名

但是,当执行以下语句后,上述printf的结果不再符合预期,空格分隔的元素值或者名称被分行输出:

colors+=("soft red")
grades+=(["Zhang San"]=90)

使用双引号可以避免出现上述问题:

printf "%s\n" "${colors[@]}"
printf "%s\n" "${!grades[@]}"

因此,不建议省略双引号,平时养成使用双引号的习惯很重要。

总结
#

Shell数组为我们的脚本编写提供了极大的便利,使得管理复杂的数据集变得更加容易。无论是简单的任务还是复杂的自动化流程,掌握数组的使用都是提高效率的关键一步。

相关文章

使用命令行备份MySQL数据库
Mysql Database
使用docker-compose限制内存和cpu
Docker-Compose
如何构建高效可靠的AI基础设施
AI Infrastructure