在Bash中从一个大于32767的数组中随机选择一个元素

4

具有:

mapfile -t words < <( head -10000 /usr/share/dict/words)
echo "${#words[@]}" #10000
r=$(( $RANDOM % ${#words[@]} ))
echo "$r ${words[$r]}"

这段代码从包含1万个单词的数组中随机选择一个单词。

但是,如果数组更大(例如,整个文件有超过200k个单词),它会停止工作,因为$RANDOM只能到达32767。来自man bash的信息如下:

每次引用该参数时,都会生成0到32767之间的随机整数。

mapfile -t words < /usr/share/dict/words
echo "${#words[@]}" # 235886
r=$(( $RANDOM % ${#words[@]} )) #how to change this?
echo "$r ${words[$r]}"

不想使用像perl -plE 's/.*/int(rand()*$_)/e'这样的Perl代码,因为并非每个系统都安装了Perl。寻找最简单的解决方案-也不关心真正的随机性-这不是用于加密技术。:)

3个回答

1

一个可能的解决方案是对$RANDOM的结果进行一些数学运算:

big_random=`expr $RANDOM \* 32767 + $RANDOM`

另一种方法是使用$RANDOM一次来选择输入文件的一个块,然后再次使用$RANDOM来从该块中选择一行。
请注意,$RANDOM不允许您指定范围。%会给出非均匀结果。更多讨论请参见:如何在Bash中生成随机数? 此外,将整个words读入内存似乎并不明智。除非您将频繁访问此数据结构,否则请考虑尝试在不一次性读取整个文件的情况下完成此操作。

你认为使用类似 sed -n "${num}p" file 更好吗?比如运行一个读取文件的外部程序?mapfile是内置的,我可以简单地清除数组...还是我漏掉了什么? - cajwine
我会使用 sed,就像你提到的那样。mapfile 将至少消耗与文件大小相同的进程内存(尽管是临时的)。sed 会一次消耗一行的内存。你是否接受这种方式取决于你自己。 - slim
如果你对运行外部程序感到困扰,你就不会写Bash代码了,对吧? - slim
没问题,这是我第一次需要运行wc -l来获取单词数量,然后第二次需要运行sed,因此我使用了mapfile。不过,我会做一些测试并看看效果。感谢您的建议。 :) - cajwine
为什么要调用expr来进行数学计算,当shell已经完全能够胜任呢:“big_random=$((32768*RANDOM+RANDOM))”。是的,这个乘数应该是32768(而不是32767)。或者,如果你想避免乘法,可以使用更快的位移方式:"big_random=$(((RANDOM<<15)+RANDOM))"。 - user8017719

1
如果您的系统上有 shuf ...
r=$(shuf -i 0-${#words[@]} -n 1)

如果没有的话,您可以使用$RANDOM多次,并连接结果以获得足够覆盖您需求的位数。您应该连接而不是相加,因为相加随机数不会产生均匀分布(就像投掷两个随机骰子比投掷总和为1更常见产生总和为7一样)。
例如:
printf -v r1 %05d $RANDOM
printf -v r2 %05d $RANDOM
printf -v r3 %05d $RANDOM
r4=${r1:1}${r2:1}${r3:1}
r=$(( $r4 % ${#words[@]} ))

"

printf语句用于确保保留前导零;-v选项是一个隐藏的宝石,允许将变量赋值给该值(这可以在许多有用的实际情况下避免使用eval)。r1r2r3中的每个第一个数字都被剥离,因为它只能是0、1、2或3。

"

可以在Linux上工作,但是shuf在MacOSX上默认情况下不可用。 :( - cajwine

0

被接受的答案将为您提供十个数字,但对于每个五位数前缀,最后五位数字只能在00000-32767范围内。

例如,数字1234567890不是可能的选择,因为67890 > 32767

这也许没关系。个人认为这个选项更好一些。它给你的数字是0-1073676289,没有间隙。

big_random=$(expr $RANDOM \* $RANDOM)

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