Chinaunix首页 | 论坛 | 博客
  • 博客访问: 512275
  • 博文数量: 119
  • 博客积分: 5054
  • 博客等级: 大校
  • 技术积分: 1305
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-03 13:13
文章分类

全部博文(119)

文章存档

2011年(4)

2010年(115)

我的朋友

分类:

2010-01-03 22:34:56

摘自《高级bash脚本编程指南》

变量的间接引用

假设一个变量的值是第二个变量的名字. 那么我们如何从第一个变量中取得第二个变量的值呢? 比如, 如果a=letter_of_alphabet并且letter_of_alphabet=z, 那么我们能够通过引用变量a来获得z么? 这确实是可以做到的, 它被称为间接引用. 它使用eval var1=\$$var2这种不平常的形式.

例子 1: 间接引用

  1 #!/bin/bash
  2 # ind-ref.sh: 间接变量引用.
  3 # 访问一个以另一个变量内容作为名字的变量的值.(译者注: 怎么译都不顺)
  4
  5 a=letter_of_alphabet   # 变量"a"的值是另一个变量的名字.
  6 letter_of_alphabet=z
  7
  8 echo
  9
 10 # 直接引用.
 11 echo "a = $a"          # a = letter_of_alphabet
 12
 13 # 间接引用.
 14 eval a=\$$a
 15 echo "Now a = $a"      # 现在 a = z
 16
 17 echo
 18
 19
 20 # 现在, 让我们试试修改第二个引用的值.
 21
 22 t=table_cell_3
 23 table_cell_3=24
 24 echo "\"table_cell_3\" = $table_cell_3"            # "table_cell_3" = 24
 25 echo -n "dereferenced \"t\" = "; eval echo \$$t    # 解引用 "t" = 24
 26 # 在这个简单的例子中, 下面的表达式也能正常工作么(为什么?).
 27 #         eval t=\$$t; echo "\"t\" = $t"
 28
 29 echo
 30
 31 t=table_cell_3
 32 NEW_VAL=387
 33 table_cell_3=$NEW_VAL
 34 echo "Changing value of \"table_cell_3\" to $NEW_VAL."
 35 echo "\"table_cell_3\" now $table_cell_3"
 36 echo -n "dereferenced \"t\" now "; eval echo \$$t
 37 # "eval" 带有两个参数 "echo" 和 "\$$t" (与$table_cell_3等价)
 38
 39 echo
 40
 41 # (感谢, Stephane Chazelas, 澄清了上边语句的行为.)
 42
 43
 44 # 另一个方法是使用${!t}符号, 见"Bash, 版本2"小节的讨论.
 45 # 也请参考 ex78.sh.
 46
 47 exit 0

变量的间接引用到底有什么应用价值? 它给Bash添加了一种类似于C语言指针的功能。Bash并不支持指针运算操作, 因此这极大的限制了间接引用的使用. 事实上, 在脚本语言中, 间接引用是一个蹩脚的东西.

在Bash版本2中引入的 ${!variable} 形式使得使用间接引用更加直观。

例子 2. 间接变量引用 – 新方法

  1 #!/bin/bash
  2
  3 # 间接变量引用.
  4 # 这种方法比较像C++中的引用特性.
  5
  6
  7 a=letter_of_alphabet
  8 letter_of_alphabet=z
  9
 10 echo "a = $a"           # 直接引用.
 11
 12 echo "Now a = ${!a}"    # 间接引用.
 13 # ${!variable}表示法比老式的"eval var1=\$$var2"表示法高级的多.
 14
 15 echo
 16
 17 t=table_cell_3
 18 table_cell_3=24
 19 echo "t = ${!t}"                      # t = 24
 20 table_cell_3=387
 21 echo "Value of t changed to ${!t}"    # 387
 22
 23 #  在引用数组成员或者引用表的时候, 这种方法非常有用,
 24 #+ 还可以用来模拟多维数组.
 25 #  如果有能够索引的选项(类似于指针的算术运算)
 26 #+ 就更好了. 可惜.
 27
 28 exit 0

$RANDOM: 产生随机整数

