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