Bash 数组
Bash 数组
数组(array)是一个包含多个值的变量。成员的编号从 0 开始,数量没有上限,也没有要求成员被连续索引。
数量没有上限和不要求成员连续,真的很自由
数组可以定义为相似类型元素的集合。与大多数编程语言不同,Bash 中的数组不必是相似类型元素的集合。由于 Bash 不能将字符串与数字区分开,因此数组可以同时包含字符串和数字。
Bash 仅支持一维数组(不支持多维数组),并且没有限定数组的长度大小。Bash 还支持关联数组(类似于 Java Map 类型的数据结构)。
Bash 支持下标访问,如果要从最后一个访问数字索引数组,可以使用负索引。索引 -1
是最后一个元素的参考。可以在数组中使用几个元素
这种方式跟 Python 中的数据容器的下标访问很像,具体请看 Python 的《数据容器.md》的
序列的切片操作
输出数组的时候,一个元素换一行
$(printf "%s\n" "${hosts_config[@]}")
创建数组
数组可以采用逐个赋值的方法创建。
ARRAY[INDEX]=value
上面语法中,ARRAY
是数组的名字,可以是任意合法的变量名。INDEX
是一个大于或等于零的整数,也可以是算术表达式。注意数组第一个元素的下标是 0,而不是 1。
下面创建一个三个成员的数组。
$ array[0]=val
$ array[1]=val
$ array[2]=val
数组也可以采用一次性赋值的方式创建。
ARRAY=(value1 value2 ... valueN)
# 等同于
ARRAY=(
value1
value2
value3
)
采用上面方式创建数组时,可以按照默认顺序赋值,也可以在每个值前面指定位置。
$ array=(a b c)
$ array=([2]=c [0]=a [1]=b)
$ days=(Sun Mon Tue Wed Thu Fri Sat)
$ days=([0]=Sun [1]=Mon [2]=Tue [3]=Wed [4]=Thu [5]=Fri [6]=Sat)
只为某些值指定位置,也是可以的。
names=(hatter [5]=duchess alice)
上面例子中,hatter
是数组的 0 号位置,duchess
是 5 号位置,alice
是 6 号位置。
这种情况下,可以这样理解,先将未指定位置的元素组成数组,然后将指定位置的元素依次插入指定位置
没有赋值的数组元素的默认值是空字符串。
定义数组的时候,可以使用通配符。
$ mp3s=( *.mp3 )
上面例子中,将当前目录的所有 MP3 文件,放进一个数组。
具体请看《Bash 的模式拓展》中的
星号拓展
小节
我们还可以通过大括号拓展来快速构建列表:
$ arr=({1..10})
$ echo ${arr[@]}
1 2 3 4 5 6 7 8 9 10
具体请看《Bash 的模式拓展》的
大括号扩展
小节
先用 declare -a
命令声明一个数组对象,也是可以的。
$ declare -a ARRAYNAME
简单实践如下:
$ declare -a arr
$ arr[0]=11
$ arr[1]=22
$ echo ${arr[@]}
11 22
我们还可以 declare -A
声明一个关联数组,
$ declare -A MAPARRAY
关于 declare 命令,请看《Bash 变量》的
declare 命令
小节
什么是关联数组?关联数组可以使用任意的字符串、或者整数作为下标来访问数组元素。有点像 Map
这种数据结构。
简单实践如下:
$ declare -A asso_arr
$ asso_arr['a']=aaa
$ asso_arr['b']=bbb
$ echo ${asso_arr[@]}
aaa bbb
$ echo ${asso_arr['b']}
bbb
$ echo ${asso_arr['a']}
aaa
$ declare -A site=(["google"]="www.google.com" ["runoob"]="www.runoob.com" ["taobao"]="www.taobao.com")
$ echo ${site['google']}
www.google.com
$ echo ${site['runoob']}
www.runoob.com
$ echo ${site[@]}
www.google.com www.runoob.com www.taobao.com
read -a
命令则是将用户的命令行输入,存入一个数组。
$ read -a dice
上面命令将用户的命令行输入,存入数组 dice
。
具体请看《Bash read 命令》的
参数
小节
读取数组
读取单个元素
读取数组指定位置的成员,要使用下面的语法。
$ echo ${array[i]} # i 是索引
上面语法里面的大括号是必不可少的,否则 Bash 会输出数组的第一个元素同时索引部分 [i]
紧跟其后原样输出。
简单实践如下:
$ arr=(aa bb)
$ echo ${arr[0]}
aa
$ echo $arr[0]
aa[0]
数组的下标支持负数,从最后一位元素(-1)开始遍历。
从左往右数,即正序的时候,第一个字符的下标是 0,从右往左数,即倒序的时候,第一个数的下标是 -1。
读取所有成员
@
和 *
是数组的特殊索引,表示返回数组的所有成员。这两个特殊索引配合 for
循环,就可以用来遍历数组。
$ arr=({1..10})
$ for ele in ${arr[@]};do echo $ele;done
1
2
3
4
5
6
7
8
9
10
注意:@
或 *
放不放在双引号之中,是有差别的。如果数组元素自带空格的话,区别就会显示出来。
先看 ${arr[@]}
放在引号中和不放引号中的区别:
$ arr=(aaa bbb "ccc ddd" eeee "fff ggg")
# 结果不正确
$ for ele in ${arr[@]};do echo $ele;done
aaa
bbb
ccc
ddd
eeee
fff
ggg
# 结果正确
$ for ele in "${arr[@]}";do echo $ele;done
aaa
bbb
ccc ddd
eeee
fff ggg
可以看到,将 ${arr[@]}
放到引号中才能正确遍历,不放到引号中的时候,数组元素的内容会以空拆分成多个元素输出,这是不对的。
再来看看 ${arr[*]}
放到引号中和不妨到引号中的区别
$ arr=(aaa bbb "ccc ddd" eeee "fff ggg")
# 结果不正确
$ for ele in ${arr[*]};do echo $ele;done
aaa
bbb
ccc
ddd
eeee
fff
ggg
# 结果不正确,且只有一个元素
$ for ele in "${arr[*]}";do echo element: $ele;done
element: aaa bbb ccc ddd eeee fff ggg
${arr[*]}
不放在双引号之中,跟 ${arr[@]}
不放在双引号之中是一样的。但是当 ${arr[*]}
放在双引号之中,整个数组变成了一个字符串,即只有一个元素用于循环。
因此,我们不建议使用 ${arr[*]}
访问数组的所有元素,推荐使用 ${arr[@]}
。
拷贝一个数组的最方便方法,就是写成下面这样。
$ arr_copy=( "${arr[@]}" )
这种写法也可以用来为新数组添加成员。
$ arr_copy=( "${arr[@]}" aaa "bbb ccc")
简单实践如下:
$ arr_copy=( "${arr[@]}" hhh "iiii jjjj " )
$ for ele in "${arr_copy[@]}";do echo $ele;done
aaa
bbb
ccc ddd
eeee
fff ggg
hhh
iiii jjjj
这种复制数组的写法,很像 Python 的自动拆包,具体请看《Python 解包》
默认位置
如果读取数组成员时,没有读取指定哪一个位置的成员,默认使用 0
号位置。
$ declare -a foo
$ foo=A
$ echo ${foo[0]}
A
上面例子中,foo
是一个数组,赋值的时候不指定位置,实际上是给 foo[0]
赋值。
引用一个不带下标的数组变量,则引用的是 0
号位置的数组元素。
$ foo=(a b c d e f)
$ echo ${foo}
a
$ echo $foo
a
上面例子中,引用数组元素的时候,没有指定位置,结果返回的是 0
号位置。
数组的长度
要想知道数组的长度(即一共包含多少成员),可以使用下面两种语法。
${#array[*]}
${#array[@]}
这个语法跟获取字符串长度是一样的,获取字符串长度
${#str}
提取数组序号
数组的元素的数量没有上限,也没有要求成员被连续索引。
${!array[@]}
或 ${!array[*]}
,可以返回数组的成员序号,即哪些位置是有值的。
$ arr=([5]=a [9]=b [23]=c)
$ echo ${!arr[@]}
5 9 23
$ echo ${!arr[*]}
5 9 23
上面例子中,数组的 5、9、23 号位置有值。
利用这个语法,也可以通过 for
循环遍历数组。
arr=(a b c d)
for i in ${!arr[@]};do
echo ${arr[i]}
done
我们在删除元素之后,实际上并没有删除元素,而只是将其设置为了空字符串,数组的元素并没有变少,数组的长度也没有变,这样其实挺不方便,我们可以通过以下这段脚本,清理数组中的空字符串元素,返回一个不包含空字符串元素的数组。
创建 arr_clean.sh
#!/usr/bin/env bash
arr=([2]=111 [8]=222 [9]=3333)
echo "${arr[@]}"
echo "${!arr[@]}"
i=0
for index in "${!arr[@]}"; do
arr_new[i]=${arr[index]}
((i++))
done
echo "${arr_new[@]}"
echo "${!arr_new[@]}"
exit
赋权后执行
$ ./arr_clean.sh
111 222 3333
2 8 9
111 222 3333
0 1 2
可以看到数组的索引重新整理为连续的。
提取子数组
${array[@]:position:length}
的语法可以提取数组成员。
这个语法跟字符串获取子串的语法
${varname:offset:length}
不一样,不支持负数下标关于字符串的子串,请看《Bash 字符串操作》的
子字符串
小节
$ food=( apples bananas cucumbers dates eggs fajitas grapes )
$ echo ${food[@]:1:1}
bananas
$ echo ${food[@]:1:3}
bananas cucumbers dates
上面例子中,${food[@]:1:1}
返回从数组 1 号位置开始的 1 个成员,${food[@]:1:3}
返回从 1 号位置开始的 3 个成员。
如果省略长度参数 length
,则返回从指定位置开始的所有成员。
$ echo ${food[@]:4}
eggs fajitas grapes
上面例子返回从 4 号位置开始到结束的所有成员。
注意,${array[@]:position:length}
返回的是一个列表,或者说序列,其本质上是一个字符串,而不是一个数组,需要将其放到 ()
中,才是一个数组。
简单实践如下:
$ arr=(111 222 333 444 555 666)
$ arr_sub=${arr[@]:2:2}
$ echo ${arr_sub[@]}
333 444
$ echo ${#arr_sub[@]}
1
$ arr_sub=(${arr[@]:2:2})
$ echo ${#arr_sub[@]}
2
此时是一个全新的数组,跟原数组已经没有关系了。所以对这个子数组的修改,不会同步到原数组
Bash 中不存在对象,所以也不存在引用传递
两个数组的合并
两个数组的合并,可以通过 arrA+=arrB
这种格式,此时 arrB 的元素会追加到 arrA 的后面。
我们可以通过这种语法来在数组末尾追加成员,只要将这个成员通过 ()
包装成一个数组即可。这样就能够实现自动地把值追加到数组末尾的效果。否则,就需要知道数组的最大序号,比较麻烦。
$ arr=(111 222 333 444 555 666)
$ echo ${arr[@]}
111 222 333 444 555 666
$ arr+=(666 777 888)
$ echo ${arr[@]}
111 222 333 444 555 666 666 777 888
$ for index in {1..15};do arr+=($index);done
$ echo ${arr[@]}
111 222 333 444 555 666 666 777 888 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
删除数组成员
删除一个数组成员,使用 unset
命令。将某个成员设为空值,可以从 ${arr[@]}
的返回值中“隐藏”这个成员。同时 ${#arr[@]}
和 ${!arr[@]}
也会相应减少
关于
unset
命令,请看《Bash 变量》的删除变量
$ arr=(111 222 333)
$ unset arr[1]
$ echo ${arr[@]}
111 333
$ echo ${#arr[@]}
2
$ echo ${!arr[@]}
0 2
将数组的成员设为空字符串,也可以实现让某个成员从 ${arr[@]}
的返回值中隐藏,注意,这里是“隐藏”,而不是删除,因为这个成员仍然存在,只是值变成了空值。此时 ${#arr[@]}
和 ${!arr[@]}
的结果不会有变化
$ arr=(111 222 333)
$ arr[1]=""
$ echo ${arr[@]}
111 333
$ echo ${#arr[@]}
3
$ echo ${!arr[@]}
0 1 2
由于空值就是空字符串,所以下面这样写也有隐藏效果,但是不建议这种写法。
$ arr[1]=
因此如果不是有特别的目的,建议使用 unset
删除数组成员。
unset ArrayName
可以清空整个数组。
$ arr=(111 222 333)
$ unset arr
$ echo ${arr[@]}
在有些情况下,我们想删除数组中的多个指定元素,比如我们想删除 arr
数组中的所有的 ele
元素,此时可以使用 ${arr[@]/ele}
这样的语法,而且,ele
部分还可以使用 ?
或者 *
等通配符来进行匹配。
不过注意,通过 ${arr[@]/ele}
返回的结果并不是一个数组而是一个列表(由 IFS 变量分割的多个字符串),因此如果希望返回一个数组,需要用 ()
包起来,即 (${arr[@]/ele})
。
简单实践如下:
$ arr=(a b c d a b c d)
$ arr_new=(${arr[@]/b})
$ echo ${arr_new[@]}
a c d a c d
$ echo ${!arr_new[@]}
0 1 2 3 4 5
$ echo ${#arr_new[@]}
6
此时如果把 @
换成 *
效果也是一样的。
$ arr=(a b c d a b c d)
$ arr_new=(${arr[*]/b})
$ echo ${arr_new[@]}
a c d a c d
$ echo ${!arr_new[@]}
0 1 2 3 4 5
$ echo ${#arr_new[@]}
6
而将 ${arr[@]/ele}
放到 ""
之中和不放到 ""
之中,也是有差别的
若将 ${arr[@]/ele}
放到 ""
之中,则删除元素之后,数组长度不变。
$ arr=(a b c d a b c d)
$ arr_new=("${arr[@]/b}")
$ echo ${arr_new[@]}
a c d a c d
$ echo ${!arr_new[@]}
0 1 2 3 4 5 6 7
$ echo ${#arr_new[@]}
8
将 ${arr[*]/ele}
放到 ""
之中,则删除元素之后,数组长度变为 1。
$ arr=(a b c d a b c d)
$ arr_new=("${arr[*]/b}")
$ echo ${arr_new[@]}
a c d a c d
$ echo ${!arr_new[@]}
0
$ echo ${#arr_new[@]}
1
因此,推荐使用 ${arr[@]/ele}
,且不放到 ""
之中。
其实这个删除语法,跟字符串中删除特定字符串的语法是一样的,只不过没有删除一个和删除多个的区别,在数组中是都删除。
通配符的简单使用:
$ arr=(abcd aab ccc eee)
$ arr_new=(${arr[@]/*ab*})
$ echo ${arr_new[@]}
ccc eee
删除语法的另一种使用方式,用于判断元素是否在数组中:
if [ "${Hostname[@]/${host}}" != "${Hostname[@]}" ]; then command;fi