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 内建命令接受。可以使用上面介绍的 name[subscript]=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 a[4]),参数将受到 shell 文件名扩展的影响。如果不希望进行路径名扩展,请引用该参数(例如,unset 'a[4]')。

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

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

相关推荐
wsad053212 小时前
Linux Shell脚本执行方式全解析:source、点号、路径、bash与exec的区别
linux·运维·bash·shell
叠叠乐12 小时前
EasyTier 免费自建自用5$每个月的服务器
linux·运维·bash
白太岁1 天前
操作系统开发:(8) 任务/线程的创建、调度与管理(实现 tasks.h 与 tasks.c)
c语言·开发语言·bash
之歆2 天前
Bash 循环与函数、Linux 进程管理
linux·chrome·bash
dingdingfish2 天前
Bash学习 - 第6章:Bash Features,第3节:Interactive Shells
bash·shell·interactive
追夢秋陽2 天前
MacOS app打包Dmg线下分发测试及公证shell脚本
macos·shell·dmg·cocoa打包·打包脚本
袁袁袁袁满2 天前
Linux怎么创建Shell脚本.sh文件
linux·运维·服务器·shell·shell脚本.sh文件·创建shell脚本·创建.sh文件
之歆3 天前
Shell 命令与基础完全指南
shell
鸠摩智首席音效师7 天前
如何在Bash中捕获标准错误到一个变量 ?
bash