$RANDOM是Bash的内部函数 (并不是常量), 这个函数将返回一个伪随机(真正的”随机事件, “在它存在的范围内, 只发生在特定的几个未知的自然界现象中, 比如放射性衰变. 计算机只能产生模拟的随机事件, 并且计算机产生的”随机”数只能称为伪随机数.) 整数, 范围在0 – 32767之间. 它不应该被用来产生密匙.

例子 3. 产生随机整数

  1 #!/bin/bash
  2
  3 # 每次调用$RANDOM都会返回不同的随机整数.
  4 # 一般范围为: 0 - 32767 (有符号的16-bit整数).
  5
  6 MAXCOUNT=10
  7 count=1
  8
  9 echo
 10 echo "$MAXCOUNT random numbers:"
 11 echo "-----------------"
 12 while [ "$count" -le $MAXCOUNT ]      # 产生10 ($MAXCOUNT)个随机整数.
 13 do
 14   number=$RANDOM
 15   echo $number
 16   let "count += 1"  # 增加计数.
 17 done
 18 echo "-----------------"
 19
 20 # 如果你需要在特定范围内产生随机整数, 那么使用'modulo'(模)操作.(译者注: 事实上, 这不是一个非常好的办法. 理由见man 3 rand)
 21 # 取模操作会返回除法的余数.
 22
 23 RANGE=500
 24
 25 echo
 26
 27 number=$RANDOM
 28 let "number %= $RANGE"
 29 #           ^^
 30 echo "Random number less than $RANGE  ---  $number"
 31
 32 echo
 33
 34
 35
 36 #  如果你需要产生一个大于某个下限的随机整数.
 37 #+ 那么建立一个test循环来丢弃所有小于此下限值的整数. 
 38
 39 FLOOR=200
 40
 41 number=0   #初始化
 42 while [ "$number" -le $FLOOR ]
 43 do
 44   number=$RANDOM
 45 done
 46 echo "Random number greater than $FLOOR ---  $number"
 47 echo
 48
 49    # 让我们对上边的循环尝试一个小改动, 如下:
 50    #       let "number = $RANDOM + $FLOOR"
 51    # 这将不再需要那个while循环, 并且能够运行的更快.
 52    # 但是, 这可能会产生一个问题, 思考一下是什么问题?(溢出)
 53
 54
 55
 56 # 结合上边两个例子, 来在指定的上下限之间来产生随机数.
 57 number=0   #initialize
 58 while [ "$number" -le $FLOOR ]
 59 do
 60   number=$RANDOM
 61   let "number %= $RANGE"  # 让$number依比例落在$RANGE的范围内.
 62 done
 63 echo "Random number between $FLOOR and $RANGE ---  $number"
 64 echo
 65
 66
 67
 68 # 产生二元值, 就是, "true" 或 "false" 两个值.
 69 BINARY=2
 70 T=1
 71 number=$RANDOM
 72
 73 let "number %= $BINARY"
 74 #  注意 let "number >>= 14"    将会给出一个更好的随机分配. #(译者注: 正如man页中提到的, 更高位的随机分布更加平均)
 75 #+ (右移14位将把所有的位全部清空, 除了第15位, 因为有符号, 第16位是符号位). #取模操作使用低位来产生随机数会相对不平均)
 76 if [ "$number" -eq $T ]
 77 then
 78   echo "TRUE"
 79 else
 80   echo "FALSE"
 81 fi
 82
 83 echo
 84
 85
 86 # 抛骰子.
 87 SPOTS=6   # 模6给出的范围是0 - 5.
 88           # 加1会得到期望的范围1 - 6.
 89           # 感谢, Paulo Marcel Coelho Aragao, 对此进行的简化.
 90 die1=0
 91 die2=0
 92 # 是否让SPOTS=7会比加1更好呢? 解释行或者不行的原因?(不行,掷骰子不会有 0 的)
 93
 94 # 每次抛骰子, 都会给出均等的机会.
 95
 96     let "die1 = $RANDOM % $SPOTS +1" # 抛第一次.
 97     let "die2 = $RANDOM % $SPOTS +1" # 抛第二次.
 98     #  上边的算术操作中, 哪个具有更高的优先级呢 --
 99     #+ 模(%) 还是加法操作(+)?
