Contents

Bash Array简介

1.简介

Bash 数组是强大的数据结构,在我们处理文件或字符串的集合时非常有用。 在本教程中,我们将探索如何使用它们。

2. 数组的类型

Bash 中有两种类型的数组:

  1. 索引数组——可以通过整数索引访问值
  2. 关联数组——可以通过键访问值(这也称为映射)

在我们的示例中,我们将主要使用第一种类型,但偶尔也会讨论地图。 一个特殊的方面是Bash 数组没有最大限制。也没有关于连续分配的要求。

我们稍后会解释最后一部分。现在,让我们看看如何定义数组。

2.1. 索引数组

我们可以通过多种方式声明索引数组。

让我们首先使用带有*-a选项的declare*关键字

declare -a indexed_array

此外,我们可以使用一些字符串值初始化数组:

declare -a indexed_array=("Blogdemo" "is" "cool")

由于我们提供了一些初始值,我们可以跳过 declare 关键字和标志

indexed_array=("Blogdemo" "is" "cool")

我们还可以在初始化序列中使用索引:

indexed_array=([0]="Blogdemo" [1]="is" [2]="cool")

或者我们可以一一分配元素:

indexed_array[0]="Blogdemo"
indexed_array[1]="is"
indexed_array[2]="cool"

在 Bash 中,所有这些样本都是有效的数组定义。但是,请注意索引从零开始。 还记得我们所说的关于连续分配没有限制的内容吗?让我们看看发生了什么:

indexed_array[0]="Blogdemo"
indexed_array[2]="cool"

这个例子也是一个有效的数组定义。与其他元素不同,索引 1 处的元素没有被分配,但这并没有什么坏处。

2.2. 关联数组

现在,让我们看看如何声明关联数组。

与索引数组不同,我们必须使用***-A*选项显式声明关联数组**:

declare -A associative_array=(["one"]="Blogdemo" ["two"]="is" ["three"]="cool")

同样,我们可以通过指定键和相应的值来分配更多的值:

associative_array["four"]="yeah"

当然,现在我们的地图中有四个元素。

3. 基本操作

接下来,我们将看看我们可以对数组进行的一些基本操作。

3.1. 打印数组元素

打印数组元素是最直观和基本的操作之一。 让我们看看它是什么样子的:

declare -a indexed_array=("Blogdemo" "is" "cool")
echo "Array elements : ${indexed_array[@]}"

我们得到输出:

Array elements : Blogdemo is cool

在这里,我们使用@符号作为索引来指定我们数组的所有成员

我们还用${}构造包围了数组变量。这会触发 Bash参数扩展 。 稍后我们将看到一些有趣的可能性。

或者,我们也可以使用*符号作为索引来获得相同的输出:

echo "Array elements : ${indexed_array[*]}"

但是,这两个选项之间存在差异。我们稍后也会讨论这个问题。

3.2. 增强迭代

增强迭代类似于Java 中的列表迭代

declare -a indexed_array=("Blogdemo" "is" "cool")
for element in ${indexed_array[@]}
    do
       echo "["$element"]"
    done

在这里,我们再次使用${indexed_array[@]}的参数扩展来返回数组的所有元素

然后,我们只是循环遍历它:

[Blogdemo]
[is]
[cool]

当然,这也适用于* 符号:

for element in ${indexed_array[*]}
    do
        echo "["$element"]"
    done

我们还可以在关联数组上使用增强迭代

declare -A associative_array=(["one"]="Blogdemo" ["two"]="is" ["three"]="cool")
for element in ${associative_array[@]}
    do
        echo "["$element"]"
    done

让我们看一下输出:

[is]
[cool]
[Blogdemo]

我们可以看到我们的输出没有排序。打印元素的顺序与初始化序列中的顺序不同。

3.3. 使用索引迭代

我们可以在数组上使用基于索引的迭代和增强的循环结构:

declare -a indexed_array=("Blogdemo" "is" "cool")
for index in ${!indexed_array[@]}
    do
        echo "["$index"]:["${indexed_array[$index]}"]"
    done

现在,我们使用${!indexed_array[@]}返回数组的所有索引

[0]:[Blogdemo]
[1]:[is]
[2]:[cool]

让我们看看如果我们有一个关联数组会发生什么:

declare -A associative_array=(["one"]="Blogdemo" ["two"]="is" ["three"]="cool")
for key in ${!associative_array[@]}
    do
        echo "["$key"]:["${associative_array[$key]}"]"
    done

我们的索引值现在实际上是 map 中的键

同样,输出中没有特定的顺序:

[two]:[is]
[three]:[cool]
[one]:[Blogdemo]

当然,我们也可以做一个经典的增量循环结构:

