魅力博客

魅力Linux|魅力空间|魅力博客|学习Linux|ubuntu日记|电脑教程|手机软件

深入学习shell脚本艺术-高级Bash脚本编程指南12



Example 25-2 用"与列表"的另一个命令行参数测试
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 ARGS=1        # 期望的参数个数.
 4 E_BADARGS=65  # 如果用户给出不正确的参数个数的退出码.
 5
 6 test $# -ne $ARGS && echo "Usage: `basename $0` $ARGS argument(s)" && exit $E_BADARGS
 7 #  如果 条件1 测试为真(表示传给脚本的参数不对),
 8 #+ 则余下的命令会被执行,并且脚本结束运行.
 9
10 # 下面的代码只有当上面的测试失败时才会执行.
11 echo "Correct number of arguments passed to this script."
12
13 exit 0
14
15 # 为了检查退出码,脚本结束后用"echo $?"来查看退出码.
################################End Script#########################################

    当然,一个与列表也能给变量设置默认值.

       1 arg1=$@       # 不管怎样,设置变量$arg1为命令行参数.
       2
       3 [ -z "$arg1" ] && arg1=DEFAULT
       4               # 如果没有在命令行上指定参数则把$arg1设置为DEFAULT.

或列表(or list)

       1 command-1 || command-2 || command-3 || ... command-n

    只要前一个命令返回假命令链就会依次执行下去. 一旦有一个命令返回真, 命令链就会结
    束(第一个返回真的命令将会是最后一个执行的命令). 这显然和"与列表"正好相反.

Example 25-3 "或列表"和"与列表"的结合使用
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 #  delete.sh, 不是很聪明的文件删除功能.
 4 #  用法: delete filename
 5
 6 E_BADARGS=65
 7
 8 if [ -z "$1" ]
 9 then
10   echo "Usage: `basename $0` filename"
11   exit $E_BADARGS  # 没有参数? 跳出脚本.
12 else  
13   file=$1          # 设置文件名.
14 fi  
15
16
17 [ ! -f "$file" ] && echo "File \"$file\" not found. \
18 Cowardly refusing to delete a nonexistent file."
19 # 与列表, 用于文件不存在时给出一个错误信息.
20 # 注意 echo 命令的参数用了一个转义符继续使第二行也是这个命令的参数.
21
22 [ ! -f "$file" ] || (rm -f $file; echo "File \"$file\" deleted.")
23 # 或列表, 用于存在文件时删除此文件.
24
25 # 注意上面两个相反的逻辑.
26 # 与列表为真时才执行, 或列表为假时执行.
27
28 exit 0
################################End Script#########################################
    注意:    如果在与列表的第一个命令返回真时,它会执行.

   1 # ==> 下面的片断摘自Miquel van Smoorenburg写的 /etc/rc.d/init.d/single 脚本
   2 #+==> 示例与和或列表的使用.
   3 # ==> "箭头"的注释由本书作者添加.
   4
   5 [ -x /usr/bin/clear ] && /usr/bin/clear
   6   # ==> 如果 /usr/bin/clear 存在, 则调用它.
   7   # ==> 在调用一个命令前检查它是否存在,
   8   #+==> 以避免产生错误信息和其他难读懂的结果.
   9
  10   # ==> . . .
  11
  12 # 如果他们想在单用户模式下运行某些程序, 可能也会运行这个...
  13 for i in /etc/rc1.d/S[0-9][0-9]* ; do
  14         # 检查脚本是否可执行.
  15         [ -x "$i" ] || continue
  16   # ==> 如果在目录$PWD中相应的文件没有发现,
  17   #+==> 则会跳过此次循环.
  18
  19         # 不接受备份文件和由rpm产生的文件.
  20         case "$1" in
  21                 *.rpmsave|*.rpmorig|*.rpmnew|*~|*.orig)
  22                         continue;;
  23         esac
  24         [ "$i" = "/etc/rc1.d/S00single" ] && continue
  25   # ==> 设置脚本名,但还不执行它.
  26         $i start
  27 done
  28
  29   # ==> . . .

注意:    与列表或是或列表的退出状态是最后一个执行命令的退出状态.

灵活地组合"与"和"或"列表是允许的,但这样逻辑会很容易变得费解并且需要较多的测试.

   1 false && true || echo false         # false
   2
   3 # 结果等同
   4 ( false && true ) || echo false     # false
   5 # 但不同与
   6 false && ( true || echo false )     # (没有输出)
   7
   8 #  注意是从左到右来分组并求值的,
   9 #+ 因为逻辑操作符"&&"和"||"有相同的优先处理权.
  10
  11 #  最好避免这种复杂,除非你确实知道你在做什么.
  12
  13 #  Thanks, S.C.

参考例子 A-7和例子 7-4 演示的使用与/或列表测试变量的例子.


第26章    数组
============
较新的Bash版本支持一维数组. 数组元素可以用符号variable[xx]来初始化. 另外,脚本可以
用declare -a variable语句来清楚地指定一个数组. 要访问一个数组元素,可以使用花括号
来访问,即${variable[xx]}.

Example 26-1 简单的数组用法
################################Start Script#######################################
 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
################################End Script#########################################
注意:    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
          11 # Thank you, Michael Zick, for pointing this out.

        类似的示范请参考Bash variables are untyped.

Example 26-2 格式化一首诗
################################Start Script#######################################
 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 # 修改这个脚本使其从一个文本文件中提取内容打印一首行.
################################End Script#########################################
数组元素有它们独有的语法, 并且甚至Bash命令和操作符有特殊的选项可以支持数组使用.

Example 26-3 多种数组操作
################################Start Script#######################################
 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
################################End Script#########################################

大部分标准的字符串操作符 可以用于数组操作.

Example 26-4 用于数组的字符串操作符
################################Start Script#######################################
  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
################################End Script#########################################

命令替换能创建数组的新的单个元素.

Example 26-5 将脚本的内容传给数组
################################Start Script#######################################
 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 #
################################End Script#########################################
在数组的环境里, 一些 Bash 内建的命令 含义有一些轻微的改变. 例如, unset 会删除数组
元素, 或甚至删除整个数组.

Example 26-6 一些数组专用的工具
################################Start Script#######################################
 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
################################End Script#########################################

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

Example 26-7 关于空数组和空数组元素
################################Start Script#######################################
  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
################################End Script#########################################

${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)使将一个文本内容加载进数组成为可能.

   1 #!/bin/bash
   2
   3 filename=sample_file
   4
   5 #            cat sample_file
   6 #
   7 #            1 a b c
   8 #            2 d e fg
   9
  10
  11 declare -a array1
  12
  13 array1=( `cat "$filename"`)                #  加载$filename文件的内容进数组array1.
  14 #         打印文件到标准输出               #
  15 #
  16 #  array1=( `cat "$filename" | tr '\n' ' '`)
  17 #                            把文件里的换行变为空格.
  18 #  这是没必要的,因为Bash做单词分割时会把换行变为空格.
  19 #
  20
  21 echo ${array1[@]}            # 打印数组.
  22 #                              1 a b c 2 d e fg
  23 #
  24 #  文件中每个由空白符分隔开的“词”都被存在数组的一个元素里
  25 #
  26
  27 element_count=${#array1[*]}
  28 echo $element_count          # 8

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

Example 26-8 初始化数组
################################Start Script#######################################
 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
################################End Script#########################################

注意:    对变量增加 declare -a  语句声明可以加速后面的数组操作速度.

Example 26-9 复制和连接数组
################################Start Script#######################################
 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
################################End Script#########################################

Example 26-10 关于连接数组的更多信息
################################Start Script#######################################
  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
################################End Script#########################################
--
数组允许在脚本中实现一些常见的熟悉算法.这是否是必要的好想法在此不讨论,留给读者自
行判断.

Example 26-11 一位老朋友: 冒泡排序
################################Start Script#######################################
 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
################################End Script#########################################
--
在数组内嵌一个数组有可能做到吗?

   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) 的组合使用产生了一些有趣的用法.

Example 26-12 内嵌数组和间接引用
################################Start Script#######################################
 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 #   (难) 留给读者的练习: 实现它.
################################End Script#########################################

--
数组使埃拉托色尼素数筛子有了shell脚本的实现. 当然, 如果是追求效率的应用自然应该用
一种编译型的语言,例如用C. 这种脚本运行实在是太慢.

Example 26-13 复杂数组应用: 埃拉托色尼素数筛子
################################Start Script#######################################
  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
################################End Script#########################################
比较这个用数组的素数产生器和另一种不用数组的例子 A-16.

--

数组可以做一定程度的扩展,以模拟支持Bash原本不支持的数据结构.

Example 26-14 模拟下推的堆栈
################################Start Script#######################################
  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 #   + 写一个栈实现的四则运算计算器.
################################End Script#########################################

--

要想操作数组的下标需要中间变量. 如果确实要这么做, 可以考虑使用一种更强功能的编程语
言, 例如 Perl 或 C.

Example 26-15 复杂的数组应用: 列出一种怪异的数学序列
################################Start Script#######################################
 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 # 警告: 递归地计算这个序列会花很长的时间.
################################End Script#########################################

--

