分类:
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是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, 但是这次产生随机整数.