ZMonster's Blog 巧者劳而智者忧,无能者无所求,饱食而遨游,泛若不系之舟

Shell 中的随机数生成方法

昨天写了个脚本,用 ImageMagick 生成单个字符的图片,并随机为其附加模糊、噪声、旋转、扭曲等效果,这个脚本生成的图片是准备用来测试 DNN 的。目前来看的话,用 ImageMagick 生成的图片并不完全理想 —— 不过还能凑合着用。总之,为了生成尽量多尽量丰富的样本,我希望各种效果的作用程度是随机的,而且应用哪些效果也是随机的。毫无疑问这里是需要使用随机数的了。

当然了,本文提到的“随机数”实质上都是伪随机数,毕竟计算机只能模拟这一过程。

最后我选择的是使用 shuf 这个工具,不过在解决这个问题的过程中也总结了不止一种办法,在这里分享一下。

$RANDOM

这里 讲, RANDOM 是 Bash 的一个内建函数(而不是常量),会返回一个在区间 [0, 32767] 内的整数。

echo $RANDOM
# 4230

使用这个方法时,可以通过设置相同的 seed 来得到相同的随机数,当然也可以反过来用不同的 seed 来产生不同的随机数。

RANDOM=10
echo $RANDOM
# 4230

设置相同的 seed 后,可以观察到连续使用 $RANDOM 生成的随机数序列都是一样的。实际情况中我们可能希望生成的“随机数”尽量的不同,我们可以将时间转换为数字来设置 seed:

function random_with_time{} {
    RANDOM=$(date +%s)
    echo $RANDOM
}

不过需要注意的是,由于这里 date 命令的输出是精确到秒,seed 的值在 1 秒内是不变的,产生的随机数值也会相同。而在不设置 seed 的情况下,RANDOM 的输出在 1 秒内是会变化的,这里只是为了说明而举个例子而已。

如果需要产生特定范围内的随机数,可以这么写:

# @args <beg> <end>
# return random integer in [<beg>, <end>)
function random_range() {
    local beg=$1
    local end=$2
    echo $((RANDOM % ($end - $beg) + $beg))
}

/dev/random

/dev/random 是 Linux 上的一个字符设备,里面会源源不断地产生随机数,只要进行读取就行了。一般来说,用 od 命令即可:

od -An -N2 -i /dev/random
# 13176

这里的 -N2 指定要读取的字节数, -i 则是指定输入。

若要产生特定范围内的随机数,则和使用 $RANDOM 的方法类似:

# @args <beg> <end>
# return random integer in [<beg>, <end>)
function random_range() {
    local beg=$1
    local end=$2
    echo $(($(od -An -N2 -i /dev/random) % ($end - $beg) + $beg))
}

seq + sort

sort 命令有一个 -R 选项,可以根据随机 hash 排序,那么我们就可以用 seq 命令先生成一个整数序列,然后用 sort 的 -R 选项处理取其中一行即可。

# @args <beg> <end>
# return random integer in [<beg>, <end>]
function random_range {
    local beg=$1
    local end=$2
    seq $beg $end | sort -R | head -n1
}

值得注意的是,使用这种方法时,要求的值域可以包含负数区域,而之前的两种方法则要进行不同的处理。

shuf

shuf 和 'sort -R' 的作用类似,用来根据输入生成随机序列:

# @args <beg> <end>
# return random integer in [<beg>, <end>]
function random_range {
    shuf -i $1-$2 -n1
}

在各种方法中,使用 shuf 命令是最简洁的,因此就选择了这种方法。