第五部分 进阶话题 - 27 数组

优质
小牛编辑
125浏览
2023-12-01

新版本的Bash支持一维数组。 数组元素可以使用符号variable[xx] 来初始化。另外,脚本可以使用declare -a variable语句来制定一个数组。 如果想引用一个数组元素(也就是取值),可以使用大括号,访问形式为 ${element[xx]} 。

例子 27-1. 简单的数组使用

  1. #!/bin/bash
  2. area[11]=23
  3. area[13]=37
  4. area[51]=UFOs
  5. # 数组成员不一定非得是相邻或连续的。
  6. # 数组的部分成员可以不被初始化。
  7. # 数组中允许空缺元素。
  8. # 实际上,保存着稀疏数据的数组(“稀疏数组”)
  9. #+ 在电子表格处理软件中是非常有用的。
  10. echo -n "area[11] = "
  11. echo ${area[11]} # 需要{大括号}。
  12. echo -n "area[13] = "
  13. echo ${area[13]}
  14. echo "Contents of area[51] are ${area[51]}."
  15. # 没被初始化的数组成员打印为空值(null变量)。
  16. echo -n "area[43] = "
  17. echo ${area[43]}
  18. echo "(area[43] unassigned)"
  19. echo
  20. # 两个数组元素的和被赋值给另一个数组元素。
  21. area[5]=`expr ${area[11]} + ${area[13]}`
  22. echo "area[5] = area[11] + area[13]"
  23. echo -n "area[5] = "
  24. echo ${area[5]}
  25. area[6]=`expr ${area[11]} + ${area[51]}`
  26. echo "area[6] = area[11] + area[51]"
  27. echo -n "area[6] = "
  28. echo ${area[6]}
  29. # 这里会失败,是因为不允许整数与字符串相加。
  30. echo; echo; echo
  31. # -----------------------------------------------------------------
  32. # 另一个数组, "area2".
  33. # 另一种给数组变量赋值的方法...
  34. # array_name=( XXX YYY ZZZ ... )
  35. area2=( zero one two three four )
  36. echo -n "area2[0] = "
  37. echo ${area2[0]}
  38. # 啊哈,从0开始计算数组下标(也就是,数组的第一个元素为[0],而不是[1]).
  39. echo -n "area2[1] = "
  40. echo ${area2[1]} # [1] 是数组的第二个元素。
  41. # -----------------------------------------------------------------
  42. echo; echo; echo
  43. # -----------------------------------------------
  44. # 第三个数组, "area3".
  45. # 另外一种给数组元素赋值的方法...
  46. # array_name=([xx]=XXX [yy]=YYY ...)
  47. area3=([17]=seventeen [24]=twenty-four)
  48. echo -n "area3[17] = "
  49. echo ${area3[17]}
  50. echo -n "area3[24] = "
  51. echo ${area3[24]}
  52. # -----------------------------------------------
  53. 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. # 就是这个字符串本身。
  10. # 感谢你, Michael Zick, 指出这一点.

类似的示范可以参考 Bash变量是无类型的 。

例子 27-2. 格式化一首诗

  1. #!/bin/bash
  2. # poem.sh: 将本书作者非常喜欢的一首诗,漂亮的打印出来。
  3. # 诗的行数 (单节).
  4. Line[1]="I do not know which to prefer,"
  5. Line[2]="The beauty of inflections"
  6. Line[3]="Or the beauty of innuendoes,"
  7. Line[4]="The blackbird whistling"
  8. Line[5]="Or just after."
  9. # 注意 引用允许嵌入的空格。
  10. # 出处.
  11. Attrib[1]=" Wallace Stevens"
  12. Attrib[2]=""Thirteen Ways of Looking at a Blackbird""
  13. # 这首诗已经是公共版权了 (版权已经过期了).
  14. echo
  15. tput bold # 粗体打印.
  16. for index in 1 2 3 4 5 # 5行.
  17. do
  18. printf " %sn" "${Line[index]}"
  19. done
  20. for index in 1 2 # 出处为2行。
  21. do
  22. printf " %sn" "${Attrib[index]}"
  23. done
  24. tput sgr0 # 重置终端。Reset terminal.
  25. # 查看 'tput' 文档.
  26. echo
  27. exit 0
  28. # 练习:
  29. # --------
  30. # 修改这个脚本,使其能够从一个文本数据文件中提取出一首诗的内容,然后将其漂亮的打印出来。

数组元素有它们独特的语法,甚至标准Bash命令和操作符,都有特殊的选项用以配合数组操作。

