#!/bin/bash
# random-between.sh
# 生成指定范围内的随机数。
# 本书作者在 Bill Gradwhol 所提供的脚本的基础上作了些细微修改。
# Anthony Le Clezio 修正了 187 行和 189 行。
# 本书被授权使用该脚本。
randomBetween() {
# 生成一个范围在 $min 和 $max 之间,
#+ 并且能被 $divisibleBy 整除的
#+ 随机正数或负数。
# 返回的随机数遵循合理的随机分布。
# Bill Gradwohl - Oct 1, 2003
syntax() {
# 嵌套函数。
echo
echo "Syntax: randomBetween [min] [max] [multiple]"
echo
echo -n "Expects up to 3 passed parameters, "
echo "but all are completely optional."
echo "min is the minimum value"
echo "max is the maximum value"
echo -n "multiple specifies that the answer must be "
echo "a multiple of this value."
echo " i.e. answer must be evenly divisible by this number."
echo
echo "If any value is missing, defaults area supplied as: 0 32767 1"
echo -n "Successful completion returns 0, "
echo "unsuccessful completion returns"
echo "function syntax and 1."
echo -n "The answer is returned in the global variable "
echo "randomBetweenAnswer"
echo -n "Negative values for any passed parameter are "
echo "handled correctly."
}
local min=${1:-0}
local max=${2:-32767}
local divisibleBy=${3:-1}
# 考虑到没有给函数传参的情况,给变量设置默认值。
local x
local spread
# 确保 divisibleBy 的值为正数。
[ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy))
# 合规校验。
if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o ${min} -eq ${max} ]; then
syntax
return 1
fi
# 检查 min 和 max 的值是否颠倒。
if [ ${min} -gt ${max} ]; then
# 交换它们。
x=${min}
min=${max}
max=${x}
fi
# 如果 min 值本身不能被 $divisibleBy 整除,
#+ 则将其修正到范围内。
if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ]; then
if [ ${min} -lt 0 ]; then
min=$((min/divisibleBy*divisibleBy))
else
min=$((((min/divisibleBy)+1)*divisibleBy))
fi
fi
# 如果 max 值本身不能被 $divisibleBy 整除,
#+ 则将其修正到范围内。
if [ $((max/divisibleBy*divisibleBy)) -ne ${max} ]; then
if [ ${max} -lt 0 ]; then
max=$((((max/divisibleBy)-1)*divisibleBy))
else
max=$((max/divisibleBy*divisibleBy))
fi
fi
# ---------------------------------------------------------------------
# 接下来开始真正的内容。
# 需要注意的是,为了得到端点间合理的随机分布,
#+ 随机数的取值范围应是 0 至 abs(max-min)+divisibleBy,
#+ 而不是简单的 abs(max-min)+1。
# 少量的增长将会带来端点间
#+ 合理的随机分布。
# 将公式修改为使用 abs(max-min)+1 仍然可以得到正确的答案,
#+ 但是获得的这些随机数的随机性是有缺陷的,
#+ 因为这种情况下返回的端点值 ($min 和 $max) 的次数远少于
#+ 使用正确公式时所返回的次数。
# ---------------------------------------------------------------------
spread=$((max-min))
# Omair Eshkenazi 指出在这里没有必要进行校验,
#+ 因为 max 和 min 的值已经被交换了。
[ ${spread} -lt 0 ] && spread=$((0-spread))
let spread+=divisibleBy
randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min))
return 0
# 但是 Paulo Marcel Coelho Aragao 指出
#+ 当 $max 和 $min 不能被 $divisibleBy 整除时,
#+ 该公式就会失效。
#
# 他建议替换为下面的公式:
# rnumber = $(((RANDOM%(max-min+1)+min)/divisibleBy*divisibleBy))
}
# 接下来测试函数。
min=-14
max=20
divisibleBy=3
# 循环执行足够多次数的函数,生成包含这些随机数的数组,
#+ 然后校验数组中是否包含了端点范围内的每一个数字。
declare -a answer
minimum=${min}
maximum=${max}
if [ $((minimum/divisibleBy*divisibleBy)) -ne ${minimum} ]; then
if [ ${minimum} -lt 0 ]; then
minimum=$((minimum/divisibleBy*divisibleBy))
else
minimum=$((((minimum/divisibleBy)+1)*divisibleBy))
fi
fi
# 如果 max 值本身不能被 $divisibleBy 整除,
#+ 则将其修正到范围内。
if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; then
if [ ${maximum} -lt 0 ]; then
maximum=$((((maximum/divisibleBy)-1)*divisibleBy))
else
maximum=$((maximum/divisibleBy*divisibleBy))
fi
fi
# 需要保证数组的下标只能为正数,
#+ 因此这里需要通过位移来保证
#+ 结果为正。
disp=$((0-minimum))
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
answer[i+disp]=0
done
# 现在开始循环执行函数以获得大量的随机数。
loopIt=1000 # 脚本的作者建议使用 100000,
#+ 但是这会花费大量的时间。
for ((i=0; i<${loopIt}; i++)); do
# 注意,我们在这里颠倒了 min 和 max 的值,
#+ 为的是校验函数在这种情况下是否能正常执行。
randomBetween ${max} ${min} ${divisibleBy}
# 如果获得了非预期的答案,则报错。
[ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] \
&& echo MIN or MAX error - ${randomBetweenAnswer}!
[ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] \
&& echo DIVISIBLE BY error - ${randomBetweenAnswer}!
# 保存统计结果。
answer[randomBetweenAnswer+disp]=$((answer[randomBetweenAnswer+disp]+1))
done
# 校验最终结果。
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
[ ${answer[i+disp]} -eq 0 ] \
&& echo "We never got an answer of $i." \
|| echo "${i} occurred ${answer[i+disp]} times."
done
exit 0