Bash 只支持一维数组,但有一些技巧可用来模拟多维数组.

Example 26-16 模拟二维数组,并使它倾斜
################################Start Script#######################################
  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 #     尝试旋转数组时做到最小"失真".
################################End Script#########################################
二维数组本质上等同于一维数组, 而只增加了使用行和列的位置来引用和操作元素的寻址模式.

关于二维数组更好的例子, 请参考例子 A-10.

--

另一个有趣的使用数组的脚本:

    * 例子 14-3

第27章    /dev 和 /proc
=====================
Linux 或 UNIX 机器都带有/dev和/proc目录用于特殊目的.


27.1. /dev
----------
在 /dev 目录内包含以或不以硬件形式出现的物理设备条目. [1] 包含被挂载的文件系统的硬
设备分区在/dev目录下都有对应的条目, 就像df命令所展示的.

 bash$ df
 Filesystem           1k-blocks      Used Available Use%
 Mounted on
 /dev/hda6               495876    222748    247527  48% /
 /dev/hda1                50755      3887     44248   9% /boot
 /dev/hda8               367013     13262    334803   4% /home
 /dev/hda5              1714416   1123624    503704  70% /usr

在其他方面, /dev 目录也包含环回设备(loopback devices) , 例如/dev/loop0. 环回设备是
一个使普通文件能被像对待块设备一样来进行存取的机制. [2] 这使我们可以将一个大文件内
的整个文件系统挂载到系统目录下. 参考例子 13-8和例子 13-7.

/dev还有少量的伪设备用于特殊的用途, 例如/dev/null, /dev/zero, /dev/urandom,
/dev/sda1, /dev/udp, 和/dev/tcp.

例如:
为了挂载(mount) 一个USB闪盘设备, 将下面一行添加到/etc/fstab. [3]

   1 /dev/sda1    /mnt/flashdrive    auto    noauto,user,noatime    0 0

(也请参考例子 A-23.)

当对/dev/tcp/$host/$port 伪设备文件执行一个命令时, Bash会打开一个相关的TCP的socket.
[4]

从nist.gov得到时间:

 bash$ cat </dev/tcp/time.nist.gov/13
 53082 04-03-18 04:26:54 68 0 0 502.3 UTC(NIST) *
           
[Mark贡献了上面的例子.]

下载一个 URL:

 bash$ exec 5<>/dev/tcp/www.net.cn/80
 bash$ echo -e "GET / HTTP/1.0\n" >&5
 bash$ cat <&5
           
[Thanks, Mark 和 Mihai Maties.]

Example 27-1 利用/dev/tcp 来检修故障
################################Start Script#######################################
 1 #!/bin/bash
 2 # dev-tcp.sh: 用/dev/tcp 重定向来检查Internet连接.
 3
 4 # Troy Engel编写.
 5 # 已得到作者允许.
 6  
 7 TCP_HOST=www.dns-diy.com   # 一个已知的 ISP.
 8 TCP_PORT=80                # http的端口是80 .
 9   
10 # 尝试连接. (有些像 'ping' . . .)
11 echo "HEAD / HTTP/1.0" >/dev/tcp/${TCP_HOST}/${TCP_PORT}
12 MYEXIT=$?
13
14 : <<EXPLANATION
15 If bash was compiled with --enable-net-redirections, it has the capability of
16 using a special character device for both TCP and UDP redirections. These
17 redirections are used identically as STDIN/STDOUT/STDERR. The device entries
18 are 30,36 for /dev/tcp:
19
20   mknod /dev/tcp c 30 36
21
22 >From the bash reference:
23 /dev/tcp/host/port
24     If host is a valid hostname or Internet address, and port is an integer
25 port number or service name, Bash attempts to open a TCP connection to the
26 corresponding socket.
27 EXPLANATION
28
29    
30 if [ "X$MYEXIT" = "X0" ]; then
31   echo "Connection successful. Exit code: $MYEXIT"
32 else
33   echo "Connection unsuccessful. Exit code: $MYEXIT"
34 fi
35
36 exit $MYEXIT
################################End Script#########################################

译者补充上面这个例子输出的解释(EXPLANATION)译文:

如果bash以--enable-net- redirections选项来编译,它就拥有了使用一个特殊字符设备来完
成TCP和UDP重定向功能的能力.这种重定向能力就像 STDIN/STDOUT/STDERR一样被标识.该字
符设备/dev/tcp的主次设备号是30,36:

mknod /dev/tcp c 30 36

>摘自bash参考手册:

/dev/tcp/host/port

如果host是一个有效的主机名或因特网有效地址,并且port是一个整数的端口号或是服务名称
,Bash会尝试打开一个相对应的TCP连接socket.

