Bash 数组
Bash 数组
数组(array)是一个包含多个值的变量。成员的编号从 0 开始,数量没有上限,也没有要求成员被连续索引。
数量没有上限和不要求成员连续,真的很自由
数组可以定义为相似类型元素的集合。与大多数编程语言不同,Bash 中的数组不必是相似类型元素的集合。由于 Bash 不能将字符串与数字区分开,因此数组可以同时包含字符串和数字。
Bash 仅支持一维数组(不支持多维数组),并且没有限定数组的长度大小。Bash 还支持关联数组(类似于 Java Map 类型的数据结构)。
Bash 支持下标访问,如果要从最后一个访问数字索引数组,可以使用负索引。索引-1
是最后一个元素的参考。可以在数组中使用几个元素
这种方式跟 Python 中的数据容器的下标访问很像,具体请看 Python 的《数据容器.md》的
序列的切片操作
输出数组的时候,一个元素换一行
|
|
创建数组
数组可以采用逐个赋值的方法创建。
|
|
上面语法中,ARRAY
是数组的名字,可以是任意合法的变量名。INDEX
是一个大于或等于零的整数,也可以是算术表达式。注意数组第一个元素的下标是 0,而不是 1。
下面创建一个三个成员的数组。
|
|
数组也可以采用一次性赋值的方式创建。
|
|
采用上面方式创建数组时,可以按照默认顺序赋值,也可以在每个值前面指定位置。
|
|
只为某些值指定位置,也是可以的。
|
|
上面例子中,hatter
是数组的 0 号位置,duchess
是 5 号位置,alice
是 6 号位置。
这种情况下,可以这样理解,先将未指定位置的元素组成数组,然后将指定位置的元素依次插入指定位置
没有赋值的数组元素的默认值是空字符串。
定义数组的时候,可以使用通配符。
|
|
上面例子中,将当前目录的所有 MP3 文件,放进一个数组。
具体请看《Bash 的模式拓展》中的
星号拓展
小节
我们还可以通过大括号拓展来快速构建列表:
|
|
具体请看《Bash 的模式拓展》的
大括号扩展
小节
先用declare -a
命令声明一个数组对象,也是可以的。
|
|
简单实践如下:
|
|
我们还可以declare -A
声明一个关联数组,
|
|
关于 declare 命令,请看《Bash 变量》的
declare 命令
小节
什么是关联数组?关联数组可以使用任意的字符串、或者整数作为下标来访问数组元素。有点像Map
这种数据结构。
简单实践如下:
|
|
read -a
命令则是将用户的命令行输入,存入一个数组。
|
|
上面命令将用户的命令行输入,存入数组dice
。
具体请看《Bash read 命令》的
参数
小节
读取数组
读取单个元素
读取数组指定位置的成员,要使用下面的语法。
|
|
上面语法里面的大括号是必不可少的,否则 Bash 会输出数组的第一个元素同时索引部分[i]
紧跟其后原样输出。
简单实践如下:
|
|
数组的下标支持负数,从最后一位元素(-1)开始遍历。
从左往右数,即正序的时候,第一个字符的下标是 0,从右往左数,即倒序的时候,第一个数的下标是 -1。
读取所有成员
@
和*
是数组的特殊索引,表示返回数组的所有成员。这两个特殊索引配合for
循环,就可以用来遍历数组。
|
|
注意:@
或*
放不放在双引号之中,是有差别的。如果数组元素自带空格的话,区别就会显示出来。
先看${arr[@]}
放在引号中和不放引号中的区别:
|
|
可以看到,将${arr[@]}
放到引号中才能正确遍历,不放到引号中的时候,数组元素的内容会以空拆分成多个元素输出,这是不对的。
再来看看${arr[*]}
放到引号中和不妨到引号中的区别
|
|
${arr[*]}
不放在双引号之中,跟${arr[@]}
不放在双引号之中是一样的。但是当${arr[*]}
放在双引号之中,整个数组变成了一个字符串,即只有一个元素用于循环。
因此,我们不建议使用${arr[*]}
访问数组的所有元素,推荐使用${arr[@]}
。
拷贝一个数组的最方便方法,就是写成下面这样。
|
|
这种写法也可以用来为新数组添加成员。
|
|
简单实践如下:
|
|
这种复制数组的写法,很像 Python 的自动拆包,具体请看《Python 解包》
默认位置
如果读取数组成员时,没有读取指定哪一个位置的成员,默认使用0
号位置。
|
|
上面例子中,foo
是一个数组,赋值的时候不指定位置,实际上是给foo[0]
赋值。
引用一个不带下标的数组变量,则引用的是0
号位置的数组元素。
|
|
上面例子中,引用数组元素的时候,没有指定位置,结果返回的是0
号位置。
数组的长度
要想知道数组的长度(即一共包含多少成员),可以使用下面两种语法。
|
|
这个语法跟获取字符串长度是一样的,获取字符串长度
${#str}
提取数组序号
数组的元素的数量没有上限,也没有要求成员被连续索引。
${!array[@]}
或${!array[*]}
,可以返回数组的成员序号,即哪些位置是有值的。
|
|
上面例子中,数组的 5、9、23 号位置有值。
利用这个语法,也可以通过for
循环遍历数组。
|
|
我们在删除元素之后,实际上并没有删除元素,而只是将其设置为了空字符串,数组的元素并没有变少,数组的长度也没有变,这样其实挺不方便,我们可以通过以下这段脚本,清理数组中的空字符串元素,返回一个不包含空字符串元素的数组。
创建arr_clean.sh
|
|
赋权后执行
|
|
可以看到数组的索引重新整理为连续的。
提取子数组
${array[@]:position:length}
的语法可以提取数组成员。
这个语法跟字符串获取子串的语法
${varname:offset:length}
不一样,不支持负数下标关于字符串的子串,请看《Bash 字符串操作》的
子字符串
小节
|
|
上面例子中,${food[@]:1:1}
返回从数组 1 号位置开始的 1 个成员,${food[@]:1:3}
返回从 1 号位置开始的 3 个成员。
如果省略长度参数length
,则返回从指定位置开始的所有成员。
|
|
上面例子返回从 4 号位置开始到结束的所有成员。
注意,${array[@]:position:length}
返回的是一个列表,或者说序列,其本质上是一个字符串,而不是一个数组,需要将其放到()
中,才是一个数组。
简单实践如下:
|
|
此时是一个全新的数组,跟原数组已经没有关系了。所以对这个子数组的修改,不会同步到原数组
Bash 中不存在对象,所以也不存在引用传递
两个数组的合并
两个数组的合并,可以通过arrA+=arrB
这种格式,此时 arrB 的元素会追加到 arrA 的后面。
我们可以通过这种语法来在数组末尾追加成员,只要将这个成员通过()
包装成一个数组即可。这样就能够实现自动地把值追加到数组末尾的效果。否则,就需要知道数组的最大序号,比较麻烦。
|
|
删除数组成员
删除一个数组成员,使用unset
命令。将某个成员设为空值,可以从${arr[@]}
的返回值中“隐藏”这个成员。同时${#arr[@]}
和${!arr[@]}
也会相应减少
关于
unset
命令,请看《Bash 变量》的删除变量
|
|
将数组的成员设为空字符串,也可以实现让某个成员从${arr[@]}
的返回值中隐藏,注意,这里是“隐藏”,而不是删除,因为这个成员仍然存在,只是值变成了空值。此时${#arr[@]}
和${!arr[@]}
的结果不会有变化
|
|
由于空值就是空字符串,所以下面这样写也有隐藏效果,但是不建议这种写法。
|
|
因此如果不是有特别的目的,建议使用unset
删除数组成员。
unset ArrayName
可以清空整个数组。
|
|
在有些情况下,我们想删除数组中的多个指定元素,比如我们想删除arr
数组中的所有的ele
元素,此时可以使用${arr[@]/ele}
这样的语法,而且,ele
部分还可以使用?
或者*
等通配符来进行匹配。
不过注意,通过${arr[@]/ele}
返回的结果并不是一个数组而是一个列表(由 IFS 变量分割的多个字符串),因此如果希望返回一个数组,需要用()
包起来,即(${arr[@]/ele})
。
简单实践如下:
|
|
此时如果把@
换成*
效果也是一样的。
|
|
而将${arr[@]/ele}
放到""
之中和不放到""
之中,也是有差别的
若将${arr[@]/ele}
放到""
之中,则删除元素之后,数组长度不变。
|
|
将${arr[*]/ele}
放到""
之中,则删除元素之后,数组长度变为 1。
|
|
因此,推荐使用${arr[@]/ele}
,且不放到""
之中。
其实这个删除语法,跟字符串中删除特定字符串的语法是一样的,只不过没有删除一个和删除多个的区别,在数组中是都删除。
通配符的简单使用:
|
|
删除语法的另一种使用方式,用于判断元素是否在数组中:
|
|