如何在Bash中生成随机数?

471

如何在Bash中生成指定范围内的随机数?


36
需要多随机才够? - bdonlan
26个回答

558
使用$RANDOM。它通常与简单的shell算术结合使用非常有用。例如,要生成一个介于1和10之间(包括1和10)的随机数:
$ echo $((1 + $RANDOM % 10))
3

实际的生成器在variables.c中,函数brand()旧版本是一个简单的线性生成器。版本4.0的bash使用一个带有引用的生成器一篇1988年的论文,这可能意味着它是一个不错的伪随机数源。我不会将其用于模拟(当然也不会用于加密),但对于基本的脚本任务来说,它可能是足够的。
如果你正在做一些需要严格随机数的事情,可以使用/dev/random/dev/urandom(如果可用):
$ dd if=/dev/urandom count=4 bs=1 | od -t d

48
注意,这里要小心。虽然在紧急情况下这样做是可以的,但对随机数进行算术运算可能会严重影响结果的随机性。例如,在"$RANDOM % 10"的情况下,即使"$RANDOM"是一个可靠的随机数据源,8和9的概率也明显(尽管轻微地)低于0-7。 - dimo414
9
@dimo414,我对“marginally”很感兴趣,你有没有一些可以让我了解更多的来源? - PascalVKooten
119
通过对您的随机输入进行模,您正在“将鸽子放进笼子里”限制结果。由于 $RANDOM$ 的范围是 0-32767,数字 0-7 映射到 3277 种不同的可能输入,但数字 8 和 9 只能通过 3276 种不同的方式生成(因为 32768 和 32769 是不可能的)。这对于快速 hack 来说是一个小问题,但意味着结果不是均匀分布的。像 Java 的 Random 这样的随机库提供了函数,可以正确地返回给定范围内的均匀随机数,而不仅仅是取非可被整除的数的模。 - dimo414
45
仅供参考,% 10 的基本分组意味着数字8和9比0-7出现的可能性少约0.03%。如果您的shell脚本需要比这更准确的均匀随机数,则可以使用更复杂和正确的机制。 - Nelson
11
(())中,你可以省略变量的前缀$,像这样 $((1 + RANDOM % 10)) - LianSheng
显示剩余9条评论

124
请参考$RANDOM

$RANDOM 是一个Bash内部函数(不是常量),返回0-32767范围内的伪随机整数。不应用于生成加密密钥。


3
32767有特殊含义吗? - Jin Kwon
35
@JinKwon 32767 是有符号 16 位整数的上限,其值为 2^16 / 2 - 1 - Jeffrey Martinez
5
@JinKwon,你能否解释一下为什么你不说它是“2的15次方减1”吗?虽然这两种方式是等价的,但我很好奇是否有我不了解的上下文信息。 - Brett Holman
23
我会尽力进行翻译,不过请注意我的语言水平可能无法完全达到您的期望。根据您提供的内容,我理解为对“signed 16 bit integer”中的“signed”部分进行了强调,意为16位有符号整数,共包含2^16个值,其中一半为正数,另一半为负数。 - cody
3
那么范围不应该是-32768到32767吗? - sidney
显示剩余2条评论

119

你也可以使用 shuf (在coreutils中可用)。

shuf -i 1-100000 -n 1

1
$var添加到范围末尾,像这样:var=100 && shuf -i 1-${var} -n 1 - knipwim
2
我更喜欢这个选项,因为使用“-n”可以轻松生成N个随机数。例如:生成1到100之间的5个随机数shuf -i 1-100 -n 5 - aerijman
6
据我所理解,这些数字并不是随机的。如果你指定 shuf -i 1-10 -n 10,你将会得到所有从1到10的数字正好一次。如果你指定 -n 15,你仍然只会得到那十个数字且每个数字只有一次。这只是简单的洗牌,而非生成随机数。 - radlan
3
获取带重复的随机数:-r - Geoffrey Anderson
5
默认情况下,shuf 不使用密码学安全的伪随机数生成器,但可以使用 /dev/urandom--random-source 选项)与 shuf 一起使用。相关链接:https://unix.stackexchange.com/a/705633/133353(*默认情况下,这些命令使用内部的伪随机发生器进行初始化*...) - Artfaith
显示剩余2条评论

53

尝试在您的Shell中执行此操作:

$ od -A n -t d -N 1 /dev/urandom

这里,-t d指定输出格式应为有符号十进制;-N 1表示从/dev/urandom读取一个字节。

4
问题要求在一个范围内提供数字。 - JSycamore
14
你可以移除空格:od -A n -t d -N 1 /dev/urandom |tr -d ' ' - Robert
虽然这是一个很好的随机数源,但应该强调一点,即有符号的1(或更多)字节小数存在一个警告,就像$RANDOM一样,它们不以10的倍数结束,如果使用模运算可能会导致偏差。因此,使用1个字节最多可以达到255,因此使用%10会使某些数字具有比其他数字更高的概率。2个字节、3个字节等也是如此。 - Pier A

