Bash 的关联数组详解
Bash 支持关联数组(associative arrays),可以使用任意的字符串、或者整数作为下标来访问数组元素。
关联数组的下标和值称为键值对,它们是一一对应关系,键是唯一的,值可以不唯一。
要使用关联数组之前,需要用 declare -A array_name
来进行显式声明 array_name 变量为关联数组。
查看 help declare 对 -A
选项的说明如下:
-A
to make NAMEs associative arrays (if supported)
例如下面的语句定义了一个名为 filetypes 的关联数组,并为数组赋值:
$ declare -A filetypes=([txt]=text [sh]=shell [mk]=makefile)
$ filetypes[c]="c source file"
在使用数组名进行赋值时,需要用小括号 ()
把所有的值括起来。
在关联数组里面,用方括号 []
括起来的值是 key。
为方括号 []
赋予的值是该 key 对应的 value。
不同的键值对之间用空格隔开。注意不是用逗号隔开。
也可以使用 filetypes[key]=value
的方式单独为指定的关联数组元素赋值。
如果所给的 key 之前不存在,bash 会自动创建它。
如果已经存在,则修改它的值为 value 对应的值。
基于前面定义的 filetypes 这个数组名:
-
${!filetypes[*]}
:获取关联数组的所有键名,注意在 filetypes 前面有一个感叹号 ‘!’。$ echo ${!filetypes[*]} txt sh c mk
-
${!filetypes[@]}
: 获取关联数组的所有键名。后面会说明使用*
和@
的区别。$ echo ${!filetypes[@]} txt sh c mk
-
${filetypes[*]}
:获取关联数组的所有值。相比于获取键名的表达式,少了前面的感叹号 ‘!’。$ echo ${filetypes[*]} text shell c source file makefile
-
${filetypes[@]}
:获取关联数组的所有值。$ echo ${filetypes[@]} text shell c source file makefile
-
${#filetypes[*]}
:获取关联数组的长度,即元素个数。注意在 filetypes 前面有一个井号 ‘#’。$ echo ${#filetypes[*]} 4
-
${#filetypes[@]}
:获取关联数组的长度,即元素个数$ echo ${#filetypes[@]} 4
-
${filetypes[key]}
:获取 key 这个键名对应的值。注意大括号{}
是必须的。$ echo ${filetypes[sh]} shell $ echo $filetypes[sh] [sh] # 可以看到,不加大括号时,并不能获取到数组元素的值
查看 man bash 的 Arrays 小节,说明了这几个表达式的含义,同时还提到使用 *
和 @
的区别,贴出具体的区别如下:
If the word is double-quoted,${name[*]}
expands to a single word with the value of each array member separated by the first character of the IFS special variable, and${name[@]}
expands each element of name to a separate word. When there are no array members,${name[@]}
expands to nothing.
${!name[@]}
and${!name[*]}
expand to the indices assigned in array variable name. The treatment when in double quotes is similar to the expansion of the special parameters @ and * within double quotes.
即,使用 *
时,如果用双引号把整个表达式括起来,例如写为 "${!name[*]}"
、或者 "${name[*]}"
,那么会把所有值合并成一个字符串。
使用 @
时,如果用双引号把整个表达式括起来,例如写为 "${!name[@]}"
、或者 "${name[@]}"
,那么会得到一个字符串数组。
每个数组元素会用双引号括起来,所以数组元素自身的空格不会导致拆分成几个单词。
具体如下面的例子所示,这也是遍历数组元素的例子:
$ for key in "${filetypes[*]}"; do echo "****:" $key; done
****: text shell c source file makefile
$ for key in "${filetypes[@]}"; do echo "@@@@:" $key; done
@@@@: text
@@@@: shell
@@@@: c source file
@@@@: makefile
可以看到,"${filetypes[*]}"
只产生一个字符串,for 循环只遍历一次。
而 "${filetypes[@]}"
产生了多个字符串,for 循环遍历多次,是一个字符串数组。
而且所给的 "c source file" 这个字符串没有被空格隔开成几个单词。
上面的例子也演示了如何用 for
命令来遍历数组元素。
可以使用 declare -p
命令来查看数组具体的键值对关系:
$ declare -p filetypes
declare -A filetypes='([txt]="text" [sh]="shell" [c]="c source file" [mk]="makefile" )'
Bash 的一维数组详解
Bash 只支持一维数组 (one-dimensional indexed array),不支持二维数组。
声明一维数组的方式是:declare -a array_name
。
由于 bash 不要求明确指定变量的类型,其实不声明也可以,按数组的方式直接赋值给变量即可。
查看 help declare 对 -a
选项的说明如下:
-a
to make NAMEs indexed arrays (if supported)
使用 declare -a
声明的数组,默认以数字作为数组下标,而且不需要指定数组长度。
其赋值方式说明如下:
-
array=(value1 value2 value3 ... valueN):这种方式从数组下标 0 开始为数组元素赋值,不同值之间用空格隔开,所给的值可以是数字、字符串等。
$ declare -a array=(1 2 "30" "40" 5) $ echo ${array[@]} 1 2 30 40 5
-
array=([0]=var1 [1]=var2 [2]=var3 ... [n]=varN):这种方式显式提供数组下标,指定为该元素赋值,所给的数组下标可以不连续。
$ declare -a array=([0]=1 [1]=2 [3]="30" [6]="60" [9]=9) $ echo ${array[@]} # 用 ${array[@]} 获取所有数组元素的值 1 2 30 60 9 $ echo ${array[5]} # 上面赋值的时候,跳过了数组下标 5,所以它对应的值为空 $ declare -p array # 使用 declare -p 命令查看,会打印出被赋值的所有元素 declare -a array='([0]="1" [1]="2" [3]="30" [6]="60" [9]="9")'
-
array[0]=value1; array[1]=value2; ...; array[n]=varN:这种方式是单独为数组元素赋值。
$ unset array; declare -a array $ array[0]=0; array[1]=1; array[7]="70" $ declare -p array declare -a array='([0]="0" [1]="1" [7]="70")'
一维数组的其他用法和前面文章介绍的关联数组用法一样。
例如,可以用 ${array[@]}
获取所有数组元素的值,用 ${#array[@]}
获取数组的元素个数,等等。
可以参考下面的代码片段来遍历一维数组元素:
for item in "${array[@]}"; do
echo $item
done
一维数组通过正整数来索引数组元素。
如果提供负整数的下标值,那么它具有特殊含义,表示从数组末尾开始往前索引。
例如,array[-1]
会索引到数组的最后一个元素,array[-2]
索引到数组的倒数第二个元素,依此类推。
具体举例说明如下:
$ declare -a array=([0]=0 [1]=1 [2]="20" [3]=3)
$ echo ${array[-1]}, ${array[-3]}
3, 1
注意:虽然 declare -a
声明的数组要用数字作为数组下标,但是使用字符串作为数组下标并不会报错。
实际测试有一些比较古怪的地方。具体举例如下:
$ declare -a array=([0]=0 [1]=1 [2]="20" [3]=3)
$ array[index]=1000
$ echo ${array[index]}
1000
$ array[new]=2000
$ echo ${array[index]}
2000
$ echo ${array[new]}
2000
$ declare -p array
declare -a array='([0]="2000" [1]="1" [2]="20" [3]="3")'
可以看到,为 array[index]
元素赋值,没有报错,使用 ${array[index]}
可以正常获取到它的值。
但是为 array[new]
赋值为 2000 后,使用 ${array[index]}
打印 index 这个字符串下标对应的数组元素值,发现变成了 2000,跟 ${array[new]}
打印的值一样。
看起来,就像是这两个字符串下标关连到同一个数组元素。
实际上,它们都对应到数组元素 0。可以看到,上面的 declare -p array
命令打印出 [0]
这个元素值变成了 2000。
查看 man bash 的 Arrays 部分,说明如下:
Indexed arrays are referenced using integers (including arithmetic expressions) and are zero-based;An indexed array is created automatically if any variable is assigned to using the syntax name[subscript]=value.
The subscript is treated as an arithmetic expression that must evaluate to a number.Referencing an array variable without a subscript is equivalent to referencing the array with a subscript of 0.
即,indexed array 的下标一定是数字、或者是经过算术表达式 (arithmetic expressions) 计算得到的数字。
如果没有提供数组下标,默认会使用数组下标 0。
由于 bash 的算术表达式在获取变量值时,不需要使用 $
符号,所以上面的 array[index]
实际上相当于 array[$index]
,也就是获取 index 变量的值来作为数组下标。
如果所给的 index 变量没有值,就相当于没有提供数组下标,默认使用数组下标 0,所以为 array[index]
赋值,实际上是为 array[0]
赋值。
同理,为 array[new]
赋值,也是为 array[0]
赋值,会看到 array[index]
的值也跟着改变。
如果 index 变量的值不是 0,而且 new 变量没有值,那么为 array[index]
赋值,将不会影响到 array[new]
。
在上面例子的基础上,继续执行下面语句:
$ index=1
$ array[index]=100
$ echo "array[index] = ${array[index]}, array[1] = ${array[1]}"
array[index] = 100, array[1] = 100
$ array[new]=900
$ echo "array[new] = ${array[new]}, array[0] = ${array[0]}, array[index]=${array[index]}"
array[new] = 900, array[0] = 900, array[index]=100
$ recurse=index
$ array[recurse]=500
$ echo "array[index] = ${array[index]}, array[recurse] = ${array[recurse]}, array[1] = ${array[1]}"
array[index] = 500, array[recurse] = 500, array[1] = 500
可以看到,将 index 变量赋值为 1,修改 array[index]
的值,则改变的是数组下标 1 对应的元素、也就是 array[1]
的值。
即相当于用 $index
获取该变量的值来作为数组下标。
此时,由于没有为 new 变量赋值,修改 array[new]
的值还是关连到 array[0]
,不会影响到 array[index]
。
如果将变量赋值为字符串,那么会往下递归获取该字符串对应的变量值。
上面将 recurse 赋值为 "index" 字符串,修改 array[recurse]
的值,可以看到 array[1]
的值被改变了。
即相当于先用 $recurse
获取 recurse 变量的值是 "index",发现是字符串,继续把 "index" 字符串作为变量名。
用 $index
来获取 index 变量的值是 1,最终使用 1 作为数组下标。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。