for ((index=0; index < ${#indexed_array[@]} ; index++))
    do
        echo "["$index"]:["${indexed_array[$index]}"]"
    done

与之前的方法不同,现在我们使用${#indexed_array[@]}来检索数组中的元素数量

我们之前看到关联数组返回键而不是索引。因此,这种类型的增量循环不适用于 map

3.4. 在数组中插入元素

一开始,我们说 Bash 数组没有大小和连续性约束。 因此,我们可以使用简单的赋值在任何索引处插入元素

declare -a indexed_array=("Blogdemo" "is" "cool")
indexed_array[2]="lorem"
indexed_array[5]="ipsum"
for index in ${!indexed_array[@]}
    do
        echo "["$index"]:["${indexed_array[$index]}"]"
    done

不是一种非常直观的添加值的方法,这样做会产生令人困惑的结果:

[0]:[Blogdemo]
[1]:[is]
[2]:[lorem]
[5]:[ipsum]

在这种情况下,索引是不连续的。我们只是跳过了位置 3 和 4。

这仅在尝试替换数组中的现有元素时才有意义。 假设我们之前的示例,我们可以使用更简洁的方法将值附加到我们的数组中:

indexed_array+=("lorem")
for index in ${!indexed_array[@]}
    do
        echo "["$index"]:["${indexed_array[$index]}"]"
    done

让我们看看这里发生了什么:

[0]:[Blogdemo]
[1]:[is]
[2]:[cool]
[3]:[lorem]

我们使用+=构造将一个新值附加到我们的数组中。这也可以用来初始化数组

3.5. 从数组中删除元素

当我们想从数组中删除项目时,我们使用unset构造

declare -a indexed_array=("Blogdemo" "is" "cool")
echo "Array elements : ${indexed_array[@]}"
unset indexed_array[1]
echo "Size of array after removal: ${#indexed_array[@]}"
echo "Array elements after removal: ${indexed_array[@]}"

在此示例中,我们删除了索引 1 处的元素:

Array elements : Blogdemo is cool
Size of array after removal: 2
Array elements after removal : Blogdemo cool

让我们看看如果我们在不提供任何索引的情况下使用unset会发生什么

declare -a indexed_array=("Blogdemo" "is" "cool")
echo "Array elements : ${indexed_array[*]}"
unset indexed_array
echo "Size of array after removal: ${#indexed_array[@]}"
echo "Removed complete array : ${indexed_array[@]}"

我们得到输出:

Array elements : Blogdemo is cool
Size of array after removal: 0
Removed complete array :

这意味着我们的数组被完全删除了。如果我们使用@和*符号作为索引,也会发生同样的事情

declare -a indexed_array=("Blogdemo" "is" "cool")
echo "Array elements : ${indexed_array[*]}"
unset indexed_array[@]
echo "Size of array:" ${#indexed_array[@]}
echo "Removed complete array : ${indexed_array[@]}"

现在让我们尝试在刚刚删除的数组中添加一个元素:

declare -a indexed_array=("Blogdemo" "is" "cool")
echo "Array elements : ${indexed_array[@]}"
unset indexed_array[@]
indexed_array+=("lorem ipsum")
echo "Array elements : ${indexed_array[@]}"

这是可能的,因为我们正在重新初始化数组:

Array elements : Blogdemo is cool
Array elements : lorem ipsum

4. 高级操作

现在,让我们看看一些涉及数组的高级场景。

4.1. 在迭代中使用引号

还记得我们的第一个迭代示例吗?我们说过@和*之间有区别。 让我们看看这是关于什么的:

declare -a indexed_array=("Blogdemo" "is" "cool")
for element in "${indexed_array[*]}"
    do
        echo "["$element"]"
    done

现在输出:

[Blogdemo is cool]

这并不是我们所期望的。*现在让我们以@*作为索引再试一次:

for element in "${indexed_array[@]}"
    do
        echo "["$element"]"
    done

我们得到输出:

[Blogdemo]
[is]
[cool]

让我们解释一下这里发生了什么。我们在${indexed_array[*]}构造周围使用了双引号

因此,当使用*符号时,数组的元素是由空格字符分隔的单个单词

当我们的数组元素中包含空格字符时,这种方法特别有用:

declare -a indexed_array=("Blogdemo is" "so much" "cool")
echo "Without quotes:"
for element in ${indexed_array[@]}
    do
        echo "["$element"]"
    done
echo "With quotes:"
for element in "${indexed_array[@]}"
    do
        echo "["$element"]"
    done

以及相应的输出:

Without quotes:
[Blogdemo]
[is]
[so]
[much]
[cool]
With quotes:
[Blogdemo is]
[so much]
[cool]

请注意,当使用不带引号的构造时,数组元素会受到进一步的分词

4.2. 变换数组

通常,当我们考虑转换数组的元素时,首先想到的是迭代。

幸运的是,Bash 提供了一些巧妙的参数扩展技巧,使我们免于迭代。

我们只会看一些有趣的,但我们可以随时查看 Bash 手册页了解更多信息。

让我们首先搜索并替换数组中的特定元素

declare -a indexed_array=("Blogdemo is" "so much" "cool" "cool cool")
echo "Initial array : ${indexed_array[@]}"
echo "Replacing cool with better: ${indexed_array[@]/cool/better}"

这里我们使用${indexed_array[@]/cool/better}语法来实现这个替换

Initial array :  Blogdemo is so much cool cool cool
Replacing cool with better: Blogdemo is so much better better cool

但是,这只替换了每个元素中第一次出现的字符串。如果我们想要所有字符串,那么我们需要使用*//* 语法

declare -a indexed_array=("Blogdemo is" "so much" "cool" "cool cool")
echo "Replacing cool with better: ${indexed_array[@]//cool/better}"

我们得到了想要的结果:

Replacing cool with better: Blogdemo is so much better better better

让我们看看如果我们不指定替换字符串会发生什么:

echo "Replacing cool with nothing: ${indexed_array[@]//cool}"

这将从我们的数组中删除匹配的字符串:

Replacing cool with nothing: Blogdemo is so much

我们需要记住,**这种搜索默认区分大小写,**除非我们修改 shell 的可选行为

现在,让我们尝试更改为大写:

declare -a indexed_array=("Blogdemo" "is" "cool")
echo "Uppercasing sentence case: ${indexed_array[@]^}"
echo "Uppercasing all characters: ${indexed_array[@]^^}"

在这里**,我们使用*^^^更改为大写*:

Uppercasing sentence case: Blogdemo Is Cool
Uppercasing all characters: BLOGDEMO IS COOL

另一方面,我们也可以使用 , ,, 结构进行小写转换

indexed_array=("Blogdemo" "Is" "COol")
echo "Lowercasing sentence case: ${indexed_array[@],}"
echo "Lowercasing all characters: ${indexed_array[@],,}"

我们得到了想要的结果:

Lowercasing sentence case: Blogdemo is cOol
Lowercasing all characters: blogdemo is cool

4.3. 数组之间的赋值

在之前的转换示例中,我们的初始数组没有被修改。 每次我们只是应用一个转换,但结果只是打印到标准输出。 如果我们想保留结果,我们可以将它们分配到一个单独的数组中

indexed_array=("BLogdemo" "Is" "COol")
lowercased_array=(${indexed_array[@],})
echo "Lowercasing sentence case: ${lowercased_array[@]}"

以及相应的输出:

Lowercasing sentence case: bLogdemo is cOol

让我们再举一个变换的例子,这个赋值很有用:

indexed_array=("Blogdemo is" "so much" "cool")
echo "Uppercasing sentence case1: ${indexed_array[@]^}"
echo "No of elements in first_array: ${#indexed_array[@]}"
second_array=(${indexed_array[@]})
echo "Uppercasing sentence case2: ${second_array[@]^}"
echo "No of elements in second_array: ${#second_array[@]}"

我们希望这两个输出匹配。然而,这种情况并非如此:

Uppercasing sentence case1: Blogdemo is So much Cool
No of elements in first_array: 3
Uppercasing sentence case2: Blogdemo Is So Much Cool
No of elements in second_array: 5

所以发生了什么事?还记得前面迭代示例中的引用吗? 由于我们没有使用引号,因此Bash 应用了分词,并且second_array包含的元素比 first 多。 在某些情况下,赋值对于合并数组很有用:

declare -a fist_array=("Blogdemo" "is" "cool")
declare -a second_array=("lorem" "ipsum")
declare -a merged=(${fist_array[@]} ${second_array[@]})
echo "First array : ${fist_array[@]}"
echo "Second array : ${second_array[@]}"
echo "Merged array : ${merged[@]}"

让我们仔细看看我们做了什么。我们用第一个和第二个数组的所有元素初始化合并数组:

First array : Blogdemo is cool
Second array : lorem ipsum
Merged array : Blogdemo is cool lorem ipsum

4.4. 偏移和长度遍历

在 Bash 中,这正式称为子字符串扩展,但也适用于索引数组。但是,对于map,它具有未定义的行为。 有时,我们需要提取数组的特定部分

declare -a indexed_array=("Blogdemo" "is" "cool" "and" "better" "than" "before")
echo "Offset 1 length 3: ${indexed_array[@]:1:3}"

让我们看看这是做什么的:

Offset 1 length 3: is cool and

此构造采用输入偏移量和长度。然后它选择从 index=offset开始的长度x元素。 但是如果我们省略长度呢:

echo "Offset 1 no length: ${indexed_array[@]:1}"

然后我们从偏移量开始获取数组的所有元素,直到结束

Offset 1 no length: is cool and better than before

现在让我们看看使用负偏移量是否会改变一些东西:

echo "Offset -1 length 3: ${indexed_array[@]: -4:3}"

我们得到一些有趣的结果:

Offset -1 length 3: and better than

负偏移量被认为是相对于数组的最大索引。

使用负偏移量时还要注意空格字符。如果我们不使用它,Bash 会将它与另一个构造混淆。