27 数组

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

    我们可以看出,初始化整数的一个简单的方法是 array=( element1 element2 … elementN ) 。

    1. # 使用扩展的一对范围 Using extended brace expansion
    2. #+ 去初始化数组的元素。to initialize the elements of the array.
    3. # 从 vladz's "base64.sh" 脚本中摘录过来。
    4. #+ 在"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 " %s\n" "${Line[index]}"
    19. done
    20. for index in 1 2 # 出处为2行。
    21. do
    22. printf " %s\n" "${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 命令可以删除数组元素,甚至能够删除整个数组。

    正如我们在前面的例子中所看到的,${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. # 当你还在认为你身在Kansas州时...
    106. array10=( ${array0[@]#$zap} )
    107. echo
    108. echo "Elements in array10: ${array10[@]}"
    109. # 但是,如果被引用的话,*号将不会被解释。
    110. array10=( ${array0[@]#"$zap"} )
    111. echo
    112. echo "Elements in array10: ${array10[@]}"
    113. # 可能,我们仍然在Kansas...
    114. # (上面的代码块Nathan Coulter所修改.)
    115. # 比较 array7 和array10.
    116. # 比较array8 和array9.
    117. # 重申: 所有所谓弱引用的东西
    118. # Nathan Coulter 这样解释:
    119. # word在${parameter#word}中的匹配格式在
    120. #+ 参数扩展之后和引用移除之前已经完成了。
    121. # 在通常情况下,格式匹配在引用移除之后完成。
    122. 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

    extra 在数组声明的时候添加一个额外的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. # "嵌套" 数组.
    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. # 间接引用.
    23. # 这使得$TEST1
    24. # 让我们看看还能干点什么.
    25. echo
    26. echo "\$TEST1 = $TEST1" # 仅仅是变量名字.
    27. echo; echo
    28. echo "{\$TEST1} = ${!TEST1}" # 变量内容.
    29. # 这就是
    30. #+ 间接引用的作用.
    31. echo
    32. echo "-------------------------------------------"; echo
    33. echo
    34. # 打印变量
    35. echo "Variable VARIABLE: $VARIABLE"
    36. # 打印一个字符串元素
    37. IFS="$OLD_IFS"
    38. TEST2="STRING[*]"
    39. local ${!TEST2} # 间接引用(同上).
    40. echo "String element VAR2: $VAR2 from STRING"
    41. # Print an array element
    42. TEST2="ARRAY21[*]"
    43. local ${!TEST2} # 间接引用(同上).
    44. echo "Array element VAR1_1: $VAR1_1 from ARRAY21"
    45. }
    46. print
    47. echo
    48. exit 0
    49. # 脚本作者注,
    50. #+ "你可以很容易的将其扩展成一个能创建hash 的Bash 脚本."
    51. # (难) 留给读者的练习: 实现它.

    数组使得埃拉托色尼素数筛子有了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 $?

    上边的这个例子是基于数组的素数产生器, 还有不使用数组的素数产生器 和例子 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. # 警告: 使用递归的方法来计算这个数列的话, 会花费非常长的时间.

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

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

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


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