新版本的Bash支持一维数组. 数组元素可以使用符号variable[xx]来初始化. 另外, 脚本可以使用declare -a variable语句来指定一个数组. 如果想解引用一个数组元素(也就是取值), 可以使用大括号, 访问形式为${variable[xx]}.
例子 26-1. 简单的数组使用
1 #!/bin/bash
2
3
4 area[11]=23
5 area[13]=37
6 area[51]=UFOs
7
8 # 数组成员不一定非得是相邻或连续的.
9
10 # 数组的部分成员可以不被初始化.
11 # 数组中允许空缺元素.
12 # 实际上, 保存着稀疏数据的数组("稀疏数组")
13 #+ 在电子表格处理软件中是非常有用的.
14
15
16 echo -n "area[11] = "
17 echo ${area[11]} # 需要{大括号}.
18
19 echo -n "area[13] = "
20 echo ${area[13]}
21
22 echo "Contents of area[51] are ${area[51]}."
23
24 # 没被初始化的数组成员打印为空值(null变量).
25 echo -n "area[43] = "
26 echo ${area[43]}
27 echo "(area[43] unassigned)"
28
29 echo
30
31 # 两个数组元素的和被赋值给另一个数组元素
32 area[5]=`expr ${area[11]} + ${area[13]}`
33 echo "area[5] = area[11] + area[13]"
34 echo -n "area[5] = "
35 echo ${area[5]}
36
37 area[6]=`expr ${area[11]} + ${area[51]}`
38 echo "area[6] = area[11] + area[51]"
39 echo -n "area[6] = "
40 echo ${area[6]}
41 # 这里会失败, 是因为不允许整数与字符串相加.
42
43 echo; echo; echo
44
45 # -----------------------------------------------------------------
46 # 另一个数组, "area2".
47 # 另一种给数组变量赋值的方法...
48 # array_name=( XXX YYY ZZZ ... )
49
50 area2=( zero one two three four )
51
52 echo -n "area2[0] = "
53 echo ${area2[0]}
54 # 阿哈, 从0开始计算数组下标(也就是数组的第一个元素为[0], 而不是[1]).
55
56 echo -n "area2[1] = "
57 echo ${area2[1]} # [1]是数组的第2个元素.
58 # -----------------------------------------------------------------
59
60 echo; echo; echo
61
62 # -----------------------------------------------
63 # 第3个数组, "area3".
64 # 第3种给数组元素赋值的方法...
65 # array_name=([xx]=XXX [yy]=YYY ...)
66
67 area3=([17]=seventeen [24]=twenty-four)
68
69 echo -n "area3[17] = "
70 echo ${area3[17]}
71
72 echo -n "area3[24] = "
73 echo ${area3[24]}
74 # -----------------------------------------------
75
76 exit 0 |
![]() | Bash允许把变量当成数组来操作, 即使这个变量没有明确地被声明为数组.
|
例子 26-2. 格式化一首诗
1 #!/bin/bash
2 # poem.sh: 将本书作者非常喜欢的一首诗, 漂亮的打印出来.
3
4 # 诗的行数(单节).
5 Line[1]="I do not know which to prefer,"
6 Line[2]="The beauty of inflections"
7 Line[3]="Or the beauty of innuendoes,"
8 Line[4]="The blackbird whistling"
9 Line[5]="Or just after."
10
11 # 出处.
12 Attrib[1]=" Wallace Stevens"
13 Attrib[2]="\"Thirteen Ways of Looking at a Blackbird\""
14 # 这首诗已经是公共版权了(版权已经过期了).
15
16 echo
17
18 for index in 1 2 3 4 5 # 5行.
19 do
20 printf " %s\n" "${Line[index]}"
21 done
22
23 for index in 1 2 # 出处为2行.
24 do
25 printf " %s\n" "${Attrib[index]}"
26 done
27
28 echo
29
30 exit 0
31
32 # 练习:
33 # -----
34 # 修改这个脚本, 使其能够从一个文本数据文件中提取出一首诗的内容, 然后将其漂亮的打印出来. |
数组元素有它们独特的语法, 甚至标准Bash命令和操作符, 都有特殊的选项用以配合数组操作.
例子 26-3. 多种数组操作
1 #!/bin/bash
2 # array-ops.sh: 更多有趣的数组用法.
3
4
5 array=( zero one two three four five )
6 # 数组元素 0 1 2 3 4 5
7
8 echo ${array[0]} # 0
9 echo ${array:0} # 0
10 # 第一个元素的参数扩展,
11 #+ 从位置0(#0)开始(即第一个字符).
12 echo ${array:1} # ero
13 # 第一个元素的参数扩展,
14 #+ 从位置1(#1)开始(即第2个字符).
15
16 echo "--------------"
17
18 echo ${#array[0]} # 4
19 # 第一个数组元素的长度.
20 echo ${#array} # 4
21 # 第一个数组元素的长度.
22 # (另一种表示形式)
23
24 echo ${#array[1]} # 3
25 # 第二个数组元素的长度.
26 # Bash中的数组是从0开始索引的.
27
28 echo ${#array[*]} # 6
29 # 数组中的元素个数.
30 echo ${#array[@]} # 6
31 # 数组中的元素个数.
32
33 echo "--------------"
34
35 array2=( [0]="first element" [1]="second element" [3]="fourth element" )
36
37 echo ${array2[0]} # 第一个元素
38 echo ${array2[1]} # 第二个元素
39 echo ${array2[2]} #
40 # 因为并没有被初始化, 所以此值为null.
41 echo ${array2[3]} # 第四个元素
42
43
44 exit 0 |
大部分标准字符串操作都可以用于数组中.
例子 26-4. 用于数组的字符串操作
1 #!/bin/bash
2 # array-strops.sh: 用于数组的字符串操作.
3 # 本脚本由Michael Zick所编写.
4 # 授权使用在本书中.
5
6 # 一般来说, 任何类似于${name ... }(这种形式)的字符串操作
7 #+ 都能够应用于数组中的所有字符串元素,
8 #+ 比如说${name[@] ... }或${name[*] ...}这两种形式.
9
10
11 arrayZ=( one two three four five five )
12
13 echo
14
15 # 提取尾部的子串
16 echo ${arrayZ[@]:0} # one two three four five five
17 # 所有元素.
18
19 echo ${arrayZ[@]:1} # two three four five five
20 # element[0]后边的所有元素.
21
22 echo ${arrayZ[@]:1:2} # two three
23 # 只提取element[0]后边的两个元素.
24
25 echo "-----------------------"
26
27 # 子串删除
28 # 从字符串的开头删除最短的匹配,
29 #+ 匹配的子串也可以是正则表达式.
30
31 echo ${arrayZ[@]#f*r} # one two three five five
32 # 匹配将应用于数组的所有元素.
33 # 匹配到了"four", 并且将它删除.
34
35 # 从字符串的开头删除最长的匹配
36 echo ${arrayZ[@]##t*e} # one two four five five
37 # 匹配将应用于数组的所有元素.
38 # 匹配到了"three", 并且将它删除.
39
40 # 从字符串的结尾删除最短的匹配
41 echo ${arrayZ[@]%h*e} # one two t four five five
42 # 匹配将应用于数组的所有元素.
43 # 匹配到了"hree", 并且将它删除.
44
45 # 从字符串的结尾删除最长的匹配
46 echo ${arrayZ[@]%%t*e} # one two four five five
47 # 匹配将应用于数组的所有元素.
48 # 匹配到了"three", 并且将它删除.
49
50 echo "-----------------------"
51
52 # 子串替换
53
54 # 第一个匹配到的子串将会被替换
55 echo ${arrayZ[@]/fiv/XYZ} # one two three four XYZe XYZe
56 # 匹配将应用于数组的所有元素.
57
58 # 所有匹配到的子串都会被替换
59 echo ${arrayZ[@]//iv/YY} # one two three four fYYe fYYe
60 # 匹配将应用于数组的所有元素.
61
62 # 删除所有的匹配子串
63 # 如果没有指定替换字符串的话, 那就意味着'删除'
64 echo ${arrayZ[@]//fi/} # one two three four ve ve
65 # 匹配将应用于数组的所有元素.
66
67 # 替换字符串前端子串
68 echo ${arrayZ[@]/#fi/XY} # one two three four XYve XYve
69 # 匹配将应用于数组的所有元素.
70
71 # 替换字符串后端子串
72 echo ${arrayZ[@]/%ve/ZZ} # one two three four fiZZ fiZZ
73 # 匹配将应用于数组的所有元素.
74
75 echo ${arrayZ[@]/%o/XX} # one twXX three four five five
76 # 为什么?
77
78 echo "-----------------------"
79
80
81 # 在将处理后的结果发送到awk(或者其他的处理工具)之前 --
82 # 回忆一下:
83 # $( ... )是命令替换.
84 # 函数作为子进程运行.
85 # 函数结果输出到stdout.
86 # 用read来读取函数的stdout.
87 # 使用name[@]表示法指定了一个"for-each"操作.
88
89 newstr() {
90 echo -n "!!!"
91 }
92
93 echo ${arrayZ[@]/%e/$(newstr)}
94 # on!!! two thre!!! four fiv!!! fiv!!!
95 # Q.E.D: 替换动作实际上是一个'赋值'.
96
97 # 使用"For-Each"形式的
98 echo ${arrayZ[@]//*/$(newstr optional_arguments)}
99 # 现在, 如果Bash只将匹配到的子串作为$0
100 #+ 传递给将被调用的函数 . . .
101
102 echo
103
104 exit 0 |
命令替换可以构造数组的独立元素. (译者注: 换句话说, 就是命令替换也能够给数组赋值.)
例子 26-5. 将脚本的内容赋值给数组
1 #!/bin/bash
2 # script-array.sh: 将这个脚本的内容赋值给数组.
3 # 这个脚本的灵感来自于Chris Martin的e-mail(感谢!).
4
5 script_contents=( $(cat "$0") ) # 将这个脚本的内容($0)
6 #+ 赋值给数组.
7
8 for element in $(seq 0 $((${#script_contents[@]} - 1)))
9 do # ${#script_contents[@]}
10 #+ 表示数组元素的个数.
11 #
12 # 一个小问题:
13 # 为什么必须使用seq 0?
14 # 用seq 1来试一下.
15 echo -n "${script_contents[$element]}"
16 # 在同一行上显示脚本中每个域的内容.
17 echo -n " -- " # 使用 " -- " 作为域分割符.
18 done
19
20 echo
21
22 exit 0
23
24 # 练习:
25 # -----
26 # 修改这个脚本,
27 #+ 让这个脚本能够按照它原本的格式输出,
28 #+ 连同空白, 换行, 等等. |
在数组环境中, 某些Bash内建命令的含义可能会有些轻微的改变. 比如, unset命令可以删除数组元素, 甚至能够删除整个数组.
例子 26-6. 一些数组专用的小道具
1 #!/bin/bash
2
3 declare -a colors
4 # 脚本中所有的后续命令都会把
5 #+ 变量"colors"看作数组.
6
7 echo "Enter your favorite colors (separated from each other by a space)."
8
9 read -a colors # 至少需要键入3种颜色, 以便于后边的演示.
10 # 'read'命令的特殊选项,
11 #+ 允许给数组元素赋值.
12
13 echo
14
15 element_count=${#colors[@]}
16 # 提取数组元素个数的特殊语法.
17 # 用element_count=${#colors[*]}也一样.
18 #
19 # "@"变量允许在引用中存在单词分割(word splitting)
20 #+ (依靠空白字符来分隔变量).
21 #
22 # 这就好像"$@"和"$*"
23 #+ 在位置参数中的所表现出来的行为一样.
24
25 index=0
26
27 while [ "$index" -lt "$element_count" ]
28 do # 列出数组中的所有元素.
29 echo ${colors[$index]}
30 let "index = $index + 1"
31 # 或:
32 # index+=1
33 # 如果你运行的Bash版本是3.1以后的话, 才支持这种语法.
34 done
35 # 每个数组元素被列为单独的一行.
36 # 如果没有这种要求的话, 可以使用echo -n "${colors[$index]} "
37 #
38 # 也可以使用"for"循环来做:
39 # for i in "${colors[@]}"
40 # do
41 # echo "$i"
42 # done
43 # (感谢, S.C.)
44
45 echo
46
47 # 再次列出数组中的所有元素, 不过这次的做法更优雅.
48 echo ${colors[@]} # 用echo ${colors[*]}也行.
49
50 echo
51
52 # "unset"命令即可以删除数组数据, 也可以删除整个数组.
53 unset colors[1] # 删除数组的第2个元素.
54 # 作用等效于 colors[1]=
55 echo ${colors[@]} # 再次列出数组内容, 第2个元素没了.
56
57 unset colors # 删除整个数组.
58 # unset colors[*] 或
59 #+ unset colors[@] 都可以.
60 echo; echo -n "Colors gone."
61 echo ${colors[@]} # 再次列出数组内容, 内容为空.
62
63 exit 0 |
正如我们在前面例子中所看到的, ${array_name[@]}或${array_name[*]}都与数组中的所有元素相关. 同样的, 为了计算数组的元素个数, 可以使用${#array_name[@]}或${#array_name[*]}. ${#array_name}是数组第一个元素的长度, 也就是${array_name[0]}的长度(字符个数).
例子 26-7. 空数组与包含空元素的数组
1 #!/bin/bash
2 # empty-array.sh
3
4 # 感谢Stephane Chazelas制作这个例子的原始版本,
5 #+ 同时感谢Michael Zick对这个例子所作的扩展.
6
7
8 # 空数组与包含有空元素的数组, 这两个概念不同.
9
10 array0=( first second third )
11 array1=( '' ) # "array1"包含一个空元素.
12 array2=( ) # 没有元素 . . . "array2"为空.
13
14 echo
15 ListArray()
16 {
17 echo
18 echo "Elements in array0: ${array0[@]}"
19 echo "Elements in array1: ${array1[@]}"
20 echo "Elements in array2: ${array2[@]}"
21 echo
22 echo "Length of first element in array0 = ${#array0}"
23 echo "Length of first element in array1 = ${#array1}"
24 echo "Length of first element in array2 = ${#array2}"
25 echo
26 echo "Number of elements in array0 = ${#array0[*]}" # 3
27 echo "Number of elements in array1 = ${#array1[*]}" # 1 (惊奇!)
28 echo "Number of elements in array2 = ${#array2[*]}" # 0
29 }
30
31 # ===================================================================
32
33 ListArray
34
35 # 尝试扩展这些数组.
36
37 # 添加一个元素到这个数组.
38 array0=( "${array0[@]}" "new1" )
39 array1=( "${array1[@]}" "new1" )
40 array2=( "${array2[@]}" "new1" )
41
42 ListArray
43
44 # 或
45 array0[${#array0[*]}]="new2"
46 array1[${#array1[*]}]="new2"
47 array2[${#array2[*]}]="new2"
48
49 ListArray
50
51 # 如果你按照上边的方法对数组进行扩展的话; 数组比较象'栈'
52 # 上边的操作就是'压栈'
53 # 栈'高'为:
54 height=${#array2[@]}
55 echo
56 echo "Stack height for array2 = $height"
57
58 # '出栈'就是:
59 unset array2[${#array2[@]}-1] # 数组从0开始索引,
60 height=${#array2[@]} #+ 这意味着第一个数组下标为0.
61 echo
62 echo "POP"
63 echo "New stack height for array2 = $height"
64
65 ListArray
66
67 # 只列出数组array0的第二个和第三个元素.
68 from=1 # 从0开始索引.
69 to=2 #
70 array3=( ${array0[@]:1:2} )
71 echo
72 echo "Elements in array3: ${array3[@]}"
73
74 # 处理方式就像是字符串(字符数组).
75 # 试试其他的"字符串"形式.
76
77 # 替换:
78 array4=( ${array0[@]/second/2nd} )
79 echo
80 echo "Elements in array4: ${array4[@]}"
81
82 # 替换掉所有匹配通配符的字符串.
83 array5=( ${array0[@]//new?/old} )
84 echo
85 echo "Elements in array5: ${array5[@]}"
86
87 # 当你开始觉得对此有把握的时候 . . .
88 array6=( ${array0[@]#*new} )
89 echo # 这个可能会让你感到惊奇.
90 echo "Elements in array6: ${array6[@]}"
91
92 array7=( ${array0[@]#new1} )
93 echo # 数组array6之后就没有惊奇了.
94 echo "Elements in array7: ${array7[@]}"
95
96 # 看起来非常像 . . .
97 array8=( ${array0[@]/new1/} )
98 echo
99 echo "Elements in array8: ${array8[@]}"
100
101 # 所以, 让我们怎么形容呢?
102
103 # 对数组var[@]中的每个元素
104 #+ 进行连续的字符串操作.
105 # 因此: 如果结果是长度为0的字符串,
106 #+ Bash支持字符串向量操作,
107 #+ 元素会在结果赋值中消失不见.
108
109 # 一个问题, 这些字符串是强引用还是弱引用?
110
111 zap='new*'
112 array9=( ${array0[@]/$zap/} )
113 echo
114 echo "Elements in array9: ${array9[@]}"
115
116 # 当你还在考虑, 你身在Kansas州何处时 . . .
117 array10=( ${array0[@]#$zap} )
118 echo
119 echo "Elements in array10: ${array10[@]}"
120
121 # 比较array7和array10.
122 # 比较array8和array9.
123
124 # 答案: 必须是弱引用.
125
126 exit 0 |
${array_name[@]}和${array_name[*]}的关系非常类似于$@ and $*. 这种数组用法用处非常广泛.
1 # 复制一个数组.
2 array2=( "${array1[@]}" )
3 # 或
4 array2="${array1[@]}"
5 #
6 # 然而, 如果在"缺项"数组中使用的话, 将会失败,
7 #+ 也就是说数组中存在空洞(中间的某个元素没赋值),
8 #+ 这个问题由Jochen DeSmet指出.
9 # ------------------------------------------
10 array1[0]=0
11 # array1[1]没赋值
12 array1[2]=2
13 array2=( "${array1[@]}" ) # 拷贝它?
14
15 echo ${array2[0]} # 0
16 echo ${array2[2]} # (null), 应该是2
17 # ------------------------------------------
18
19
20
21 # 添加一个元素到数组.
22 array=( "${array[@]}" "new element" )
23 # 或
24 array[${#array[*]}]="new element"
25
26 # 感谢, S.C. |
![]() | array=( element1 element2 ... elementN )初始化操作, 如果有命令替换的帮助, 就可以将一个文本文件的内容加载到数组.
|
出色的技巧使得数组的操作技术又多了一种.
例子 26-8. 初始化数组
1 #! /bin/bash
2 # array-assign.bash
3
4 # 数组操作是Bash所特有的,
5 #+ 所以才使用".bash"作为脚本扩展名.
6
7 # Copyright (c) Michael S. Zick, 2003, All rights reserved.
8 # License: Unrestricted reuse in any form, for any purpose.
9 # Version: $ID$
10 #
11 # 说明与注释由William Park所添加.
12
13 # 基于Stephane Chazelas所提供的
14 #+ 出现在本书中的一个例子.
15
16 # 'times'命令的输出格式:
17 # User CPU <space> System CPU
18 # User CPU of dead children <space> System CPU of dead children
19
20 # Bash有两种方法,
21 #+ 可以将一个数组的所有元素都赋值给一个新的数组变量.
22 # 在2.04, 2.05a和2.05b版本的Bash中,
23 #+ 这两种方法都会丢弃数组中的"空引用"(null值)元素.
24 # 另一种给数组赋值的方法将会被添加到新版本的Bash中,
25 #+ 这种方法采用[subscript]=value形式, 来维护数组下标与元素值之间的关系.
26
27 # 可以使用内部命令来构造一个大数组,
28 #+ 当然, 构造一个包含上千元素数组的其他方法
29 #+ 也能很好的完成任务.
30
31 declare -a bigOne=( /dev/* )
32 echo
33 echo 'Conditions: Unquoted, default IFS, All-Elements-Of'
34 echo "Number of elements in array is ${#bigOne[@]}"
35
36 # set -vx
37
38
39
40 echo
41 echo '- - testing: =( ${array[@]} ) - -'
42 times
43 declare -a bigTwo=( ${bigOne[@]} )
44 # ^ ^
45 times
46
47 echo
48 echo '- - testing: =${array[@]} - -'
49 times
50 declare -a bigThree=${bigOne[@]}
51 # 这次没用括号.
52 times
53
54 # 正如Stephane Chazelas所指出的, 通过比较,
55 #+ 可以了解到第二种格式的赋值比第三或第四种形式更快.
56 #
57 # William Park解释:
58 #+ 数组bigTwo是作为一个单个字符串被赋值的,
59 #+ 而数组bigThree, 则是一个元素一个元素进行的赋值.
60 # 所以, 实质上是:
61 # bigTwo=( [0]="... ... ..." )
62 # bigThree=( [0]="..." [1]="..." [2]="..." ... )
63
64
65 # 在本书的例子中, 我还是会继续使用第一种形式,
66 #+ 因为我认为这种形式更有利于将问题阐述清楚.
67
68 # 在我所使用的例子中, 在其中复用的部分,
69 #+ 还是使用了第二种形式, 那是因为这种形式更快.
70
71 # MSZ: 很抱歉早先的疏忽(译者: 应是指本书的老版本).
72
73
74 # 注意事项:
75 # ---------
76 # 31行和43行的"declare -a"语句其实不是必需的,
77 #+ 因为Array=( ... )形式
78 #+ 只能用于数组赋值.
79 # 然而, 如果省略这些声明的话,
80 #+ 会导致脚本后边的相关操作变慢.
81 # 试一下, 看看发生了什么.
82
83 exit 0 |
![]() | 在数组声明的时候添加一个额外的declare -a语句, 能够加速后续的数组操作速度. |
例子 26-9. 拷贝和连接数组
1 #! /bin/bash
2 # CopyArray.sh
3 #
4 # 这个脚本由Michael Zick所编写.
5 # 已通过作者授权, 可以在本书中使用.
6
7 # 如何"通过名字传值&通过名字返回"(译者注: 这里可以理解为C中的"数组指针", 或C++中的"数组引用")
8 #+ 或者"建立自己的赋值语句".
9
10
11 CpArray_Mac() {
12
13 # 建立赋值命令
14
15 echo -n 'eval '
16 echo -n "$2" # 目的参数名
17 echo -n '=( ${'
18 echo -n "$1" # 原参数名
19 echo -n '[@]} )'
20
21 # 上边这些语句会构成一条命令.
22 # 这仅仅是形式上的问题.
23 }
24
25 declare -f CopyArray # 函数"指针"
26 CopyArray=CpArray_Mac # 构造语句
27
28 Hype()
29 {
30
31 # 需要连接的数组名为$1.
32 # (把这个数组与字符串"Really Rocks"结合起来, 形成一个新数组.)
33 # 并将结果从数组$2中返回.
34
35 local -a TMP
36 local -a hype=( Really Rocks )
37
38 $($CopyArray $1 TMP)
39 TMP=( ${TMP[@]} ${hype[@]} )
40 $($CopyArray TMP $2)
41 }
42
43 declare -a before=( Advanced Bash Scripting )
44 declare -a after
45
46 echo "Array Before = ${before[@]}"
47
48 Hype before after
49
50 echo "Array After = ${after[@]}"
51
52 # 连接的太多了?
53
54 echo "What ${after[@]:3:2}?"
55
56 declare -a modest=( ${after[@]:2:1} ${after[@]:3:2} )
57 # ---- 子串提取 ----
58
59 echo "Array Modest = ${modest[@]}"
60
61 # 'before'发生了什么变化么?
62
63 echo "Array Before = ${before[@]}"
64
65 exit 0 |
例子 26-10. 关于串联数组的更多信息
1 #! /bin/bash
2 # array-append.bash
3
4 # Copyright (c) Michael S. Zick, 2003, All rights reserved.
5 # License: Unrestricted reuse in any form, for any purpose.
6 # Version: $ID$
7 #
8 # 在格式上, 由M.C做了一些修改.
9
10
11 # 数组操作是Bash特有的属性.
12 # 传统的UNIX /bin/sh缺乏类似的功能.
13
14
15 # 将这个脚本的输出通过管道传递给'more',
16 #+ 这么做的目的是防止输出的内容超过终端能够显示的范围.
17
18
19 # 依次使用下标.
20 declare -a array1=( zero1 one1 two1 )
21 # 数组中存在空缺的元素([1]未定义).
22 declare -a array2=( [0]=zero2 [2]=two2 [3]=three2 )
23
24 echo
25 echo '- Confirm that the array is really subscript sparse. -'
26 echo "Number of elements: 4" # 仅仅为了演示, 所以就写死了.
27 for (( i = 0 ; i < 4 ; i++ ))
28 do
29 echo "Element [$i]: ${array2[$i]}"
30 done
31 # 也可以参考一个更通用的例子, basics-reviewed.bash.
32
33
34 declare -a dest
35
36 # 将两个数组合并(附加)到第3个数组.
37 echo
38 echo 'Conditions: Unquoted, default IFS, All-Elements-Of operator'
39 echo '- Undefined elements not present, subscripts not maintained. -'
40 # # 那些未定义的元素不会出现; 组合时会丢弃这些元素.
41
42 dest=( ${array1[@]} ${array2[@]} )
43 # dest=${array1[@]}${array2[@]} # 令人奇怪的结果, 或许是个bug.
44
45 # 现在, 打印结果.
46 echo
47 echo '- - Testing Array Append - -'
48 cnt=${#dest[@]}
49
50 echo "Number of elements: $cnt"
51 for (( i = 0 ; i < cnt ; i++ ))
52 do
53 echo "Element [$i]: ${dest[$i]}"
54 done
55
56 # 将数组赋值给一个数组中的元素(两次).
57 dest[0]=${array1[@]}
58 dest[1]=${array2[@]}
59
60 # 打印结果.
61 echo
62 echo '- - Testing modified array - -'
63 cnt=${#dest[@]}
64
65 echo "Number of elements: $cnt"
66 for (( i = 0 ; i < cnt ; i++ ))
67 do
68 echo "Element [$i]: ${dest[$i]}"
69 done
70
71 # 检查第二个元素的修改状况.
72 echo
73 echo '- - Reassign and list second element - -'
74
75 declare -a subArray=${dest[1]}
76 cnt=${#subArray[@]}
77
78 echo "Number of elements: $cnt"
79 for (( i = 0 ; i < cnt ; i++ ))
80 do
81 echo "Element [$i]: ${subArray[$i]}"
82 done
83
84 # 如果你使用'=${ ... }'形式
85 #+ 将一个数组赋值到另一个数组的一个元素中,
86 #+ 那么这个数组的所有元素都会被转换为一个字符串,
87 #+ 这个字符串中的每个数组元素都以空格进行分隔(其实是IFS的第一个字符).
88
89 # 如果原来数组中的所有元素都不包含空白符 . . .
90 # 如果原来的数组下标都是连续的 . . .
91 # 那么我们就可以将原来的数组进行恢复.
92
93 # 从修改过的第二个元素中, 将原来的数组恢复出来.
94 echo
95 echo '- - Listing restored element - -'
96
97 declare -a subArray=( ${dest[1]} )
98 cnt=${#subArray[@]}
99
100 echo "Number of elements: $cnt"
101 for (( i = 0 ; i < cnt ; i++ ))
102 do
103 echo "Element [$i]: ${subArray[$i]}"
104 done
105 echo '- - Do not depend on this behavior. - -'
106 echo '- - This behavior is subject to change - -'
107 echo '- - in versions of Bash newer than version 2.05b - -'
108
109 # MSZ: 抱歉, 之前混淆了一些要点(译者注: 指的是本书以前的版本).
110
111 exit 0 |
--
有了数组, 我们就可以在脚本中实现一些比较熟悉的算法. 这么做, 到底是不是一个好主意, 我们在这里不做讨论, 还是留给读者决定吧.
例子 26-11. 一位老朋友: 冒泡排序
1 #!/bin/bash
2 # bubble.sh: 一种排序方式, 冒泡排序.
3
4 # 回忆一下冒泡排序的算法. 我们在这里要实现它...
5
6 # 依靠连续的比较数组元素进行排序,
7 #+ 比较两个相邻元素, 如果顺序不对, 就交换这两个元素的位置.
8 # 当第一轮比较结束之后, 最"重"的元素就会被移动到最底部.
9 # 当第二轮比较结束之后, 第二"重"的元素就会被移动到次底部的位置.
10 # 依此类推.
11 # 这意味着每轮比较不需要比较之前已经"沉淀"好的数据.
12 # 因此你会注意到后边的数据在打印的时候会快一些.
13
14
15 exchange()
16 {
17 # 交换数组中的两个元素.
18 local temp=${Countries[$1]} # 临时保存
19 #+ 要交换的那个元素.
20 Countries[$1]=${Countries[$2]}
21 Countries[$2]=$temp
22
23 return
24 }
25
26 declare -a Countries # 声明数组,
27 #+ 此处是可选的, 因为数组在下面被初始化.
28
29 # 我们是否可以使用转义符(\)
30 #+ 来将数组元素的值放在不同的行上?
31 # 可以.
32
33 Countries=(Netherlands Ukraine Zaire Turkey Russia Yemen Syria \
34 Brazil Argentina Nicaragua Japan Mexico Venezuela Greece England \
35 Israel Peru Canada Oman Denmark Wales France Kenya \
36 Xanadu Qatar Liechtenstein Hungary)
37
38 # "Xanadu"虚拟出来的世外桃源.
39 #+
40
41
42 clear # 开始之前的清屏动作.
43
44 echo "0: ${Countries[*]}" # 从索引0开始列出整个数组.
45
46 number_of_elements=${#Countries[@]}
47 let "comparisons = $number_of_elements - 1"
48
49 count=1 # 传递数字.
50
51 while [ "$comparisons" -gt 0 ] # 开始外部循环
52 do
53
54 index=0 # 在每轮循环开始之前, 重置索引.
55
56 while [ "$index" -lt "$comparisons" ] # 开始内部循环
57 do
58 if [ ${Countries[$index]} \> ${Countries[`expr $index + 1`]} ]
59 # 如果原来的排序次序不对...
60 # 回想一下, 在单括号中,
61 #+ \>是ASCII码的比较操作符.
62
63 # if [[ ${Countries[$index]} > ${Countries[`expr $index + 1`]} ]]
64 #+ 这样也行.
65 then
66 exchange $index `expr $index + 1` # 交换.
67 fi
68 let "index += 1" # 或者, index+=1 在Bash 3.1之后的版本才能这么用.
69 done # 内部循环结束
70
71 # ----------------------------------------------------------------------
72 # Paulo Marcel Coelho Aragao建议我们可以使用更简单的for循环.
73 #
74 # for (( last = $number_of_elements - 1 ; last > 1 ; last-- ))
75 # do
76 # for (( i = 0 ; i < last ; i++ ))
77 # do
78 # [[ "${Countries[$i]}" > "${Countries[$((i+1))]}" ]] \
79 # && exchange $i $((i+1))
80 # done
81 # done
82 # ----------------------------------------------------------------------
83
84
85 let "comparisons -= 1" # 因为最"重"的元素到了底部,
86 #+ 所以每轮我们可以少做一次比较.
87
88 echo
89 echo "$count: ${Countries[@]}" # 每轮结束后, 都打印一次数组.
90 echo
91 let "count += 1" # 增加传递计数.
92
93 done # 外部循环结束
94 # 至此, 全部完成.
95
96 exit 0 |
--
我们可以在数组中嵌套数组么?
1 #!/bin/bash
2 # "嵌套"数组.
3
4 # Michael Zick提供了这个用例,
5 #+ William Park做了一些修正和说明.
6
7 AnArray=( $(ls --inode --ignore-backups --almost-all \
8 --directory --full-time --color=none --time=status \
9 --sort=time -l ${PWD} ) ) # 命令及选项.
10
11 # 空格是有意义的 . . . 并且不要在上边用引号引用任何东西.
12
13 SubArray=( ${AnArray[@]:11:1} ${AnArray[@]:6:5} )
14 # 这个数组有六个元素:
15 #+ SubArray=( [0]=${AnArray[11]} [1]=${AnArray[6]} [2]=${AnArray[7]}
16 # [3]=${AnArray[8]} [4]=${AnArray[9]} [5]=${AnArray[10]} )
17 #
18 # Bash数组是字符串(char *)类型
19 #+ 的(循环)链表.
20 # 因此, 这不是真正意义上的嵌套数组,
21 #+ 只不过功能很相似而已.
22
23 echo "Current directory and date of last status change:"
24 echo "${SubArray[@]}"
25
26 exit 0 |
--
如果将"嵌套数组"与间接引用组合起来使用的话, 将会产生一些非常有趣的用法.
例子 26-12. 嵌套数组与间接引用
1 #!/bin/bash
2 # embedded-arrays.sh
3 # 嵌套数组和间接引用.
4
5 # 本脚本由Dennis Leeuw编写.
6 # 经过授权, 在本书中使用.
7 # 本书作者做了少许修改.
8
9
10 ARRAY1=(
11 VAR1_1=value11
12 VAR1_2=value12
13 VAR1_3=value13
14 )
15
16 ARRAY2=(
17 VARIABLE="test"
18 STRING="VAR1=value1 VAR2=value2 VAR3=value3"
19 ARRAY21=${ARRAY1[*]}
20 ) # 将ARRAY1嵌套到这个数组中.
21
22 function print () {
23 OLD_IFS="$IFS"
24 IFS=$'\n' # 这么做是为了每行
25 #+ 只打印一个数组元素.
26 TEST1="ARRAY2[*]"
27 local ${!TEST1} # 删除这一行, 看看会发生什么?
28 # 间接引用.
29 # 这使得$TEST1
30 #+ 只能够在函数内被访问.
31
32
33 # 让我们看看还能干点什么.
34 echo
35 echo "\$TEST1 = $TEST1" # 仅仅是变量名字.
36 echo; echo
37 echo "{\$TEST1} = ${!TEST1}" # 变量内容.
38 # 这就是
39 #+ 间接引用的作用.
40 echo
41 echo "-------------------------------------------"; echo
42 echo
43
44
45 # 打印变量
46 echo "Variable VARIABLE: $VARIABLE"
47
48 # 打印一个字符串元素
49 IFS="$OLD_IFS"
50 TEST2="STRING[*]"
51 local ${!TEST2} # 间接引用(同上).
52 echo "String element VAR2: $VAR2 from STRING"
53
54 # 打印一个数组元素
55 TEST2="ARRAY21[*]"
56 local ${!TEST2} # 间接引用(同上).
57 echo "Array element VAR1_1: $VAR1_1 from ARRAY21"
58 }
59
60 print
61 echo
62
63 exit 0
64
65 # 脚本作者注,
66 #+ "你可以很容易的将其扩展成一个能创建hash的Bash脚本."
67 # (难) 留给读者的练习: 实现它. |
--
数组使得埃拉托色尼素数筛子有了shell版本的实现. 当然, 如果你需要的是追求效率的应用, 那么就应该使用编译行语言来实现, 比如C语言. 因为脚本运行的太慢了.
例子 26-13. 复杂的数组应用: 埃拉托色尼素数筛子
1 #!/bin/bash
2 # sieve.sh (ex68.sh)
3
4 # 埃拉托色尼素数筛子
5 # 找素数的经典算法.
6
7 # 在同等数值的范围内,
8 #+ 这个脚本运行的速度比C版本慢的多.
9
10 LOWER_LIMIT=1 # 从1开始.
11 UPPER_LIMIT=1000 # 到1000.
12 # (如果你时间很多的话 . . . 你可以将这个数值调的很高.)
13
14 PRIME=1
15 NON_PRIME=0
16
17 let SPLIT=UPPER_LIMIT/2
18 # 优化:
19 # 只需要测试中间到最大的值(为什么?).
20 # (译者注: 这个变量在脚本正文并没有被使用, 仅仅在107行之后的优化部分才使用.)
21
22 declare -a Primes
23 # Primes[]是个数组.
24
25
26 initialize ()
27 {
28 # 初始化数组.
29
30 i=$LOWER_LIMIT
31 until [ "$i" -gt "$UPPER_LIMIT" ]
32 do
33 Primes[i]=$PRIME
34 let "i += 1"
35 done
36 # 假定所有数组成员都是需要检查的(素数)
37 #+ 直到检查完成.
38 }
39
40 print_primes ()
41 {
42 # 打印出所有数组Primes[]中被标记为素数的元素.
43
44 i=$LOWER_LIMIT
45
46 until [ "$i" -gt "$UPPER_LIMIT" ]
47 do
48
49 if [ "${Primes[i]}" -eq "$PRIME" ]
50 then
51 printf "%8d" $i
52 # 每个数字打印前先打印8个空格, 在偶数列才打印.
53 fi
54
55 let "i += 1"
56
57 done
58
59 }
60
61 sift () # 查出非素数.
62 {
63
64 let i=$LOWER_LIMIT+1
65 # 我们都知道1是素数, 所以我们从2开始.
66 # (译者注: 从2开始并不是由于1是素数, 而是因为要去掉以后每个数的倍数, 感谢网友KevinChen.)
67 until [ "$i" -gt "$UPPER_LIMIT" ]
68 do
69
70 if [ "${Primes[i]}" -eq "$PRIME" ]
71 # 不要处理已经过滤过的数字(被标识为非素数).
72 then
73
74 t=$i
75
76 while [ "$t" -le "$UPPER_LIMIT" ]
77 do
78 let "t += $i "
79 Primes[t]=$NON_PRIME
80 # 标识为非素数.
81 done
82
83 fi
84
85 let "i += 1"
86 done
87
88
89 }
90
91
92 # ==============================================
93 # main ()
94 # 继续调用函数.
95 initialize
96 sift
97 print_primes
98 # 这里就是被称为结构化编程的东西.
99 # ==============================================
100
101 echo
102
103 exit 0
104
105
106
107 # -------------------------------------------------------- #
108 # 因为前面的'exit'语句, 所以后边的代码不会运行.
109
110 # 下边的代码, 是由Stephane Chazelas所编写的埃拉托色尼素数筛子的改进版本,
111 #+ 这个版本可以运行的快一些.
112
113 # 必须在命令行上指定参数(这个参数就是: 寻找素数的限制范围).
114
115 UPPER_LIMIT=$1 # 来自于命令行.
116 let SPLIT=UPPER_LIMIT/2 # 从中间值到最大值.
117
118 Primes=( '' $(seq $UPPER_LIMIT) )
119
120 i=1
121 until (( ( i += 1 ) > SPLIT )) # 仅需要从中间值检查.
122 do
123 if [[ -n $Primes[i] ]]
124 then
125 t=$i
126 until (( ( t += i ) > UPPER_LIMIT ))
127 do
128 Primes[t]=
129 done
130 fi
131 done
132 echo ${Primes[*]}
133
134 exit 0 |
上边的这个例子是基于数组的素数产生器, 还有不使用数组的素数产生器例子 A-16, 让我们来比较一番.
--
数组可以进行一定程度上的扩展, 这样就可以模拟一些Bash原本不支持的数据结构.
例子 26-14. 模拟一个下推堆栈
1 #!/bin/bash
2 # stack.sh: 模拟下推堆栈
3
4 # 类似于CPU栈, 下推堆栈依次保存数据项,
5 #+ 但是取数据时, 却反序进行, 后进先出.
6
7 BP=100 # 栈数组的基址指针.
8 # 从元素100开始.
9
10 SP=$BP # 栈指针.
11 # 将其初始化为栈"基址"(栈底).
12
13 Data= # 当前栈的数据内容.
14 # 必须定义为全局变量,
15 #+ 因为函数所能够返回的整数存在范围限制.
16
17 declare -a stack
18
19
20 push() # 压栈.
21 {
22 if [ -z "$1" ] # 没有可压入的数据项?
23 then
24 return
25 fi
26
27 let "SP -= 1" # 更新栈指针.
28 stack[$SP]=$1
29
30 return
31 }
32
33 pop() # 从栈中弹出数据项.
34 {
35 Data= # 清空保存数据项的中间变量.
36
37 if [ "$SP" -eq "$BP" ] # 栈空?
38 then
39 return
40 fi # 这使得SP不会超过100,
41 #+ 例如, 这可以防止堆栈失控.
42
43 Data=${stack[$SP]}
44 let "SP += 1" # 更新栈指针.
45 return
46 }
47
48 status_report() # 打印当前状态.
49 {
50 echo "-------------------------------------"
51 echo "REPORT"
52 echo "Stack Pointer = $SP"
53 echo "Just popped \""$Data"\" off the stack."
54 echo "-------------------------------------"
55 echo
56 }
57
58
59 # =======================================================
60 # 现在, 来点乐子.
61
62 echo
63
64 # 看你是否能从空栈里弹出数据项来.
65 pop
66 status_report
67
68 echo
69
70 push garbage
71 pop
72 status_report # 压入garbage, 弹出garbage.
73
74 value1=23; push $value1
75 value2=skidoo; push $value2
76 value3=FINAL; push $value3
77
78 pop # FINAL
79 status_report
80 pop # skidoo
81 status_report
82 pop # 23
83 status_report # 后进, 先出!
84
85 # 注意: 栈指针在压栈时减,
86 #+ 在弹出时加.
87
88 echo
89
90 exit 0
91
92 # =======================================================
93
94
95 # 练习:
96 # -----
97
98 # 1) 修改"push()"函数,
99 # + 使其调用一次就能够压入多个数据项.
100
101 # 2) 修改"pop()"函数,
102 # + 使其调用一次就能弹出多个数据项.
103
104 # 3) 给那些有临界操作的函数添加出错检查.
105 # 说明白一些, 就是让这些函数返回错误码,
106 # + 返回的错误码依赖于操作是否成功完成,
107 # + 如果没有成功完成, 那么就需要启动合适的处理动作.
108
109 # 4) 以这个脚本为基础,
110 # + 编写一个用栈实现的四则运算计算器. |
--
如果想对数组"下标"做一些比较诡异的操作, 可能需要使用中间变量. 对于那些有这种需求的项目来说, 还是应该考虑使用功能更加强大的编程语言, 比如Perl或C.
例子 26-15. 复杂的数组应用: 探索一个神秘的数学序列
1 #!/bin/bash
2
3 # Douglas Hofstadter的声名狼藉的序列"Q-series":
4
5 # Q(1) = Q(2) = 1
6 # Q(n) = Q(n - Q(n-1)) + Q(n - Q(n-2)), 当n>2时
7
8 # 这是一个令人感到陌生的, 没有规律的"乱序"整数序列.
9 # 序列的头20项, 如下所示:
10 # 1 1 2 3 3 4 5 5 6 6 6 8 8 8 10 9 10 11 11 12
11
12 # 请参考相关书籍, Hofstadter的, "_Goedel, Escher, Bach: An Eternal Golden Braid_",
13 #+ 第137页.
14
15
16 LIMIT=100 # 需要计算的数列长度.
17 LINEWIDTH=20 # 每行打印的个数.
18
19 Q[1]=1 # 数列的头两项都为1.
20 Q[2]=1
21
22 echo
23 echo "Q-series [$LIMIT terms]:"
24 echo -n "${Q[1]} " # 输出数列头两项.
25 echo -n "${Q[2]} "
26
27 for ((n=3; n <= $LIMIT; n++)) # C风格的循环条件.
28 do # Q[n] = Q[n - Q[n-1]] + Q[n - Q[n-2]] 当n>2时
29 # 需要将表达式拆开, 分步计算,
30 #+ 因为Bash不能够很好的处理复杂数组的算术运算.
31
32 let "n1 = $n - 1" # n-1
33 let "n2 = $n - 2" # n-2
34
35 t0=`expr $n - ${Q[n1]}` # n - Q[n-1]
36 t1=`expr $n - ${Q[n2]}` # n - Q[n-2]
37
38 T0=${Q[t0]} # Q[n - Q[n-1]]
39 T1=${Q[t1]} # Q[n - Q[n-2]]
40
41 Q[n]=`expr $T0 + $T1` # Q[n - Q[n-1]] + Q[n - Q[n-2]]
42 echo -n "${Q[n]} "
43
44 if [ `expr $n % $LINEWIDTH` -eq 0 ] # 格式化输出.
45 then # ^ 取模操作
46 echo # 把每行都拆为20个数字的小块.
47 fi
48
49 done
50
51 echo
52
53 exit 0
54
55 # 这是Q-series的一个迭代实现.
56 # 更直接明了的实现是使用递归, 请读者作为练习完成.
57 # 警告: 使用递归的方法来计算这个数列的话, 会花费非常长的时间. |
--
Bash仅仅支持一维数组, 但是我们可以使用一个小手段, 这样就可以模拟多维数组了.
例子 26-16. 模拟一个二维数组, 并使他倾斜
1 #!/bin/bash
2 # twodim.sh: 模拟一个二维数组.
3
4 # 一维数组由单行组成.
5 # 二维数组由连续的多行组成.
6
7 Rows=5
8 Columns=5
9 # 5 X 5 的数组.
10
11 declare -a alpha # char alpha [Rows] [Columns];
12 # 没必要声明. 为什么?
13
14 load_alpha ()
15 {
16 local rc=0
17 local index
18
19 for i in A B C D E F G H I J K L M N O P Q R S T U V W X Y
20 do # 你可以随你的心意, 使用任意符号.
21 local row=`expr $rc / $Columns`
22 local column=`expr $rc % $Rows`
23 let "index = $row * $Rows + $column"
24 alpha[$index]=$i
25 # alpha[$row][$column]
26 let "rc += 1"
27 done
28
29 # 更简单的方法:
30 #+ declare -a alpha=( A B C D E F G H I J K L M N O P Q R S T U V W X Y )
31 #+ 但是如果写的话, 就缺乏二维数组的"风味"了.
32 }
33
34 print_alpha ()
35 {
36 local row=0
37 local index
38
39 echo
40
41 while [ "$row" -lt "$Rows" ] # 以"行序为主"进行打印:
42 do #+ 行号不变(外层循环),
43 #+ 列号进行增长. (译者注: 就是按行打印)
44 local column=0
45
46 echo -n " " # 按照行方向打印"正方形"数组.
47
48 while [ "$column" -lt "$Columns" ]
49 do
50 let "index = $row * $Rows + $column"
51 echo -n "${alpha[index]} " # alpha[$row][$column]
52 let "column += 1"
53 done
54
55 let "row += 1"
56 echo
57
58 done
59
60 # 更简单的等价写法为:
61 # echo ${alpha[*]} | xargs -n $Columns
62
63 echo
64 }
65
66 filter () # 过滤掉负的数组下标.
67 {
68
69 echo -n " " # 产生倾斜.
70 # 解释一下, 这是怎么做到的.
71
72 if [[ "$1" -ge 0 && "$1" -lt "$Rows" && "$2" -ge 0 && "$2" -lt "$Columns" ]]
73 then
74 let "index = $1 * $Rows + $2"
75 # 现在, 按照旋转方向进行打印.
76 echo -n " ${alpha[index]}"
77 # alpha[$row][$column]
78 fi
79
80 }
81
82
83
84
85 rotate () # 将数组旋转45度 --
86 { #+ 从左下角进行"平衡".
87 local row
88 local column
89
90 for (( row = Rows; row > -Rows; row-- ))
91 do # 反向步进数组, 为什么?
92
93 for (( column = 0; column < Columns; column++ ))
94 do
95
96 if [ "$row" -ge 0 ]
97 then
98 let "t1 = $column - $row"
99 let "t2 = $column"
100 else
101 let "t1 = $column"
102 let "t2 = $column + $row"
103 fi
104
105 filter $t1 $t2 # 将负的数组下标过滤出来.
106 # 如果你不做这一步, 将会怎样?
107 done
108
109 echo; echo
110
111 done
112
113 # 数组旋转的灵感来源于Herbert Mayer所著的
114 #+ "Advanced C Programming on the IBM PC"的例子(第143-146页)
115 #+ (参见参考书目).
116 # 由此可见, C语言能够做到的好多事情,
117 #+ 用shell脚本一样能够做到.
118
119 }
120
121
122 #--------------- 现在, 让我们开始吧. ------------#
123 load_alpha # 加载数组.
124 print_alpha # 打印数组.
125 rotate # 逆时钟旋转45度打印.
126 #-----------------------------------------------------#
127
128 exit 0
129
130 # 这是有点做作, 不是那么优雅.
131
132 # 练习:
133 # -----
134 # 1) 重新实现数组加载和打印函数,
135 # 让其更直观, 可读性更强.
136 #
137 # 2) 详细地描述旋转函数的原理.
138 # 提示: 思考一下倒序索引数组的实现.
139 #
140 # 3) 重写这个脚本, 扩展它, 让不仅仅能够支持非正方形的数组.
141 # 比如6 X 4的数组.
142 # 尝试一下, 在数组旋转时, 做到最小"失真". |
二维数组本质上其实就是一个一维数组, 只不过是添加了行和列的寻址方式, 来引用和操作数组的元素而已.
这里有一个精心制作的模拟二维数组的例子, 请参考例子 A-10.
--
还有两个使用脚本的更有趣的例子, 请参考: