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