100
101
102 let "throw = $die1 + $die2"
103 echo "Throw of the dice = $throw"
104 echo
105
106
107 exit 0

例子 4. 从一幅扑克牌中取出一张随机的牌

  1 #!/bin/bash
  2 # pick-card.sh
  3
  4 # 这是一个从数组中取出随机元素的一个例子.
  5
  6
  7 # 抽取一张牌, 任何一张.
  8
  9 Suites="Clubs
 10 Diamonds
 11 Hearts
 12 Spades"
 13
 14 Denominations="2
 15 3
 16 4
 17 5
 18 6
 19 7
 20 8
 21 9
 22 10
 23 Jack
 24 Queen
 25 King
 26 Ace"
 27
 28 # 注意变量的多行展开.
 29
 30
 31 suite=($Suites)                # 读入一个数组.
 32 denomination=($Denominations)
 33
 34 num_suites=${#suite[*]}        # 计算有多少个数组元素.
 35 num_denominations=${#denomination[*]}
 36
 37 echo -n "${denomination[$((RANDOM%num_denominations))]} of "
 38 echo ${suite[$((RANDOM%num_suites))]}
 39 # 双圆括号结构的算术扩展
 40
 41 # $bozo sh pick-cards.sh
 42 # Jack of Clubs
 43
 44
 45 # 感谢, "jipe," 指出$RANDOM的这个用法.
 46 exit 0

例子 5. 用随机数来摇单个骰子

  1 #!/bin/bash
  2 # RANDOM到底有多随机?
  3
  4 RANDOM=$$       # 使用脚本的进程ID来作为随机数的种子.注:RANDOM 与 $RANDOM 是不同的
  5
  6 PIPS=6          # 一个骰子有6个面.
  7 MAXTHROWS=600   # 如果你没别的事做, 可以增加这个数值.
  8 throw=0         # 抛骰子的次数.
  9
 10 ones=0          #  必须把所有的count都初始化为0,
 11 twos=0          #+ 因为未初始化的变量为null, 不是0.
 12 threes=0
 13 fours=0
 14 fives=0
 15 sixes=0
 16
 17 print_result ()
 18 {
 19 echo
 20 echo "ones =   $ones"
 21 echo "twos =   $twos"
 22 echo "threes = $threes"
 23 echo "fours =  $fours"
 24 echo "fives =  $fives"
 25 echo "sixes =  $sixes"
 26 echo
 27 }
 28
 29 update_count()
 30 {
 31 case "$1" in
 32   0) let "ones += 1";;   # 因为骰子没有"零", 所以给1.
 33   1) let "twos += 1";;   # 把这个设为2, 后边也一样.
 34   2) let "threes += 1";;
 35   3) let "fours += 1";;
 36   4) let "fives += 1";;
 37   5) let "sixes += 1";;
 38 esac
 39 }
 40
 41 echo
 42
 43
 44 while [ "$throw" -lt "$MAXTHROWS" ]
 45 do
 46   let "die1 = RANDOM % $PIPS"
 47   update_count $die1
 48   let "throw += 1"
 49 done
 50
 51 print_result
 52
 53 exit 0
 54
 55 #  如果RANDOM是真正的随机, 那么摇出来结果应该是平均的.
 56 #  把$MAXTHROWS设为600, 那么每个面应该是100, 上下的出入不应该超过20.
 57 #
 58 #  记住RANDOM毕竟是一个伪随机数,
 59 #+ 并且不是十分完美.
 60
 61 #  随机数的生成是一个十分深奥并复杂的问题.
 62 #  足够长的随机序列, 不但会展现其杂乱无章的一面,
 63 #+ 同样也会展现其机会均等的一面.
 64
 65 # 练习 (很简单):
 66 # --------------
 67 # 重写这个脚本, 做成抛1000次硬币的形式.
 68 # 分为"头"和"字"两面.

