如何在Bash中从一个字符串/数组中随机选取一个字符?

8

我有一个示例,希望在一组字母中(例如var="abcdefghijklmnopqrstuvwxyz")随机生成一个字母。

那么我该怎么做呢?

var="abcdefghijklmnopqrstuvwxyz"

echo "${var}"

yield: 某个字母

我的目标是从我选择的一组随机字母中自动生成一个。无论它是在数组还是字符串中都没有关系。


数组和字符串是两种不同的数据类型。 - Charles Duffy
1
顺便问一下,你用的是哪个shell?你也标记了sh,但如果你需要sh兼容性,一些仅适用于bash的技巧可能不可用。 - Charles Duffy
1
Stack Overflow不是一个代码编写服务。请展示你的代码。由于Stack Overflow隐藏了关闭原因:寻求调试帮助的问题(“为什么这段代码不能工作?”)必须在问题本身中包括期望的行为、具体的问题或错误以及最短的代码,以便在问题本身中复制。没有明确问题陈述的问题对其他读者来说是没有用的。请参阅:如何创建一个最小,完整和可验证的示例. - jww
1
同样地,terminal标签仅适用于关于终端的问题 - 而这不是 - 而linux标签适用于特定于Linux的问题,这也不是。 - Charles Duffy
将标题更改以匹配原帖作者的要求。 - codeforester
显示剩余4条评论
3个回答

14
var="abcdefghijklmnopqrstuvwxyz"
echo "${var:$(( RANDOM % ${#var} )):1}" # pick a 1 char substring starting at a random position

这个方法的原理是:

  • ${var:START:LEN} 是一种参数扩展,它将$var的子字符串扩展出来
  • ${#var} 是一种参数扩展,它将字符串变量var的内容长度扩展出来
  • $(( )) 创建一个算术上下文,在该上下文中,非数字字符串被假定为变量名(因此可以使用RANDOM代替$RANDOM)。
  • $RANDOM,每次评估时,都会扩展为0到32767之间的随机整数。
  • $RANDOM % ${#var} 取该随机整数除以命名为var的字符串中字符数的余数;因此,它将介于0和(var的长度-1)之间,并且将“几乎”随机地分割(如果var的长度不能平均地分成32768,则某些字符将比其他字符略微具有更高的被选中的机会)。

因此,${var:$(( RANDOM % ${#var} )) : 1}每次评估时,都会选择字符串内的一个位置,并在其中扩展为单个字符跨度。


8
对于大多数实际情况,Charles Duffy 的解决方案是可行的。然而,如果您需要随机字符选择是均匀的,则在使用 RANDOM 时,故事变得稍微复杂(请参见下面的解释)。最好的方法是使用 shufshuf 生成给定范围的随机排列,并允许您像 shuf -i 0-25 -n1 那样选择第一个数字,因此您可以使用它。
var="abcdefghijklmnopqrstuvwxyz"
echo ${var:$(shuf -i 0-$((${#var}-1)) -n1):1}

这里的想法是通过使用模式扩展${var:m,n}来从字符串var中选择一个字母,其中你选取一个长度为n的子字符串,从位置m开始。长度设置为1,起始位置由命令shuf -i 0- $(({#var}-1)定义,该命令在0和${#var}-1之间洗牌范围,其中${#var}是变量var的字符串长度。
为什么不使用RANDOM: 随机变量RANDOM生成0到32767之间的伪随机数。这意味着如果你想生成0到n之间的随机数,则不能使用mod。问题在于前32768%n个数字将有更高的概率被选中。可以通过以下脚本轻松地看到这一点:
% for i in {0..32767}; do echo $((i%5)); done | sort -g | uniq -c
   6554 0
   6554 1
   6554 2
   6553 3  < smaller change to hit 3
   6553 4  < smaller chance to hit 4

另一种经典的方法是通过将随机数生成器的范围映射到请求的范围来缩放随机值,公式为 n*RANDOM/32768。但是,这仅适用于生成实数的随机数生成器。 RANDOM生成的是一个整数。整数缩放实际上只是将之前的问题混淆了:
% for i in {0..32767}; do echo $((5*i/32768)); done | sort -g | uniq -c
   6554 0
   6554 1
   6553 2  < smaller chance to hit 2
   6554 3
   6553 4  < smaller chance to hit 4

如果您要使用RANDOM,最好的方法是跳过不需要的值,这可以通过简单的while循环来实现。
var="abcdefghijklmnopqrstuvwxyz"
n=${#var}
idx=32769; while (( idx >= (32768/n)*n )); do idx=$RANDOM; done
char=${var:$idx:1}

注意:你有可能会被while循环永远卡住。

注释:我们不评论RANDOM背后的随机数生成器有多好。我们所做的只是引用源代码中的注释:

source bash 4.4.18 (variables.c)

/* A linear congruential random number generator based on the example
   one in the ANSI C standard. This one isn't very good, but a more
   complicated one is overkill.
*/

2
好的决定。如果有人要使用这种机制来处理加密密钥等敏感信息,谨慎是绝对必要的。 - Charles Duffy

4

您还可以以更简洁的方式使用shuf

基本上,您将字符串分成每行一个字符,然后以常规方式使用shuf:

使用fold进行拆分

echo "abcdefghijklmnopqrstuvwxyz" | fold -w1 | shuf -n1

使用 grep 进行分割
echo "abcdefghijklmnopqrstuvwxyz" | grep -o . | shuf -n1

很好的一行代码,如果你找到了更短的方法,请告诉我。谢谢! - Jieiku

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