33

我喜欢这个技巧:


echo ${RANDOM:0:1} # random number between 1 and 9
echo ${RANDOM:0:2} # random number between 1 and 99

...


20
$RANDOM 的取值范围是 0 到 32767。以数字 1、2 或 3 开头的随机数会比以数字 4-9 开头的多。如果您可以接受分布不平衡,那么这个方法可以正常工作。 - jbo5112
2
@jbo5112 你说得完全正确,那么显示最后一位数字怎么样?使用 ${RANDOM:0-1} 显示一位数字,${RANDOM:0-2} 显示两位数字……? - fraff
8
如果你使用最后一位数字,这样做会相当不错,但会包括0和00。在一个数字中,0-7出现的频率比8-9多0.03%。在两个数字中,0-67出现的频率比68-99多0.3%。如果你需要一个这么好的随机数分布,希望你不要使用bash。${RANDOM:0:1}有67.8%的几率给你一个1或2,${RANDOM:0:2}只有0.03%的几率给你一个单个数字(应该是1%),两者都有0.003%的几率给你一个0。仍然有使用情况可以接受(例如不一致输入)。 - jbo5112
这应该很好:echo $((10#${RANDOM: -1}${RANDOM: -1})) 它每次使用最后一位数字,并且去掉前导零。如果需要更多位数,只需添加一个额外的 ${RANDOM: -1} - undefined

31

你也可以从awk中获取随机数

awk 'BEGIN {
   # seed
   srand()
   for (i=1;i<=1000;i++){
     print int(1 + rand() * 100)
   }
}'

1
+1 你知道吗,一开始我觉得为什么要这样做呢,但实际上我很喜欢它。 - zelanix
2
谢谢您提供包含种子的解决方案。我在其他地方找不到它! - so.very.tired
5
支持随机种子 +1。值得一提的是,srand() 函数的种子值是当前CPU时间。如果需要指定特定的种子值,以便能够复制随机数生成器 (RNG) 的结果,请使用 srand(x),其中 x 是种子值。此外,引用自GNU awk数学函数手册,“不同的awk实现在内部使用不同的随机数生成器。” 结果是,如果你希望生成统计分布,应该预期在不同平台上运行awkgawk时,在一个运行时和下一个运行时之间会有轻微的变化。 - Cbhihe
1
小心:srand()使用当前CPU时间的秒数作为种子,因此如果您在同一秒内运行此脚本多次,则脚本每次都会打印相同的值。如果您想要更安全,那么只需将 srand()替换为 srand('$(date +%s%N)') ,以使用当前纳秒级别的时间(写作时,macOS不支持该函数,但Linux应该可以)。 - Brandon

25

这里是 $RANDOM。我不确切知道它是如何工作的,但它可以使用。对于测试,您可以执行:

echo $RANDOM

我们如何在1-10范围内获取随机数? - ghost21blade
2
@ghost21blade $(( ($RANDOM % 10) + 1 )) - sherrellbc

22

bash 5.1引入了一个新变量,SRANDOM,它从系统熵引擎获取其随机数据,因此不是线性的,也不能重新播种以获得相同的随机序列。这个变量可以用作RANDOM的替代品,用于生成更多的随机数。

$ echo $((1 + SRANDOM % 10))
4

16

0到9之间(包括0和9)的随机数。

echo $((RANDOM%10))

2
我的错,没有仔细阅读手册。$RANDOM只能生成从0到32767之间的随机数。它应该说“大多数情况下生成1到3之间的随机数,偶尔会有一些异常值” ;) - David Newcomb
4
什么?虽然8和9的概率略低于0到7,但它仍将在0到9之间。这已经在另一个回答中提到过了。 - kini

9

2
我添加了一个小调整来去掉前导零:RANDOM=$(date +%s%N | cut -b10-19 | sed -e 's/^0*//;s/^$/0/') - JCCyC
1
有趣的想法!但是为什么要删除空格并与 0 交换呢?也就是说,这本来不应该发生吧? - Roel Van de Paar
1
@JCCyC 一个更好的选择是使用Bash的参数扩展来去除前导零,而不是使用sed(一个单独的程序):${RANDOM#0} - legends2k
1
@legends2k 这样做是行不通的,因为问题出在RANDOM=...的赋值上。我已经修复了答案,使用sed命令加入了一个0修复。 - Roel Van de Paar
2
@Roel 你是对的,我忽视了我们必须给RANDOM赋值,而不需要使用前导零来初始化随机数生成器。使用另一个变量也是一种选择:在线示例;虽然多了一行代码,但不需要额外的进程。感谢你的答案和链接 :) - legends2k
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接