例子 27-3. 多种数组操作

  1. #!/bin/bash
  2. # array-ops.sh: 更多有趣的数组用法.
  3. array=( zero one two three four five )
  4. # 数组元素 0 1 2 3 4 5
  5. echo ${array[0]} # 0
  6. echo ${array:0} # 0
  7. # 第一个元素的参数扩展,
  8. #+ 从位置0(#0)开始(即第一个字符).
  9. echo ${array:1} # ero
  10. # 第一个元素的参数扩扎,
  11. #+ 从位置1(#1)开始(即第二个字符)。
  12. echo "--------------"
  13. echo ${#array[0]} # 4
  14. # 第一个数组元素的长度。
  15. echo ${#array} #4
  16. # 第一个数组元素的长度。
  17. # (另一种表示形式)
  18. echo ${#array[1]} # 3
  19. # 第二个数组元素的长度。
  20. # Bash中的数组是从0开始索引的。
  21. echo ${#array[*]} # 6
  22. # 数组中的元素个数。
  23. echo ${#array[@]} # 6
  24. # 数组中的元素个数.
  25. echo "--------------"
  26. array2=( [0]="first element" [1]="second element" [3]="fourth element" )
  27. # ^ ^ ^ ^ ^ ^ ^ ^ ^
  28. # 引用允许嵌入的空格,在每个单独的数组元素中。
  29. echo ${array2[0]} # 第一个元素
  30. echo ${array2[1]} # 第二个元素
  31. echo ${array2[2]} #
  32. # 因为并没有被初始化,所以此值为null。
  33. echo ${array2[3]} # 第四个元素.
  34. echo ${#array2[0]} # 13 (第一个元素的长度)
  35. echo ${#array2[*]} # 3 (数组中元素的个数)
  36. exit

大部分标准字符串操作 都可以用于数组中。

例子27-4. 用于数组的字符串操作

  1. #!/bin/bash
  2. # array-strops.sh: 用于数组的字符串操作。
  3. # 本脚本由Michael Zick 所编写.
  4. # 通过了授权在本书中使用。
  5. # 修复: 05 May 08, 04 Aug 08.
  6. # 一般来说,任何类似于 ${name ... }(这种形式)的字符串操作
  7. #+ 都能够应用于数组中的所有字符串元素,
  8. #+ 比如说${name[@] ... } 或者 ${name[*] ...} 这两种形式。
  9. arrayZ=( one two three four five five )
  10. echo
  11. # 提取尾部的子串。
  12. echo ${arrayZ[@]:0} # one two three four five five
  13. # ^ 所有元素
  14. echo ${arrayZ[@]:1} # two three four five five
  15. # ^ element[0]后边的所有元素.
  16. echo ${arrayZ[@]:1:2} # two three
  17. # ^ 只提取element[0]后边的两个元素.
  18. echo "---------"
  19. # 子串删除
  20. # 从字符串的开头删除最短的匹配。
  21. echo ${arrayZ[@]#f*r} # one two three five five
  22. # ^ # 匹配将应用于数组的所有元素。
  23. # 匹配到了"four",并且将它删除。
  24. # 从字符串的开头删除最长的匹配
  25. echo ${arrayZ[@]##t*e} # one two four five five
  26. # ^^ # 匹配将应用于数组的所有元素
  27. # 匹配到了 "three" ,并且将它删除。
  28. # 从字符串的结尾删除最短的匹配
  29. echo ${arrayZ[@]%h*e} # one two t four five five
  30. # ^ # 匹配将应用于数组的所有元素
  31. # 匹配到了 "hree" ,并且将它删除。
  32. # 从字符串的结尾删除最长的匹配
  33. echo ${arrayZ[@]%%t*e} # one two four five five
  34. # ^^ # 匹配将应用于数组的所有元素
  35. # 匹配到了 "three" ,并且将它删除。
  36. echo "----------------------"
  37. # 子串替换
  38. # 第一个匹配到的子串将会被替换。
  39. echo ${arrayZ[@]/fiv/XYZ} # one two three four XYZe XYZe
  40. # ^ # 匹配将应用于数组的所有元素
  41. # 所有匹配到的子串将会被替换。
  42. echo ${arrayZ[@]//iv/YY} # one two three four fYYe fYYe
  43. # 匹配将应用于数组的所有元素
  44. # 删除所有的匹配子串
  45. # 如果没有指定替换字符串的话,那就意味着'删除'...
  46. echo ${arrayZ[@]//fi/} # one two three four ve ve
  47. # ^^ # 匹配将应用于数组的所有元素
  48. # 替换字符串前端子串
  49. echo ${arrayZ[@]/#fi/XY} # one two three four XYve XYve
  50. # ^ # 匹配将应用于数组的所有元素
  51. # 替换字符串后端子串
  52. echo ${arrayZ[@]/%ve/ZZ} # one two three four fiZZ fiZZ
  53. # ^ # 匹配将应用于数组的所有元素
  54. echo ${arrayZ[@]/%o/XX} # one twXX three four five five
  55. # ^ # 为什么?
  56. echo "-----------------------------"
  57. replacement() {
  58. echo -n "!!!"
  59. }
  60. echo ${arrayZ[@]/%e/$(replacement)}
  61. # ^ ^^^^^^^^^^^^^^
  62. # on!!! two thre!!! four fiv!!! fiv!!!
  63. # replacement()的标准输出就是那个替代字符串.
  64. # Q.E.D: 替换动作实际上是一个‘赋值’。
  65. echo "------------------------------------"
  66. # 使用"for-each"之前:
  67. echo ${arrayZ[@]//*/$(replacement optional_arguments)}
  68. # ^^ ^^^^^^^^^^^^^
  69. # !!! !!! !!! !!! !!! !!!
  70. # 现在,如果Bash只将匹配到的字符串
  71. #+ 传递给被调用的函数...
  72. echo
  73. exit 0
  74. # 在将处理后的结果发送到大工具之前,比如-- Perl, Python, 或者其它工具
  75. # 回忆一下:
  76. # $( ... ) 是命令替换。
  77. # 一个函数作为子进程运行。
  78. # 一个函数将结果输出到stdout。
  79. # 赋值,结合"echo"和命令替换,
  80. #+ 可以读取函数的stdout.
  81. # 使用name[@]表示法指定了一个 "for-each"
  82. #+ 操作。
  83. # Bash比你想象的更加强力.

命令替换 可以构造数组的独立元素。

例子 27-5. 将脚本中的内容赋值给数组

  1. #!/bin/bash
  2. # script-array.sh: 将脚本中的内容赋值给数组。
  3. # 这个脚本的灵感来自于 Chris Martii 的邮件 (感谢!).
  4. script_contents=( $(cat "$0") ) # 将这个脚本的内容($0)
  5. #+ 赋值给数组
  6. for element in $(seq 0 $((${#script_contents[@]} - 1)))
  7. do # ${#script_contents[@]}
  8. #+ 表示数组元素的个数
  9. #
  10. # 问题:
  11. # 为什么必须使用seq 0 ?
  12. # 用seq 1来试一下.
  13. echo -n "${script_contents[$element]}"
  14. # 在同一行上显示脚本中每个域的内容。
  15. # echo -n "${script_contents[element]}" also works because of ${ ... }.
  16. echo -n " -- " # 使用 " -- " 作为域分隔符。
  17. done
  18. echo
  19. exit 0
  20. # 练习:
  21. # --------
  22. # 修改这个脚本,
  23. #+ 让这个脚本能够按照它原本的格式输出,
  24. #+ 连同空格,换行,等等。

在数组环境中,某些Bash 内建命令 的含义可能会有些轻微的改变。比如,unset 命令可以删除数组元素,甚至能够删除整个数组。

例子 27-6. 一些数组的专有特性

  1. #!/bin/bash
  2. declare -a colors
  3. # 脚本中所有的后续命令都会把
  4. #+ "colors" 当做数组
  5. echo "Enter your favorite colors (separated from each other by a space)."
  6. read -a colors # 至少需要键入3种颜色,以便于后边的演示。
  7. # 'read'命令的特殊选项 ,
  8. #+ 允许给数组元素赋值。
  9. echo
  10. element_count=${#colors[@]}
  11. # 提取数组元素个数的特殊语法
  12. # 用element_count=${#colors[*]} 也可以。
  13. #
  14. # "@" 变量允许在引用中存在单次分割,
  15. #+ (依靠空白字符来分割变量).
  16. #
  17. # 这就好像"$@" 和 "$*"
  18. #+ 在位置参数中所表现出来的行为一样。
  19. index=0
  20. while [ "$index" -lt "$element_count" ]
  21. do # 列出数组中的所有元素
  22. echo ${colors[$index]}
  23. # ${colors[index]} 也可以工作,因为它${ ... }之中.
  24. let "index = $index + 1"
  25. # Or:
  26. # ((index++))
  27. done
  28. # 每个数组元素被列为单独的一行
  29. # 如果没有这种要求的话,可以使用echo -n "${colors[$index]} "
  30. #
  31. # 也可以使用“for”循环来做:
  32. # for i in "${colors[@]}"
  33. # do
  34. # echo "$i"
  35. # done
  36. # (Thanks, S.C.)
  37. echo
  38. # 再次列出数组中的所有元素,不过这次的做法更为优雅。
  39. echo ${colors[@]} # echo ${colors[*]} 也可以工作.
  40. echo
  41. # "unset"命令既可以删除数组数据,也可以删除整个数组。
  42. unset colors[1] # 删除数组的第2个元素。
  43. # 作用等同于colors[1]=
  44. echo ${colors[@]} # 再次列出数组内容,第2个元素没了。
  45. unset colors # 删除整个数组。
  46. # unset colors[*] 以及
  47. #+ unset colors[@] 都可以.
  48. echo; echo -n "Colors gone."
  49. echo ${colors[@]} # 再次列出数组内容,内容为空。
  50. exit 0

正如我们在前面的例子中所看到的,${array_name[@]} 或者 ${array_name[*]} 都与数组中的所有元素相关。同样的,为了计算数组的元素个数,可以使用 ${array_name[@]} 或者 ${array_name[*]}${#array_name} 是数组第一个元素的长度,也就是 ${array_name[0]} 的长度(字符个数)。

例子 27-7. 空数组与包含空元素的数组

  1. #!/bin/bash
  2. # empty-array.sh
  3. # 感谢Stephane Chazelas制作这个例子的原始版本。
  4. #+ 同时感谢Michael Zick 和 Omair Eshkenazi 对这个例子所作的扩展。
  5. # 以及感谢Nathan Coulter 作的声明和感谢。
  6. # 空数组与包含有空元素的数组,这两个概念不同。
  7. array0=( first second third )
  8. array1=( '' ) # "array1" 包含一个空元素.
  9. array2=( ) # 没有元素. . . "array2"为空
  10. array3=() # 这个数组呢?
  11. echo
  12. ListArray()
  13. {
  14. echo
  15. echo "Elements in array0: ${array0[@]}"
  16. echo "Elements in array1: ${array1[@]}"
  17. echo "Elements in array2: ${array2[@]}"
  18. echo "Elements in array3: ${array3[@]}"
  19. echo
  20. echo "Length of first element in array0 = ${#array0}"
  21. echo "Length of first element in array1 = ${#array1}"
  22. echo "Length of first element in array2 = ${#array2}"
  23. echo "Length of first element in array3 = ${#array3}"
  24. echo
  25. echo "Number of elements in array0 = ${#array0[*]}" # 3
  26. echo "Number of elements in array1 = ${#array1[*]}" # 1 (Surprise!)
  27. echo "Number of elements in array2 = ${#array2[*]}" # 0
  28. echo "Number of elements in array3 = ${#array3[*]}" # 0
  29. }
  30. # ===================================================================
  31. ListArray
  32. # 尝试扩展这些数组。
  33. # 添加一个元素到这个数组。
  34. array0=( "${array0[@]}" "new1" )
  35. array1=( "${array1[@]}" "new1" )
  36. array2=( "${array2[@]}" "new1" )
  37. array3=( "${array3[@]}" "new1" )
  38. ListArray
  39. # 或者
  40. array0[${#array0[*]}]="new2"
  41. array1[${#array1[*]}]="new2"
  42. array2[${#array2[*]}]="new2"
  43. array3[${#array3[*]}]="new2"
  44. ListArray
  45. # 如果你按照上边的方法对数组进行扩展的话,数组比较像‘栈’
  46. # 上边的操作就是‘压栈’
  47. # ‘栈’的高度为:
  48. height=${#array2[@]}
  49. echo
  50. echo "Stack height for array2 = $height"
  51. # '出栈’就是:
  52. unset array2[${#array2[@]}-1] # 数组从0开始索引
  53. height=${#array2[@]} #+ 这就意味着数组的第一个下标是0
  54. echo
  55. echo "POP"
  56. echo "New stack height for array2 = $height"
  57. ListArray
  58. # 只列出数组array0的第二个和第三个元素。
  59. from=1 # 从0开始索引。
  60. to=2
  61. array3=( ${array0[@]:1:2} )
  62. echo
  63. echo "Elements in array3: ${array3[@]}"
  64. # 处理方式就像是字符串(字符数组)。
  65. # 试试其他的“字符串”形式。
  66. # 替换:
  67. array4=( ${array0[@]/second/2nd} )
  68. echo
  69. echo "Elements in array4: ${array4[@]}"
  70. # 替换掉所有匹配通配符的字符串
  71. array5=( ${array0[@]//new?/old} )
  72. echo
  73. echo "Elements in array5: ${array5[@]}"
  74. # 当你觉得对此有把握的时候...
  75. array6=( ${array0[@]#*new} )
  76. echo # This one might surprise you.
  77. echo "Elements in array6: ${array6[@]}"
  78. array7=( ${array0[@]#new1} )
  79. echo # 数组array6之后就没有惊奇了。
  80. echo "Elements in array7: ${array7[@]}"
  81. # 看起来非常像...
  82. array8=( ${array0[@]/new1/} )
  83. echo
  84. echo "Elements in array8: ${array8[@]}"
  85. # 所以,让我们怎么形容呢?
  86. # 对数组var[@]中的每个元素The string operations are performed on
  87. #+ 进行连续的字符串操作。each of the elements in var[@] in succession.
  88. # 因此:Bash支持支持字符串向量操作,
  89. # 如果结果是长度为0的字符串
  90. #+ 元素会在结果赋值中消失不见。
  91. # 然而,如果扩展在引用中,那个空元素会仍然存在。
  92. # Michael Zick: 问题--这些字符串是强引用还是弱引用?
  93. # Nathan Coulter: 没有像弱引用的东西
  94. #! 真正发生的事情是
  95. #!+ 匹配的格式发生在
  96. #!+ [word]的所有其它扩展之后
  97. #!+ 比如像${parameter#word}.
  98. zap='new*'
  99. array9=( ${array0[@]/$zap/} )
  100. echo
  101. echo "Number of elements in array9: ${#array9[@]}"
  102. array9=( "${array0[@]/$zap/}" )
  103. echo "Elements in array9: ${array9[@]}"
  104. # 此时,空元素仍然存在
  105. echo "Number of elements in array9: ${#array9[@]}"
  106. # 当你还在认为你身在Kansas州时...
  107. array10=( ${array0[@]#$zap} )
  108. echo
  109. echo "Elements in array10: ${array10[@]}"
  110. # 但是,如果被引用的话,*号将不会被解释。
  111. array10=( ${array0[@]#"$zap"} )
  112. echo
  113. echo "Elements in array10: ${array10[@]}"
  114. # 可能,我们仍然在Kansas...
  115. # (上面的代码块Nathan Coulter所修改.)
  116. # 比较 array7 和array10.
  117. # 比较array8 和array9.
  118. # 重申: 所有所谓弱引用的东西
  119. # Nathan Coulter 这样解释:
  120. # word在${parameter#word}中的匹配格式在
  121. #+ 参数扩展之后和引用移除之前已经完成了。
  122. # 在通常情况下,格式匹配在引用移除之后完成。
  123. 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
  7. declare -a array1
  8. array1=( `cat "$filename"`) # 将$filename的内容
  9. # 把文件内容展示到输出 #+ 加载到数组array1.
  10. #
  11. # array1=( `cat "$filename" | tr 'n' ' '`)
  12. # 把文件中的换行替换为空格
  13. # 其实这样做是没必要的,因为Bash在做单词分割的时候,
  14. #+将会把换行转换为空格。
  15. echo ${array1[@]} # 打印数组
  16. # 1 a b c 2 d e fg
  17. #
  18. # 文件中每个被空白符分割的“单词”
  19. #+ 都被保存到数组的一个元素中。
  20. element_count=${#array1[*]}
  21. echo $element_count # 8

出色的技巧使得数组的操作技术又多了一种。

例子 27-8. 初始化数组

  1. #! /bin/bash
  2. # array-assign.bash
  3. # 数组操作是Bash所特有的,
  4. #+ 所以才使用".bash" 作为脚本扩展名
  5. # Copyright (c) Michael S. Zick, 2003, All rights reserved.
  6. # License: Unrestricted reuse in any form, for any purpose.
  7. # Version: $ID$
  8. #
  9. # 说明与注释由 William Park所添加.
  10. # 基于 Stephane Chazelas所提供的例子
  11. #+ 它是在ABS中的较早版本。
  12. # 'times' 命令的输出格式:
  13. # User CPU <space> System CPU
  14. # User CPU of dead children <space> System CPU of dead children
  15. # Bash有两种方法,
  16. #+ 可以将一个数组的所有元素都赋值给一个新的数组变量。
  17. # 这两个方法都会丢弃数组中的“空引用“(null值)元素
  18. #+ 在2.04和以后的Bash版本中。
  19. # 另一种给数组赋值的方法将会被添加到新版本的Bash中,
  20. #+ 这种方法采用[subscript]=value 形式,来维护数组下标与元素值之间的关系。
  21. # 可以使用内部命令来构造一个大数组,
  22. #+ 当然,构造一个包含上千元素数组的其它方法
  23. #+ 也能很好的完成任务
  24. declare -a bigOne=( /dev/* ) # /dev下的所有文件 . . .
  25. echo
  26. echo 'Conditions: Unquoted, default IFS, All-Elements-Of'
  27. echo "Number of elements in array is ${#bigOne[@]}"
  28. # set -vx
  29. echo
  30. echo '- - testing: =( ${array[@]} ) - -'
  31. times
  32. declare -a bigTwo=( ${bigOne[@]} )
  33. # 注意括号: ^ ^
  34. times
  35. echo
  36. echo '- - testing: =${array[@]} - -'
  37. times
  38. declare -a bigThree=${bigOne[@]}
  39. # 这次没用括号。
  40. times
  41. # 通过比较,可以发现第二种格式的赋值更快一些,
  42. #+ 正如 Stephane Chazelas指出的那样
  43. #
  44. # William Park 解释:
  45. #+ bigTwo数组是作为一个单个字符串被赋值的(因为括号)
  46. #+ 而BigThree数组,则是一个元素一个元素进行的赋值。
  47. # 所以,实质上是:
  48. # bigTwo=( [0]="..." [1]="..." [2]="..." ... )
  49. # bigThree=( [0]="... ... ..." )
  50. #
  51. # 通过这样确认: echo ${bigTwo[0]}
  52. # echo ${bigThree[0]}
  53. # 在本书的例子中,我还是会继续使用第一种形式,
  54. #+ 因为,我认为这种形式更有利于将问题阐述清楚。
  55. # 在我所使用的例子中,在其中复用的部分,
  56. #+ 还是使用了第二种形式,那是因为这种形式更快。
  57. # MSZ: 很抱歉早先的疏忽。
  58. # 注意:
  59. # ----
  60. # 32和44的"declare -a" 语句其实不是必需的,
  61. #+ 因为Array=(...)形式
  62. #+ 只能用于数组
  63. # 然而,如果省略这些声明的话,
  64. #+ 会导致脚本后边的相关操作变慢。
  65. # 试试看,会发生什么.
  66. exit 0

在数组声明的时候添加一个额外的declare -a语句,能够加速后续的数组操作速度。

例子 27-9. 拷贝和连接数组

  1. #! /bin/bash
  2. # CopyArray.sh
  3. #
  4. # 这个脚本由Michael Zick所编写.
  5. # 这里已经通过作者的授权
  6. # 如何“通过名字传值&通过名字返回”
  7. #+ 或者“建立自己的赋值语句”。
  8. CpArray_Mac() {
  9. # 建立赋值命令
  10. echo -n 'eval '
  11. echo -n "$2" # 目的名字
  12. echo -n '=( ${'
  13. echo -n "$1" # 源名字
  14. echo -n '[@]} )'
  15. # 上边这些语句会构成一条命令。
  16. # 这仅仅是形式上的问题。
  17. }
  18. declare -f CopyArray
  19. CopyArray=CpArray_Mac
  20. Hype() {
  21. # "Pointer"函数
  22. # 状态产生器
  23. # 需要连接的数组名为$1.
  24. # (把这个数组与字符串"Really Rocks"结合起来,形成一个新数组.)
  25. # 并将结果从数组$2中返回.
  26. local -a TMP
  27. local -a hype=( Really Rocks )
  28. $($CopyArray $1 TMP)
  29. TMP=( ${TMP[@]} ${hype[@]} )
  30. $($CopyArray TMP $2)
  31. }
  32. declare -a before=( Advanced Bash Scripting )
  33. declare -a after
  34. echo "Array Before = ${before[@]}"
  35. Hype before after
  36. echo "Array After = ${after[@]}"
  37. # 连接的太多了?
  38. echo "What ${after[@]:3:2}?"
  39. declare -a modest=( ${after[@]:2:1} ${after[@]:3:2} )
  40. # ---- 子串提取 ----
  41. echo "Array Modest = ${modest[@]}"
  42. # 'before' 发生了什么变化 ?
  43. echo "Array Before = ${before[@]}"
  44. exit 0

例子27-10. 关于串联数组的更多信息

  1. #! /bin/bash
  2. # array-append.bash
  3. # Copyright (c) Michael S. Zick, 2003, All rights reserved.
  4. # License: Unrestricted reuse in any form, for any purpose.
  5. # Version: $ID$
  6. #
  7. # 在格式上,由M.C做了一些修改.
  8. # 数组操作是Bash特有的属性。
  9. # 传统的UNIX /bin/sh 缺乏类似的功能。
  10. # 将这个脚本的输出通过管道传递给'more',
  11. #+ 这样做的目的是放止输出的内容超过终端能够显示的范围,
  12. # 或者,重定向输出到文件中。
  13. declare -a array1=( zero1 one1 two1 )
  14. # 依次使用下标
  15. declare -a array2=( [0]=zero2 [2]=two2 [3]=three2 )
  16. # 数组中存在空缺的元素-- [1] 未定义
  17. echo
  18. echo '- Confirm that the array is really subscript sparse. -'
  19. echo "Number of elements: 4" # 为了演示,这里作了硬编码
  20. for (( i = 0 ; i < 4 ; i++ ))
  21. do
  22. echo "Element [$i]: ${array2[$i]}"
  23. done
  24. # 也可以参考一个更通用的例子, basics-reviewed.bash.
  25. declare -a dest
  26. # 将两个数组合并到第3个数组中。
  27. echo
  28. echo 'Conditions: Unquoted, default IFS, All-Elements-Of operator'
  29. echo '- Undefined elements not present, subscripts not maintained. -'
  30. # # 那些未定义的元素不会出现;组合时会丢弃这些元素。
  31. dest=( ${array1[@]} ${array2[@]} )
  32. # dest=${array1[@]}${array2[@]} # 奇怪的结果,可能是个bug。
  33. # 现在,打印结果。
  34. echo
  35. echo '- - Testing Array Append - -'
  36. cnt=${#dest[@]}
  37. echo "Number of elements: $cnt"
  38. for (( i = 0 ; i < cnt ; i++ ))
  39. do
  40. echo "Element [$i]: ${dest[$i]}"
  41. done
  42. # 将数组赋值给一个数组中的元素(两次)
  43. dest[0]=${array1[@]}
  44. dest[1]=${array2[@]}
  45. # 打印结果
  46. echo
  47. echo '- - Testing modified array - -'
  48. cnt=${#dest[@]}
  49. echo "Number of elements: $cnt"
  50. for (( i = 0 ; i < cnt ; i++ ))
  51. do
  52. echo "Element [$i]: ${dest[$i]}"
  53. done
  54. # 检查第二个元素的修改状况.
  55. echo
  56. echo '- - Reassign and list second element - -'
  57. declare -a subArray=${dest[1]}
  58. cnt=${#subArray[@]}
  59. echo "Number of elements: $cnt"
  60. for (( i = 0 ; i < cnt ; i++ ))
  61. do
  62. echo "Element [$i]: ${subArray[$i]}"
  63. done
  64. # 如果你使用'=${ ... }'形式
  65. #+ 将一个数组赋值到另一个数组的一个元素中,
  66. #+ 那么这个数组的所有元素都会被转换为一个字符串,
  67. #+ 这个字符串中的每个数组元素都以空格进行分隔(其实是IFS的第一个字符).
  68. # 如果原来数组中的所有元素都不包含空白符 . . .
  69. # 如果原来的数组下标都是连续的 . . .
  70. # 那么我们就可以将原来的数组进行恢复.
  71. # 从修改过的第二个元素中, 将原来的数组恢复出来.
  72. echo
  73. echo '- - Listing restored element - -'
  74. declare -a subArray=( ${dest[1]} )
  75. 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
  81. echo '- - Do not depend on this behavior. - -'
  82. echo '- - This behavior is subject to change - -'
  83. echo '- - in versions of Bash newer than version 2.05b - -'
  84. # MSZ: 抱歉,之前混淆了一些要点。
  85. exit 0

有了数组, 我们就可以在脚本中实现一些比较熟悉的算法. 这么做, 到底是不是一个好主意, 我们在这里不做讨论, 还是留给读者决定吧.

例子 27-11. 冒泡排序

  1. #!/bin/bash
  2. # bubble.sh: 一种排序方式, 冒泡排序.
  3. # 回忆一下冒泡排序的算法. 我们在这里要实现它...
  4. # 依靠连续的比较数组元素进行排序,
  5. #+ 比较两个相邻元素, 如果顺序不对, 就交换这两个元素的位置.
  6. # 当第一轮比较结束之后, 最"重"的元素就会被移动到最底部.
  7. # 当第二轮比较结束之后, 第二"重"的元素就会被移动到次底部的位置.
  8. # 依此类推.
  9. # 这意味着每轮比较不需要比较之前已经"沉淀"好的数据.
  10. # 因此你会注意到后边的数据在打印的时候会快一些.
  11. exchange() {
  12. # 交换数组中的两个元素.
  13. local temp=${Countries[$1]} # 临时保存
  14. #+ 要交换的那个元素
  15. Countries[$1]=${Countries[$2]}
  16. Countries[$2]=$temp
  17. return
  18. }
  19. declare -a Countries # 声明数组,
  20. #+ 此处是可选的, 因为数组在下面被初始化
  21. # 我们是否可以使用转义符()
  22. #+ 来将数组元素的值放在不同的行上?
  23. # 可以.
  24. Countries=(Netherlands Ukraine Zaire Turkey Russia Yemen Syria
  25. Brazil Argentina Nicaragua Japan Mexico Venezuela Greece England
  26. Israel Peru Canada Oman Denmark Wales France Kenya
  27. Xanadu Qatar Liechtenstein Hungary)
  28. # "Xanadu" 虚拟出来的世外桃源.
  29. #+ Kubla Khan做了个愉快的决定
  30. clear # 开始之前的清屏动作
  31. echo "0: ${Countries[*]}" # 从索引0开始列出整个数组.
  32. number_of_elements=${#Countries[@]}
  33. let "comparisons = $number_of_elements - 1"
  34. count=1 # Pass number.
  35. while [ "$comparisons" -gt 0 ] # 开始外部循环
  36. do
  37. index=0 # 在每轮循环开始之前,重置索引。
  38. while [ "$index" -lt "$comparisons" ] # 开始内部循环。
  39. do
  40. if [ ${Countries[$index]} > ${Countries[`expr $index + 1`]} ]
  41. # 如果原来的排序次序不对...
  42. # 回想一下, 在单括号中,
  43. #+ >是ASCII码的比较操作符.
  44. # if [[ ${Countries[$index]} > ${Countries[`expr $index + 1`]} ]]
  45. #+ 这样也行.
  46. then
  47. exchange $index `expr $index + 1` # 交换
  48. fi
  49. let "index += 1" #或者, index+=1 在Bash 3.1之后的版本才能这么用.
  50. done # 内部循环结束
  51. # ----------------------------------------------------------------------
  52. # Paulo Marcel Coelho Aragao 建议我们可以使用更简单的for循环
  53. #
  54. # for (( last = $number_of_elements - 1 ; last > 0 ; last-- ))
  55. ## Fix by C.Y. Hunt ^ (Thanks!)
  56. # do
  57. # for (( i = 0 ; i < last ; i++ ))
  58. # do
  59. # [[ "${Countries[$i]}" > "${Countries[$((i+1))]}" ]]
  60. # && exchange $i $((i+1))
  61. # done
  62. # done
  63. # ----------------------------------------------------------------------
  64. let "comparisons -= 1" # 因为最"重"的元素到了底部,
  65. #+ 所以每轮我们可以少做一次比较。
  66. echo
  67. echo "$count: ${Countries[@]}" # 每轮结束后, 都打印一次数组.
  68. echo
  69. let "count += 1" # 增加传递计数.
  70. done # 外部循环结束
  71. # 至此, 全部完成.
  72. exit 0

我们可以在数组中嵌套数组么?

  1. #!/bin/bash
  2. # "嵌套" 数组.
  3. # Michael Zick 提供了这个用例。
  4. #+ William Park做了一些修正和说明.
  5. AnArray=( $(ls --inode --ignore-backups --almost-all
  6. --directory --full-time --color=none --time=status
  7. --sort=time -l ${PWD} ) ) # Commands and options.
  8. # 空格是有意义的 . . . 并且不要在上边用引号引用任何东西.
  9. SubArray=( ${AnArray[@]:11:1} ${AnArray[@]:6:5} )
  10. # 这个数组有六个元素:
  11. #+ SubArray=( [0]=${AnArray[11]} [1]=${AnArray[6]} [2]=${AnArray[7]}
  12. # [3]=${AnArray[8]} [4]=${AnArray[9]} [5]=${AnArray[10]} )
  13. #
  14. # Bash数组是字符串(char *)类型
  15. #+ 的(循环)链表
  16. # 因此, 这不是真正意义上的嵌套数组,
  17. #+ 只不过功能很相似而已.
  18. echo "Current directory and date of last status change:"
  19. echo "${SubArray[@]}"
  20. exit 0

如果将“嵌套数组”与间接引用 组合起来使用的话,将会产生一些非常有趣的用法。

例子 27-12. 嵌套数组与间接引用

  1. #!/bin/bash
  2. # embedded-arrays.sh
  3. # 嵌套数组和间接引用.
  4. # 本脚本由Dennis Leeuw 编写.
  5. # 经过授权, 在本书中使用.
  6. # 本书作者做了少许修改.
  7. ARRAY1=(
  8. VAR1_1=value11
  9. VAR1_2=value12
  10. VAR1_3=value13
  11. )
  12. ARRAY2=(
  13. VARIABLE="test"
  14. STRING="VAR1=value1 VAR2=value2 VAR3=value3"
  15. ARRAY21=${ARRAY1[*]}
  16. ) # 将ARRAY1嵌套到这个数组中.
  17. function print () {
  18. OLD_IFS="$IFS"
  19. IFS=$'n' # 这么做是为了每行
  20. #+ 只打印一个数组元素.
  21. TEST1="ARRAY2[*]"
  22. local ${!TEST1} # 删除这一行, 看看会发生什么?
  23. # 间接引用.
  24. # 这使得$TEST1
  25. #+ 只能够在函数内被访问.
  26. # 让我们看看还能干点什么.
  27. echo
  28. echo "$TEST1 = $TEST1" # 仅仅是变量名字.
  29. echo; echo
  30. echo "{$TEST1} = ${!TEST1}" # 变量内容.
  31. # 这就是
  32. #+ 间接引用的作用.
  33. echo
  34. echo "-------------------------------------------"; echo
  35. echo
  36. # 打印变量
  37. echo "Variable VARIABLE: $VARIABLE"
  38. # 打印一个字符串元素
  39. IFS="$OLD_IFS"
  40. TEST2="STRING[*]"
  41. local ${!TEST2} # 间接引用(同上).
  42. echo "String element VAR2: $VAR2 from STRING"
  43. # Print an array element
  44. TEST2="ARRAY21[*]"
  45. local ${!TEST2} # 间接引用(同上).
  46. echo "Array element VAR1_1: $VAR1_1 from ARRAY21"
  47. }
  48. print
  49. echo
  50. exit 0
  51. # 脚本作者注,
  52. #+ "你可以很容易的将其扩展成一个能创建hash 的Bash 脚本."
  53. # (难) 留给读者的练习: 实现它.

数组使得埃拉托色尼素数筛子有了shell版本的实现. 当然, 如果你需要的是追求效率的应用, 那么就 应该使用编译行语言来实现, 比如C语言. 因为脚本运行的太慢了.

例子 27-13. 埃拉托色尼素数筛子

  1. #!/bin/bash
  2. # sieve.sh (ex68.sh)
  3. # 埃拉托色尼素数筛子
  4. # 找素数的经典算法.
  5. # 在同等数值的范围内,
  6. #+ 这个脚本运行的速度比C版本慢的多.
  7. LOWER_LIMIT=1 # 从1开始.
  8. UPPER_LIMIT=1000 # 到1000.
  9. # (如果你时间很多的话 . . . 你可以将这个数值调的很高.)
  10. PRIME=1
  11. NON_PRIME=0
  12. let SPLIT=UPPER_LIMIT/2
  13. # 优化:
  14. # 只需要测试中间到最大的值,为什么?
  15. declare -a Primes
  16. # Primes[] 是个数组.
  17. initialize ()
  18. {
  19. # 初始化数组.
  20. i=$LOWER_LIMIT
  21. until [ "$i" -gt "$UPPER_LIMIT" ]
  22. do
  23. Primes[i]=$PRIME
  24. let "i += 1"
  25. done
  26. # 假定所有数组成员都是需要检查的(素数)
  27. #+ 直到检查完成.
  28. }
  29. print_primes ()
  30. {
  31. # 打印出所有数组Primes[]中被标记为素数的元素.
  32. i=$LOWER_LIMIT
  33. until [ "$i" -gt "$UPPER_LIMIT" ]
  34. do
  35. if [ "${Primes[i]}" -eq "$PRIME" ]
  36. then
  37. printf "%8d" $i
  38. # 每个数字打印前先打印8个空格, 在偶数列才打印.
  39. fi
  40. let "i += 1"
  41. done
  42. }
  43. sift () # 查出非素数.
  44. {
  45. let i=$LOWER_LIMIT+1
  46. # 我们从2开始.
  47. until [ "$i" -gt "$UPPER_LIMIT" ]
  48. do
  49. if [ "${Primes[i]}" -eq "$PRIME" ]
  50. # 不要处理已经过滤过的数字(被标识为非素数).
  51. then
  52. t=$i
  53. while [ "$t" -le "$UPPER_LIMIT" ]
  54. do
  55. let "t += $i "
  56. Primes[t]=$NON_PRIME
  57. # 标识为非素数.
  58. done
  59. fi
  60. let "i += 1"
  61. done
  62. }
  63. # ==============================================
  64. # main ()
  65. # 继续调用函数.
  66. initialize
  67. sift
  68. print_primes
  69. # 这里就是被称为结构化编程的东西.
  70. # ==============================================
  71. echo
  72. exit 0
  73. # -------------------------------------------------------- #
  74. # 因为前面的'exit'语句, 所以后边的代码不会运行
  75. # 下边的代码, 是由Stephane Chazelas 所编写的埃拉托色尼素数筛子的改进版本,
  76. #+ 这个版本可以运行的快一些.
  77. # 必须在命令行上指定参数(这个参数就是: 寻找素数的限制范围)
  78. UPPER_LIMIT=$1 # 来自于命令行.
  79. let SPLIT=UPPER_LIMIT/2 # 从中间值到最大值.
  80. Primes=( '' $(seq $UPPER_LIMIT) )
  81. i=1
  82. until (( ( i += 1 ) > SPLIT )) # 仅需要从中间值检查.
  83. do
  84. if [[ -n ${Primes[i]} ]]
  85. then
  86. t=$i
  87. until (( ( t += i ) > UPPER_LIMIT ))
  88. do
  89. Primes[t]=
  90. done
  91. fi
  92. done
  93. echo ${Primes[*]}
  94. exit $?

例子 27-14. 埃拉托色尼素数筛子,优化版

  1. #!/bin/bash
  2. # 优化过的埃拉托色尼素数筛子
  3. # 脚本由Jared Martin编写, ABS Guide 的作者作了少许修改.
  4. # 在ABS Guide 中经过了许可而使用(感谢!).
  5. # 基于Advanced Bash Scripting Guide中的脚本.
  6. # http://tldp.org/LDP/abs/html/arrays.html#PRIMES0 (ex68.sh).
  7. # http://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf (引用)
  8. # Check results against http://primes.utm.edu/lists/small/1000.txt
  9. # Necessary but not sufficient would be, e.g.,
  10. # (($(sieve 7919 | wc -w) == 1000)) && echo "7919 is the 1000th prime"
  11. UPPER_LIMIT=${1:?"Need an upper limit of primes to search."}
  12. Primes=( '' $(seq ${UPPER_LIMIT}) )
  13. typeset -i i t
  14. Primes[i=1]='' # 1不是素数
  15. until (( ( i += 1 ) > (${UPPER_LIMIT}/i) )) # 只需要ith-way 检查.
  16. do # 为什么?
  17. if ((${Primes[t=i*(i-1), i]}))
  18. # 很少见, 但是很有指导意义, 在下标中使用算术扩展。
  19. then
  20. until (( ( t += i ) > ${UPPER_LIMIT} ))
  21. do Primes[t]=; done
  22. fi
  23. done
  24. # echo ${Primes[*]}
  25. echo # 改回原来的脚本,为了漂亮的打印(80-col. 展示).
  26. printf "%8d" ${Primes[*]}
  27. echo; echo
  28. exit $?

上边的这个例子是基于数组的素数产生器, 还有不使用数组的素数产生器例子A-15例子 16-46,让我们来比较一番.


数组可以进行一定程度上的扩展, 这样就可以模拟一些Bash原本不支持的数据结构.

例子 27-15. 模拟一个压入栈

  1. #!/bin/bash
  2. # stack.sh: 模拟压入栈
  3. # 类似于CPU 栈, 压入栈依次保存数据项,
  4. #+ 但是取数据时, 却反序进行, 后进先出.
  5. BP=100 # 栈数组的基址指针.
  6. # 从元素100 开始.
  7. SP=$BP # 栈指针.
  8. # 将其初始化为栈"基址"(栈底)
  9. Data= # 当前栈的数据内容.
  10. # 必须定义为全局变量,
  11. #+ 因为函数所能够返回的整数存在范围限制.
  12. # 100 基址 <-- Base Pointer
  13. # 99 第一个数据元素
  14. # 98 第二个数据元素
  15. # ... 更多数据
  16. # 最后一个数据元素 <-- Stack pointer
  17. declare -a stack
  18. push() # 压栈
  19. {
  20. if [ -z "$1" ] # 没有可压入的数据项?
  21. then
  22. return
  23. fi
  24. let "SP -= 1" # 更新栈指针.
  25. stack[$SP]=$1
  26. return
  27. }
  28. pop() #从栈中弹出数据项.
  29. {
  30. Data= # 清空保存数据项的中间变量
  31. if [ "$SP" -eq "$BP" ] # 栈空?
  32. then
  33. return
  34. fi # 这使得SP不会超过100,
  35. #+ 例如, 这可以防止堆栈失控.
  36. Data=${stack[$SP]}
  37. let "SP += 1" # 更新栈指针
  38. return
  39. }
  40. status_report() # 打印当前状态
  41. {
  42. echo "-------------------------------------"
  43. echo "REPORT"
  44. echo "Stack Pointer = $SP"
  45. echo "Just popped ""$Data"" off the stack."
  46. echo "-------------------------------------"
  47. echo
  48. }
  49. # =======================================================
  50. # 现在, 来点乐子.
  51. echo
  52. # 看你是否能从空栈里弹出数据项来.
  53. pop
  54. status_report
  55. echo
  56. push garbage
  57. pop
  58. status_report # 压入Garbage, 弹出garbage.
  59. value1=23; push $value1
  60. value2=skidoo; push $value2
  61. value3=LAST; push $value3
  62. pop # LAST
  63. status_report
  64. pop # skidoo
  65. status_report
  66. pop # 23
  67. status_report # 后进,先出!
  68. # 注意: 栈指针在压栈时减,
  69. #+ 在弹出时加.
  70. echo
  71. exit 0
  72. # =======================================================
  73. #
  74. # 练习:
  75. #
  76. # 1) 修改"push()"函数,
  77. # + 使其调用一次就能够压入多个数据项。
  78. # 2) 修改"pop()"函数,
  79. # + 使其调用一次就能弹出多个数据项.
  80. # 3) 给那些有临界操作的函数添加出错检查.
  81. # 说明白一些, 就是让这些函数返回错误码,
  82. # + 返回的错误码依赖于操作是否成功完成,
  83. # + 如果没有成功完成, 那么就需要启动合适的处理动作.
  84. # 4) 以这个脚本为基础,
  85. # + 编写一个用栈实现的四则运算计算器.

如果想对数组”下标”做一些比较诡异的操作, 可能需要使用中间变量. 对于那些有这种需求的项目来说, 还是应该考虑使用功能更加强大的编程语言, 比如Perl或C。

例子 27-16. 复杂的数组应用: 探索一个神秘的数学序列

  1. !/bin/bash
  2. # Douglas Hofstadter 的声名狼藉的序列"Q-series":
  3. # Q(1) = Q(2) = 1
  4. # Q(n) = Q(n - Q(n-1)) + Q(n - Q(n-2)), 当 n>2时
  5. # 这是一个令人感到陌生的, 没有规律的"乱序"整数序列
  6. #+ 并且行为不可预测
  7. # 序列的头20项, 如下所示:
  8. # 1 1 2 3 3 4 5 5 6 6 6 8 8 8 10 9 10 11 11 12
  9. # 请参考相关书籍, Hofstadter的, "_Goedel, Escher, Bach: An Eternal Golden Braid_",
  10. #+ 第137页.
  11. LIMIT=100 # 需要计算的数列长度.
  12. LINEWIDTH=20 # 每行打印的个数.
  13. Q[1]=1 # 数列的头两项都为1.
  14. Q[2]=1
  15. echo
  16. echo "Q-series [$LIMIT terms]:"
  17. echo -n "${Q[1]} " # 输出数列头两项.
  18. echo -n "${Q[2]} "
  19. for ((n=3; n <= $LIMIT; n++)) # C风格的循环条件.
  20. do # Q[n] = Q[n - Q[n-1]] + Q[n - Q[n-2]] for n>2
  21. # 需要将表达式拆开, 分步计算,
  22. #+ 因为Bash 不能够很好的处理复杂数组的算术运算.
  23. let "n1 = $n - 1" # n-1
  24. let "n2 = $n - 2" # n-2
  25. t0=`expr $n - ${Q[n1]}` # n - Q[n-1]
  26. t1=`expr $n - ${Q[n2]}` # n - Q[n-2]
  27. T0=${Q[t0]} # Q[n - Q[n-1]]
  28. T1=${Q[t1]} # Q[n - Q[n-2]]
  29. Q[n]=`expr $T0 + $T1` # Q[n - Q[n-1]] + Q[n - Q[n-2]]
  30. echo -n "${Q[n]} "
  31. if [ `expr $n % $LINEWIDTH` -eq 0 ] # 格式化输出
  32. then # ^ 取模操作
  33. echo # 把每行都拆为20个数字的小块.
  34. fi
  35. done
  36. echo
  37. exit 0
  38. # 这是Q-series的一个迭代实现.
  39. # 更直接明了的实现是使用递归, 请读者作为练习完成.
  40. # 警告: 使用递归的方法来计算这个数列的话, 会花费非常长的时间.
  41. #+ C/C++ 将会计算的快一些。

Bash仅仅支持一维数组, 但是我们可以使用一个小手段, 这样就可以模拟多维数组了.

例子 27-17. 模拟一个二维数组,并使它倾斜

  1. #!/bin/bash
  2. # twodim.sh: 模拟一个二维数组.
  3. # 一维数组由单行组成.
  4. # 二维数组由连续的多行组成.
  5. Rows=5
  6. Columns=5
  7. # 5 X 5 的数组.
  8. declare -a alpha # char alpha [Rows] [Columns];
  9. # 没必要声明. 为什么?
  10. load_alpha ()
  11. {
  12. local rc=0
  13. local index
  14. 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
  15. do # 你可以随你的心意, 使用任意符号.
  16. local row=`expr $rc / $Columns`
  17. local column=`expr $rc % $Rows`
  18. let "index = $row * $Rows + $column"
  19. alpha[$index]=$i
  20. # alpha[$row][$column]
  21. let "rc += 1"
  22. done
  23. # 更简单的方法:
  24. #+ 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 )
  25. #+ 但是如果写的话, 就缺乏二维数组的"风味"了.
  26. }
  27. print_alpha ()
  28. {
  29. local row=0
  30. local index
  31. echo
  32. while [ "$row" -lt "$Rows" ] # 以"行序为主"进行打印:
  33. do #+ 行号不变(外层循环),
  34. #+ 列号进行增长.
  35. local column=0
  36. echo -n " " # 按照行方向打印"正方形"数组.
  37. while [ "$column" -lt "$Columns" ]
  38. do
  39. let "index = $row * $Rows + $column"
  40. echo -n "${alpha[index]} " # alpha[$row][$column]
  41. let "column += 1"
  42. done
  43. let "row += 1"
  44. echo
  45. done
  46. # 更简单的等价写法为:
  47. # echo ${alpha[*]} | xargs -n $Columns
  48. echo
  49. }
  50. filter () # 过滤掉负的数组下标.
  51. {
  52. echo -n " " # 产生倾斜.
  53. # 解释一下, 这是怎么做到的.
  54. if [[ "$1" -ge 0 && "$1" -lt "$Rows" && "$2" -ge 0 && "$2" -lt "$Columns" ]]
  55. then
  56. let "index = $1 * $Rows + $2"
  57. # 现在, 按照旋转方向进行打印.
  58. echo -n " ${alpha[index]}"
  59. # alpha[$row][$column]
  60. fi
  61. }
  62. rotate () # 将数组旋转45度 --
  63. { #+ 从左下角进行"平衡".
  64. local row
  65. local column
  66. for (( row = Rows; row > -Rows; row-- ))
  67. do # 反向步进数组, 为什么?
  68. for (( column = 0; column < Columns; column++ ))
  69. do
  70. if [ "$row" -ge 0 ]
  71. then
  72. let "t1 = $column - $row"
  73. let "t2 = $column"
  74. else
  75. let "t1 = $column"
  76. let "t2 = $column + $row"
  77. fi
  78. filter $t1 $t2 # 将负的数组下标过滤出来.
  79. # 如果你不做这一步, 将会怎样?
  80. done
  81. echo; echo
  82. done
  83. # 数组旋转的灵感来源于Herbert Mayer 所著的
  84. #+ "Advanced C Programming on the IBM PC"的例子(第143-146页)
  85. #+ (参见参考书目).
  86. # 由此可见, C语言能够做到的好多事情,
  87. #+ 用shell 脚本一样能够做到.
  88. }
  89. #--------------- 现在, 让我们开始吧. ------------#
  90. load_alpha # 加载数组
  91. print_alpha # 打印数组.
  92. rotate # 逆时钟旋转45度打印.
  93. #-----------------------------------------------------#
  94. exit 0
  95. # 这有点做作, 不是那么优雅.
  96. # 练习:
  97. # -----
  98. # 1) 重新实现数组加载和打印函数,
  99. # 让其更直观, 可读性更强.
  100. #
  101. # 2) 详细地描述旋转函数的原理.
  102. # 提示: 思考一下倒序索引数组的实现.
  103. #
  104. # 3) 重写这个脚本, 扩展它, 让不仅仅能够支持非正方形的数组.
  105. # 比如6 X 4的数组.
  106. # 尝试一下, 在数组旋转时, 做到最小"失真".

二维数组本质上其实就是一个一维数组, 只不过是添加了行和列的寻址方式, 来引用和操作数组的元素而已.

这里有一个精心制作的模拟二维数组的例子, 请参考例子 A-10.


还有更多使用数组的有趣的脚本,请参考: