Bash学习 - 第6章:Bash Features,第7节:Arrays

本文为 Bash Reference Manual第6章:Bash Features 第7节:Arrays 的读书笔记。

完整的笔记目录参见Bash学习笔记总目录。

6.7 Arrays

Bash 提供一维索引数组和关联数组变量。任何变量都可以用作索引数组;declare 内置命令可以显式声明一个数组。数组的大小没有最大限制,成员不需要连续索引或赋值。索引数组通过必须扩展为整数 的算术表达式引用(参见 Shell 算术),且索引从零开始 ;关联数组使用任意字符串。除非另有说明,索引数组的索引必须为非负整数。

shell 会对索引数组的下标执行参数和变量扩展、算术扩展、命令替换以及引号移除。由于这可能导致空字符串,所以下标索引会将这些空字符串视为求值为 0 的表达式。

bash 复制代码
$ declare -a idxarr
$ idxarr[0]=one
$ declare -p idxarr
declare -a idxarr=([0]="one")
$ var=1
$ idxarr[$var]=two
$ declare -p idxarr
declare -a idxarr=([0]="one" [1]="two")
$ var=
$ idxarr[$var]=three
$ declare -p idxarr
declare -a idxarr=([0]="three" [1]="two")

Shell 对关联数组的下标执行波浪号展开、参数和变量展开、算术展开、命令替换以及引号去除。空字符串不能用作关联数组的键。

如果使用以下语法向任何变量赋值,Bash 会自动创建一个索引数组

bash 复制代码
name[subscript]=value

下标被视为必须求值为大于或等于零的数字的算术表达式。要显式声明一个索引数组,请使用:

bash 复制代码
declare -a name

以下语法也被接受;下标subscript会被忽略。(参见 Bash 内置命令)。

bash 复制代码
declare -a name[subscript]

关联数组使用以下语法创建:

bash 复制代码
declare -A name

可以使用 declare 和 readonly 内置命令为数组变量指定属性。每个属性都适用于数组的所有成员。

💡 关联数组必须显式的创建,而索引数组不需要。

bash 复制代码
$ unset idxarray
$ idxarray[1]=jan
$ echo ${idxarray@A}
declare -a idxarray

## 没有显式创建关联数组
$ unset asoarray
$ asoarray=([name]="grace" [gender]="male" [age]=18)
$ echo ${asoarray@A}
declare -a asoarray='18'

## 显式创建关联数组
$ unset asoarray
$ declare -A asoarray
$ asoarray=([name]="grace" [gender]="male" [age]=18)
$ echo ${asoarray@A}
declare -A asoarray

数组通过如下形式的复合赋值进行赋值:

bash 复制代码
name=(value1 value2 ... )

每个值可以是 subscript=string 的形式。索引数组赋值只接受字符串。

列表中的每个值都会进行上述的 shell 扩展(参见 Shell 扩展),但如果值是有效的变量赋值,包括括号和下标,则不会进行大括号扩展和单词分割,就像单独的变量赋值一样。

在为索引数组赋值时,如果提供了可选下标,则赋值到该下标;否则,赋值的元素的下标为该语句上一次赋值的最后下标加一。索引从零开始。

在给关联数组赋值时,复合赋值中的单词可以是赋值语句(此时需要下标),也可以是一列单词,该列单词被解释为交替出现的键和值序列:name=(key1 value1 key2 value2 ...)。这些的处理方式与 name=(key1=value1 key2=value2 ...) 相同。列表中的第一个单词决定剩余单词的解释方式;列表中的所有赋值必须是同一类型。使用键/值对时,键不能为空或缺失;最后缺失的值会被视为空字符串。

这个语法也被 declare 内建命令接受。可以使用上面介绍的 namesubscript=value 语法给单个数组元素赋值。

在给索引数组赋值时,如果 name 的下标为负数,这个负数会被解释为相对于 name 的最大索引加1的位置,因此负索引从数组末尾向前计算,索引为 -1 表示数组的最后一个元素。

💡 Bash中的索引数组是稀疏数组,即下标可以不连续,只有实际赋值的元素才占用内存。

索引数组示例:

bash 复制代码
$ declare -a idxarray
$ idxarray=(1 2 3 4)
$ echo ${#idxarray[@]}
4
$ idxarray[6]=7
$ echo ${idxarray[@]}
1 2 3 4 7
$ echo ${idxarray[5]}

$ echo ${#idxarray[@]}
5
$ idxarray[-1]=-7
$ echo ${idxarray[@]}
1 2 3 4 -7
$ idxarray[-2]=-4
$ echo ${idxarray[@]}
1 2 3 4 -4 -7

关联数组示例:

bash 复制代码
$ unset asoarray
$ declare -A asoarray
# 尽管 asoarray=(k1 v1 k2 v2 k3 v3 k4 v4) 也可以,但不建议
$ asoarray=([k1]=v1 [k2]=v2 [k3]=v3 [k4]=v4)
$ echo ${#asoarray[@]}
4
$ echo ${asoarray[k1]}
v1
$ asoarray[k8]=v8
$ echo ${#asoarray[@]}
5

使用复合赋值语法赋值时,+= 运算符会将值追加到数组变量中;详见上文的 Shell 参数部分。

💡 Bash中,数组元素默认都是字符串类型,但可以在使用时进行算术运算。

示例:

bash 复制代码
$ unset intarray
$ declare -a intarray=(1 2 3 4)
$ declare -p intarray
declare -a intarray=([0]="1" [1]="2" [2]="3" [3]="4")
## 此时的+=为字符串拼接操作
$ intarray[0]+=100
$ echo ${intarray[0]}
1100
## 利用数学运算符实现数学操作
$ ((intarray[0]+=100))
$ echo ${intarray[0]}
1200

declare -i可以影响操作的行为,尽管仍以字符串形式存储:

bash 复制代码
$ unset intarray
$ declare -ai intarray=(1 2 3 4)
$ declare -p intarray
declare -ai intarray=([0]="1" [1]="2" [2]="3" [3]="4")
$ intarray[0]+=100
$ echo ${intarray[0]}
101

数组元素使用 ${name[subscript]} 引用。大括号是必需的,以避免与 Shell 的文件名扩展操作符冲突。如果下标是 @ 或 *,该单词会扩展为数组 name 中的所有成员,除非内建命令或单词扩展的描述另有说明。这些下标在单词出现在双引号内时才有区别 。如果单词使用双引号引用,${name[*]} 会扩展为一个单词 ,其值为每个数组成员的值,并用 IFS 变量的第一个字符分隔;而 ${name[@]} 则会将 name 的每个元素扩展为单独的单词 。当没有数组成员时,${name[@]} 不会扩展为任何内容。如果双引号中的扩展出现在一个单词内部,第一个参数的展开会与原始单词展开的开头部分连接,最后一个参数的展开会与原始单词展开的末尾部分连接。这类似于特殊参数 @ 和 * 的展开方式。

💡 在所有情况下,数组元素使用 ${name[subscript]} 引用。在算术运算符中,还可以用name[subscript]的形式引用。

bash 复制代码
$ arr=(10 20 30)
$ ((ret=arr[0]+arr[1]))
$ echo $ret
30
$ ((ret=${arr[1]}+arr[2]))
$ echo $ret
50

在 6.5 Shell Arithmetic 中描述了对此的支持:

Within an expression, shell variables may also be referenced by name without using the parameter expansion syntax.

${#name[subscript]} 会扩展为 ${name[subscript]} 的长度。如果下标是@或*,则扩展为数组中元素的数量。

bash 复制代码
$ arr=(tom jerry steven sabrina judy)
$ echo ${arr[@]}
tom jerry steven sabrina judy
$ echo ${arr[*]}
tom jerry steven sabrina judy
$ set -- "${arr[@]}"
$ echo $#
5
$ set -- "${arr[*]}"
$ echo $#
1
## 数组元素个数
$ echo ${#arr[@]}
5
$ echo ${#arr[*]}
5
## 单词的长度
$ echo ${#arr[0]}
3
$ echo ${#arr[1]}
5

下面这个例子可以更好的说明@和*的区别:

bash 复制代码
arr=("a b" "c" "d e")
for word in prefix"${arr[@]}"suffix; do
    echo "WORD:$word"
done

arr=("a b" "c" "d e")
for word in prefix"${arr[*]}"suffix; do
    echo "WORD:$word"
done

他们的输出依次如下:

bash 复制代码
# "@"
WORD:prefixa b
WORD:c
WORD:d esuffix

# "*"
WORD:prefixa b c d esuffix

如果用于引用索引数组元素的下标计算结果为小于零的数字,它会被解释为相对于数组最大索引加1的位置,因此负数索引会从数组末尾向前计数,而索引 -1 指的是最后一个元素。

引用数组变量而不带下标,相当于使用下标 0 进行引用。任何使用有效下标的变量引用都是有效的;如果需要,Bash 会创建一个数组。

如果数组变量的下标已被赋值,则该数组变量被视为已设置。空字符串是一个有效值。

可以获取数组的键(索引)以及对应的值。${!name[@]} 和 ${!name[*]} 会展开为数组变量 name 中分配的索引。在双引号中使用时,其处理方式类似于特殊参数 @ 和 * 在双引号中的展开。

bash 复制代码
$ declare -A asoarray
$ asoarray=([k1]=v1 [k2]=v2 [k3]=v3 [k4]=v4)
$ echo ${!asoarray[@]}
k4 k1 k2 k3
$ echo ${asoarray[@]}
v4 v1 v2 v3
$ for k in "${!asoarray[@]}"; do echo ${asoarray[$k]}; done
v4
v1
v2
v3

$ idxarray=(1 2 3 4)
$ echo ${!idxarray[@]}
0 1 2 3
$ echo ${idxarray[@]}
1 2 3 4

💡 Bash的关联数组内部通过哈希表实现,并不保证插入键的顺序。在现象上,与数据库堆表的插入类似。

unset 内置命令用于销毁数组。unset name*subscript* 会取消索引为 subscript 的数组元素。如上所述,对索引数组的负索引也会按同样方式处理。取消数组变量的最后一个元素并不会取消该变量。unset name ,其中 name 是一个数组,会删除整个数组。当 subscript 为 *或 @ 时,unset name*subscript* 的行为取决于数组类型。如果 name 是关联数组,它会删除键为 * 或 @ 的元素。如果 name 是索引数组,unset 会删除所有元素,但不会删除数组本身。

bash 复制代码
$ arr=(1 2 3 4)
$ declare -p arr
declare -a arr=([0]="1" [1]="2" [2]="3" [3]="4")
$ unset arr
$ declare -p arr
-bash: declare: arr: not found

$ arr=(1 2 3 4)
$ echo ${arr[@]}
1 2 3 4
$ unset arr[1]
$ declare -p arr
declare -a arr=([0]="1" [2]="3" [3]="4")

$ arr=(1 2 3 4)
$ unset arr[@]
$ declare -a arr
$

在将带下标的变量名作为命令的参数使用时,例如使用 unset,如果不使用上述描述的字词展开语法(例如,unset a4),参数将受到 shell 文件名扩展的影响。如果不希望进行路径名扩展,请引用该参数(例如,unset 'a4')。

declare、local 和 readonly 内置命令都接受 -a 选项以指定索引数组,接受 -A 选项以指定关联数组。如果同时提供了这两个选项,则以 -A 为准。read 内置命令接受 -a 选项,将从标准输入读取的单词列表赋值给数组,也可以将标准输入中的值读取到各个数组元素中。set 和 declare 内置命令以一种方式显示数组值,使它们可以作为输入重新使用。其他内置命令也接受数组名作为参数(例如 mapfile);有关详细信息,请参阅各个内置命令的描述。Shell 提供了许多内置数组变量。

mapfile或readarray可以标准输入导入索引数组。可参见4.2 Bash Builtin Commands的笔记。

相关推荐
吴声子夜歌7 天前
Shell编程实例——编写安全的shell脚本(二)
linux·运维·shell
吴声子夜歌9 天前
Shell编程实例——内务及管理任务(一)
linux·运维·shell
吴声子夜歌9 天前
Shell编程实例——高级脚本编程(一)
linux·运维·网络·shell
吴声子夜歌9 天前
Shell编程实例——bash的配置与自定义(二)
linux·运维·shell
吴声子夜歌10 天前
Shell编程实例——与解析相关的任务(二)
linux·运维·shell
吴声子夜歌10 天前
Shell编程实例——脚本编程的附加特性
linux·运维·shell
云计算练习生10 天前
什么是内核?操作系统内核到底管哪些事
linux·windows·操作系统·内核·shell
吴声子夜歌13 天前
Shell编程实例——bash入门
linux·运维·shell
Swizard13 天前
Linux 日志神器 journalctl 完全指南:彻底替代传统日志查看方式
shell
吴声子夜歌14 天前
Shell编程——数组
linux·运维·shell