注意事项:
[1]        /dev目录中的条目是为各种物理设备和虚拟设备提供的挂载点. 这些条目使用非常少
        的设备空间.
        一些像/dev/null, /dev/zero, 和 /dev/urandom的设备是虚拟的. 它们不是真正的
        物理设备,而只是存在于软件的虚拟设备.
[2]        块设备读或写(或两者兼之)数据都是以块为单位的进行的, 与之相对应的字符设备
        则使用字符为单位来进行存取.块设备典型的有硬盘和CD-ROM设备,字符设备典型的
        例子如键盘.
[3]        当然,挂载点/mnt/flashdrive必须存在,如果不存在,以root用户来执行
        mkdir /mnt/flashdrive.  
        为了最终能挂载设备,用下面的命令: mount /mnt/flashdrive
        较新的Linux发行版自动把闪盘设备挂载到/media目录.
[4]        socket是一种特殊的用于通信的I/O端口. 它允许同一台主机内不同硬件设备间的数
        据传输,允许在相同网络中的主机间的数据传输,也允许穿越不同网络的主机间的数
        据传输,当然,也允许在Internet上不同位置主机间的数据传输.

27.2. /proc
-----------
/proc目录实际上是一个伪文件系统 . 在 /proc 目录里的文件是当前运行系统和内核进程及
它们的相关信息和统计.

 bash$ cat /proc/devices
 Character devices:
   1 mem
   2 pty
   3 ttyp
   4 ttyS
   5 cua
   7 vcs
  10 misc
  14 sound
  29 fb
  36 netlink
 128 ptm
 136 pts
 162 raw
 254 pcmcia

 Block devices:
   1 ramdisk
   2 fd
   3 ide0
   9 md
 
 
 bash$ cat /proc/interrupts
            CPU0       
   0:      84505          XT-PIC  timer
   1:       3375          XT-PIC  keyboard
   2:          0          XT-PIC  cascade
   5:          1          XT-PIC  soundblaster
   8:          1          XT-PIC  rtc
  12:       4231          XT-PIC  PS/2 Mouse
  14:     109373          XT-PIC  ide0
 NMI:          0
 ERR:          0
 
 
 bash$ cat /proc/partitions
 major minor  #blocks  name     rio rmerge rsect ruse wio wmerge wsect wuse running use aveq

    3     0    3007872 hda 4472 22260 114520 94240 3551 18703 50384 549710 0 111550 644030
    3     1      52416 hda1 27 395 844 960 4 2 14 180 0 800 1140
    3     2          1 hda2 0 0 0 0 0 0 0 0 0 0 0
    3     4     165280 hda4 10 0 20 210 0 0 0 0 0 210 210
    ...
 
 
 bash$ cat /proc/loadavg
 0.13 0.42 0.27 2/44 1119
 
 
 bash$ cat /proc/apm
 1.16 1.2 0x03 0x01 0xff 0x80 -1% -1 ?

Shell 脚本可以从/proc目录中的一些文件里提取数据. [1]

   1 FS=iso                       # ISO 文件系统是否被内核支持?
   2
   3 grep $FS /proc/filesystems   # iso9660

   1 kernel_version=$( awk '{ print $3 }' /proc/version )

   1 CPU=$( awk '/model name/ {print $4}' < /proc/cpuinfo )
   2
   3 if [ $CPU = Pentium ]
   4 then
   5   run_some_commands
   6   ...
   7 else
   8   run_different_commands
   9   ...
  10 fi

   1 devfile="/proc/bus/usb/devices"
   2 USB1="Spd=12"
   3 USB2="Spd=480"
   4
   5
   6 bus_speed=$(grep Spd $devfile | awk '{print $9}')
   7
   8 if [ "$bus_speed" = "$USB1" ]
   9 then
  10   echo "USB 1.1 port found."
  11   # 这儿开始操作USB 1.1相关的动作.
  12 fi

/proc目录下有许多不相同的数字命名的子目录. 这些子目录的数字名字都映射对应的当前正
在运行的进程的进程号(process ID) . 这些子目录里面有许多文件用于保存对应进程的信息.
文件 stat 和 status 保存着进程运行时的各项统计, the cmdline文件保存该进程的被调用
时的命令行参数, 而and the exe 文件是该运行进程完整路径名的符号链接. 还有其他一些文
件,但从脚本的观点来看它们都非常的有意思.

返回顶部

发表评论:

Powered By Z-BlogPHP 1.7.3


知识共享许可协议
本作品采用知识共享署名 3.0 中国大陆许可协议进行许可。
网站备案号粤ICP备15104741号-1