9.3 $RANDOM:生成随机数

任何试图通过确定性方法生成随机数的行为都是在犯罪。

—— 约翰·冯·诺伊曼

$RANDOM 是 Bash 中用来生成 0 至 32767 之间随机整数的一个内置 函数(而非常量)。其不应被用于生成密钥。

样例 9-11. 生成随机数

#!/bin/bash

# $RANDOM 每一次调用都会返回一个随机的不同的整数。
# 随机数的标称范围为 0 - 32767(16位有符号整型)。

MAXCOUNT=10
count=1

echo
echo "$MAXCOUNT random numbers:"
echo "-----------------"
while [ "$count" -le $MAXCOUNT ]      # 生成 10 ($MAXCOUNT) 个随机整数。
do
  number=$RANDOM
  echo $number
  let "count += 1"  # 增加计数。
done
echo "-----------------"

# 如果你需要一个小于指定上界的随机数,可以使用 'modulo' 操作符。
# 该操作符可以返回除法后的余数。

RANGE=500

echo

number=$RANDOM
let "number %= $RANGE"
#           ^^
echo "Random number less than $RANGE --- $number"

echo



#  如果你需要生成的随机数大于一个指定的下界,
#+ 可以增加一步判断,判别并丢弃所有小于下界的数。

FLOOR=200

number=0   # 初始化
while [ "$number" -le $FLOOR ]
do
  number=$RANDOM
done
echo "Random number greater than $FLOOR --- $number"
echo

   # 现在来看一种可以代替上面循环的更简单的方式,也就是
   #       let "number = $RANDOM + $FLOOR"
   # 该方式可以不使用 while 循环,效率更高。
   # 但是,该方法可能会产生一些问题,是什么呢?



# 通过结合上面的两种方法,可以获得一个特定范围内的随机数。
number=0   # 初始化
while [ "$number" -le $FLOOR ]
do
  number=$RANDOM
  let "number %= $RANGE"  # 将 $number 缩小至 $RANGE 的范围内。
done
echo "Random number between $FLOOR and $RANGE --- $number"
echo



# 生成二元选择值,即真(true)或假(false)。
BINARY=2
T=1
number=$RANDOM

let "number %= $BINARY"
#  如果使用    let "number >>= 14"    可以获得更优的随机分布
#+ (除了最低位,其余二进制位都右移)。
if [ "$number" -eq $T ]
then
  echo "TRUE"
else
  echo "FALSE"
fi

echo


# 扔一个骰子。
SPOTS=6   # 模 6 的余数范围为 0 - 5。
          # 然后加 1 就可以得到期望的范围 1 - 6。
          # 感谢 Paulo Marcel Coelho Aragao 简化了代码。
die1=0
die2=0
# 如果设置 SPOTS=7 就可以不用加 1 得到值。这是不是一种更好的方法,为什么?

# 为了保证公平,独立的投每一个骰子。

    let "die1 = $RANDOM % $SPOTS + 1" # 投第一个骰子。
    let "die2 = $RANDOM % $SPOTS + 1" # 投第二个骰子。
    #  哪一种运算符有更高的优先级,
    #+ 取余(%)还是加法(+)?


let "throw = $die1 + $die2"
echo "Throw of the dice = $throw"
echo


exit 0

样例 9-12. 从牌组中随机选牌

Example 9-13. 模拟布朗运动

Jipe 提供了一些生成指定范围内随机数的方法。

Bill Gradwohl 提出了一种改良后的仅适用于正数的公式。

Bill 在这还给出了一个生成指定范围内随机数的通用函数。

样例 9-14. 指定范围随机数

那么 $RANDOM 到底有多随机?最好的测试方法就是写一个脚本跟踪由 $RANDOM 生成的随机数的分布。接下来让我们多投几次由 $RANDOM 做的骰子...

样例 9-15. 用 RANDOM 投骰子

从上一个样例中我们可以发现,在每次调用 RANDOM 生成器时,最好利用重置生成器种子。在 RANDOM 生成器中使用相同的种子会生成相同序列的随机数。(与 C 语言中的 random() 函数的行为一致)

样例 9-16. 重置 RANDOM 种子

伪设备文件 /dev/urandom 提供了比 $RANDOM 变量更随机化的伪随机数。命令 dd if=/dev/urandom of=targetfile bs=1 count=XXX 将会创建一个包含均匀分布的伪随机数的文件。但是想要在脚本中将这些随机数赋值给变量需要做一些变通,比如使用命令 od 进行过滤(参照上面的样例以及 样例 16-14样例 A-36)或者使用管道导入命令 md5sum 中(参照 样例 36-16)。

当然也有其他在脚本中生成伪随机数的方法。比如 Awk 命令就提供了这样一种非常简易的方法。

样例 9-17. 使用 awk 命令生成伪随机数

同样,命令 date 可以用于 生成整型随机数序列

注记

真正的“随机性”,就其存在而言,只存在于一些类似放射性衰变这样还未被完全理解的自然现象中。计算机只能模拟这样的随机性,因此计算机生成的“随机数”序列被称作伪随机数。

计算机用于生成伪随机数的种子可以被视作一个标识标签。例如,你可以将用种子 23 生成的随机数序列视作第23号序列。

伪随机数序列的一个属性是该序列在开始重复之前的周期长度。一个好的伪随机数生成器能够生成周期非常长的序列。

Last updated

Was this helpful?