就像我们在上边的例子中所看到的, 最好在每次产生RANDOM的时候都使用新的种子. 因为如果使用同样种子的话, 那么RANDOM将会产生相同的序列. (计算机用来产生伪随机数的种子可以被看成是一种标识标签. 比如, 使用种子23所产生的随机序列就被称为序列 #23。一个伪随机序列的特点就是在这个序列开始重复之前的所有元素个数的总和, 也就是这个序列的长度. 一个好的伪随机产生算法可以产生一个非常长的不重复序列. ) (C语言中的random()函数也会有这样的行为.)

例子 6. 重新分配随机数种子

  1 #!/bin/bash
  2 # seeding-random.sh: 设置RANDOM变量作为种子.
  3
  4 MAXCOUNT=25       # 决定产生多少个随机数.
  5
  6 random_numbers ()
  7 {
  8 count=0
  9 while [ "$count" -lt "$MAXCOUNT" ]
 10 do
 11   number=$RANDOM
 12   echo -n "$number "
 13   let "count += 1"
 14 done
 15 }
 16
 17 echo; echo
 18
 19 RANDOM=1          # 为随机数的产生来设置RANDOM种子.
 20 random_numbers
 21
 22 echo; echo
 23
 24 RANDOM=1          # 设置同样的种子...
 25 random_numbers    # ...将会和上边产生的随机序列相同.
 26                   #
 27                   # 复制一个相同的"随机"序列在什么情况下有用呢?
 28
 29 echo; echo
 30
 31 RANDOM=2          # 在试一次, 但是这次使用不同的种子...
 32 random_numbers    # 这次将得到一个不同的随机序列.
 33
 34 echo; echo
 35
 36 # RANDOM=$$  使用脚本的进程ID来作为产生随机数的种子.
 37 # 从 'time' 或 'date' 命令中取得RANDOM作为种子也是常用的做法.
 38
 39 # 一个很有想象力的方法...
 40 SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
 41 #  首先从/dev/urandom(系统伪随机设备文件)中取出一行,
 42 #+ 然后将这个可打印行转换为8进制数, 使用"od"命令来转换.
 43 #+ 最后使用"awk"来获得一个数,
 44 #+ 这个数将作为产生随机数的种子.
 45 RANDOM=$SEED
 46 random_numbers
 47
 48 echo; echo
 49
 50 exit 0

/dev/urandom设备文件提供了一种比单独使用$RANDOM更好的, 能够产生更加”随机”的随机数的方法. dd if=/dev/urandom of=targetfile bs=1 count=XX 能够产生一个很分散的伪随机数序列. 然而, 如果想要将这个数赋值到一个脚本文件的变量中, 还需要可操作性, 比如使用 od 命令 (就像上边的例子), 或者使用 dd 命令, 或者通过管道传递到 md5sum 命令中.

注:od 命令——octal dump过滤器, 将会把输入(或文件)转换为8进制或者其他进制. 在你需要查看或处理一些二进制数据文件或者一个不可读的系统设备文件的时候, 这个命令非常有用, 比如/dev/urandom, 或者是一个二进制数据过滤器.

当然还有其他的产生伪随机数的方法. awk就能提供一个方便的方法来做到这点.

例子 9-30. 使用awk来产生伪随机数

  1 #!/bin/bash
  2 # random2.sh: 产生一个范围在 0 - 1 之间的伪随机数.
  3 # 使用了awk的rand()函数.
  4
  5 AWKSCRIPT=' { srand(); print rand() } '
  6 #            Command(s) / 传递到awk中的参数
  7 # 注意, srand()是awk中用来产生伪随机数种子的函数,rand()是产生伪随机数的函数。
  8
  9
 10 echo -n "Random number between 0 and 1 = "
 11
 12 echo | awk "$AWKSCRIPT"
 13 # 如果你省去'echo', 会怎样?
 14 #(如果省去 'echo' ,脚本执行时将等待一个输入来驱动 awk 执行。可用 CTRL-D 或 CTRL-C 终止程序。)
 15 exit 0
 16
 17
 18 # 练习:
 19 # -----
 20
 21 # 1) 使用循环结构, 打印出10个不同的随机数.
 22 #      (提示: 在每次循环过程中, 你必须使用"srand()"函数来生成不同的种子,
 23 #+     如果你不这么做会怎样?)
 24
 25 # 2) 使用整数乘法作为一个比例因子, 在10到100的范围之间,
 26 #+   来产生随机数.
 27
 28 # 3) 同上边的练习#2, 但是这次产生随机整数.

阅读(1450) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~