Bash脚本中范围内的随机数

Jas*_*ner 177 bash shell scripting

我需要在2000-65000shell脚本之间生成一个随机端口号.问题是$RANDOM15位数,所以我卡住了!

PORT=$(($RANDOM%63000+2001)) 如果不是因为尺寸限制,它会很好地工作.

有没有人有一个如何做到这一点的例子,可能是从/dev/urandom一个范围内提取并得到它?

lee*_*777 362

shuf -i 2000-65000 -n 1
Run Code Online (Sandbox Code Playgroud)

请享用!

编辑:范围包含在内.

  • 我认为`shuf`是相对较新的 - 我在过去几年在Ubuntu系统上看过它,但目前的RHEL/CentOS并没有. (6认同)
  • @Dennis Williamson:使用`-n 1`运行测试显示可忽略不计的时差,即使是`end = 4000000000`.很高兴知道`shuf`很聪明,而不是很难:-) (6认同)
  • 我的mac上没有shuf :( (6认同)
  • 此外,它可能适合这种用途,但我相信`shuf`实际上确实可以置换整个输入.如果您经常生成随机数,这将是一个糟糕的选择. (3认同)
  • @Jefromi:在我的系统上,在{1..1000}中使用此测试`i的时间; do shuf -i 0- $ end -n 1000>/dev/null; 完成`并将`end = 1`与`end = 65535`进行比较表明较短范围的改善约为25%,相当于超过一百万次迭代的约4秒差异.它比**执行OP的Bash计算快一百万倍. (2认同)
  • @Dennis Williamson:感谢您的基准测试。我当时在 CentOS 系统上,无法确定;我认为它不会那么糟糕,因为它在 C 代码中。我真的只是想指出它实际上不仅仅是生成一个随机数。 (2认同)
  • @VirenShakya - 如果您安装[Homebrew](http://mxcl.github.com/homebrew/),那么您可以`brew install coreutils`.使用前缀`g`安装命令,因此它将是`gshuf`. (2认同)
  • 缺省情况下,“ shuf” [不使用加密安全的随机数生成器](https://www.gnu.org/software/coreutils/manual/html_node/Random-sources.html)。如果需要的话,添加`--random-source = / dev / urandom`。 (2认同)

小智 76

在Mac OS X和FreeBSD上你也可以使用jot:

jot -r 1  2000 65000
Run Code Online (Sandbox Code Playgroud)

  • 在这个例子中,`jot`对于区间的最小值和最大值(即2000和65000)具有不公平的分布.换句话说,min和max的生成频率会降低.有关详细信息和解决方法,请参阅我的[jot answer](http://unix.stackexchange.com/a/241199/141917). (4认同)

Jes*_*sin 39

根据bash手册页,$RANDOM分布在0到32767之间; 也就是说,它是一个无符号的15位值.假设$RANDOM均匀分布,您可以创建一个均匀分布的无符号30位整数,如下所示:

$(((RANDOM<<15)|RANDOM))
Run Code Online (Sandbox Code Playgroud)

由于你的范围不是2的幂,简单的模运算几乎只能给你一个均匀的分布,但是输入范围是30位,输出范围小于16位,就像你的情况一样,这应该足够接近:

PORT=$(( ((RANDOM<<15)|RANDOM) % 63001 + 2000 ))
Run Code Online (Sandbox Code Playgroud)


gho*_*g74 36

这是Python的一个

randport=$(python -S -c "import random; print random.randrange(2000,63000)")
Run Code Online (Sandbox Code Playgroud)

和一个与awk

awk 'BEGIN{srand();print int(rand()*(63000-2000))+2000 }'
Run Code Online (Sandbox Code Playgroud)

  • 这个得到了我的支持.我为各种系统编写bash脚本,我相信awk可能是这项工作最丰富的工具.在没有问题的情况下使用mac os x和centos,我知道它也可以在我的debian机器上运行,也可能是任何其他普通的ish*nix机器. (5认同)
  • 但是,awk的随机种子似乎只刷新一次/秒,因此您可能希望a)不惜一切代价避免或b)重新初始化种子. (4认同)

Cas*_*bel 15

想到的最简单的一般方法是perl one-liner:

perl -e 'print int(rand(65000-2000)) + 2000'
Run Code Online (Sandbox Code Playgroud)

你总是可以使用两个数字:

PORT=$(($RANDOM + ($RANDOM % 2) * 32768))
Run Code Online (Sandbox Code Playgroud)

你仍然需要剪辑到你的范围.它不是一般的n位随机数方法,但它适用于你的情况,而且它都在bash中.

如果你想要非常可爱并从/ dev/urandom中读取,你可以这样做:

od -A n -N 2 -t u2 /dev/urandom
Run Code Online (Sandbox Code Playgroud)

那将读取两个字节并将它们打印为unsigned int; 你仍然需要剪裁.


val*_*dil 5

这是另一个.我认为它几乎可以用于任何事情,但是我的centos盒子里没有sort的随机选项.

 seq 2000 65000 | sort -R | head -n 1
Run Code Online (Sandbox Code Playgroud)

  • `sort -R`在OS X上也不可用. (3认同)

小智 5

你可以这样做

cat /dev/urandom|od -N2 -An -i|awk -v f=2000 -v r=65000 '{printf "%i\n", f + r * $1 / 65536}'
Run Code Online (Sandbox Code Playgroud)

如果需要更多详细信息,请参阅Shell脚本随机数生成器.


小智 5

Bash 文档说,每次$RANDOM引用时,都会返回 0 到 32767 之间的随机数。如果我们对两个连续引用求和,我们会得到从 0 到 65534 的值,这涵盖了 2000 到 65000 之间的随机数所需的 63001 种可能性范围。

为了将其调整到精确的范围,我们使用模 63001 求和,这将为我们提供一个从 0 到 63000 的值。反过来,只需增加 2000 即可提供所需的随机数,范围在 2000 到 65000 之间。这可以是总结如下:

port=$((((RANDOM + RANDOM) % 63001) + 2000))
Run Code Online (Sandbox Code Playgroud)

测试

# Generate random numbers and print the lowest and greatest found
test-random-max-min() {
    max=2000
    min=65000
    for i in {1..10000}; do
        port=$((((RANDOM + RANDOM) % 63001) + 2000))
        echo -en "\r$port"
        [[ "$port" -gt "$max" ]] && max="$port"
        [[ "$port" -lt "$min" ]] && min="$port"
    done
    echo -e "\rMax: $max, min: $min"
}

# Sample output
# Max: 64990, min: 2002
# Max: 65000, min: 2004
# Max: 64970, min: 2000
Run Code Online (Sandbox Code Playgroud)

计算的正确性

这是对计算正确性的完整的强力测试。该程序只是尝试使用测试中的计算随机生成所有 63001 种不同的可能性。该--jobs参数应该使它运行得更快,但它不是确定性的(生成的可能性总数可能低于 63001)。

test-all() {
    start=$(date +%s)
    find_start=$(date +%s)
    total=0; ports=(); i=0
    rm -f ports/ports.* ports.*
    mkdir -p ports
    while [[ "$total" -lt "$2" && "$all_found" != "yes" ]]; do
        port=$((((RANDOM + RANDOM) % 63001) + 2000)); i=$((i+1))
        if [[ -z "${ports[port]}" ]]; then
            ports["$port"]="$port"
            total=$((total + 1))
            if [[ $((total % 1000)) == 0 ]]; then
                echo -en "Elapsed time: $(($(date +%s) - find_start))s \t"
                echo -e "Found: $port \t\t Total: $total\tIteration: $i"
                find_start=$(date +%s)
            fi
        fi
    done
    all_found="yes"
    echo "Job $1 finished after $i iterations in $(($(date +%s) - start))s."
    out="ports.$1.txt"
    [[ "$1" != "0" ]] && out="ports/$out"
    echo "${ports[@]}" > "$out"
}

say-total() {
    generated_ports=$(cat "$@" | tr ' ' '\n' | \sed -E s/'^([0-9]{4})$'/'0\1'/)
    echo "Total generated: $(echo "$generated_ports" | sort | uniq | wc -l)."
}
total-single() { say-total "ports.0.txt"; }
total-jobs() { say-total "ports/"*; }
all_found="no"
[[ "$1" != "--jobs" ]] && test-all 0 63001 && total-single && exit
for i in {1..1000}; do test-all "$i" 40000 & sleep 1; done && wait && total-jobs
Run Code Online (Sandbox Code Playgroud)

为了确定需要多少次迭代才能获得p/q生成所有 63001 种可能性的给定概率,我相信我们可以使用下面的表达式。例如,这里是大于 1/2 的概率的计算这里是大于 9/10 的概率的计算。

表达

  • 执行“RANDOM+RANDOM”不会为您提供 0 到 65534 之间随机数的_均匀_分布。 (6认同)
  • 这就像掷两个六面骰子。从统计上来说,它给你一个钟形曲线:滚动“2”或“12”的概率很低,在中间得到“7”的概率最高。 (4认同)
  • 正确,换句话说,并非所有的和都有相同的发生机会。事实上,事实并非如此,如果我们检查图表,它是一座金字塔!我认为这就是为什么我的计算时间比上面公式预期的要长得多。模运算还存在一个问题:与其余端口相比,从 63001 到 (32767 + 32767) 的总和使前 2534 个端口出现的机会增加了一倍。我一直在考虑替代方案,但我认为最好从新的答案开始,所以我投票删除这个答案。 (3认同)
  • 我不确定你所说的“是一个整数”是什么意思,但正确的是,算法是错误的。将有限范围内的随机值相乘不会增加范围。我们需要将两次对“$RANDOM”的访问相加,并且不要将其重构为乘以二,因为“$RANDOM”应该在每次访问时发生变化。我已经用总和版本更新了答案。 (2认同)

Ren*_*lva 5

$RANDOM是0到32767之间的数字。您需要2000到65000之间的端口。这是63001个可能的端口。如果我们将值保持$RANDOM + 2000200033500之间,则可以覆盖31501个端口。如果我们掷硬币,然后有条件地将31501添加到结果中,我们可以获得更多的端口,从3350165001。然后,如果我们仅丢弃65001,就可以得到所需的确切覆盖范围,并且所有端口的概率分布均匀。

random-port() {
    while [[ not != found ]]; do
        # 2000..33500
        port=$((RANDOM + 2000))
        while [[ $port -gt 33500 ]]; do
            port=$((RANDOM + 2000))
        done

        # 2000..65001
        [[ $((RANDOM % 2)) = 0 ]] && port=$((port + 31501)) 

        # 2000..65000
        [[ $port = 65001 ]] && continue
        echo $port
        break
    done
}
Run Code Online (Sandbox Code Playgroud)

测试中

i=0
while true; do
    i=$((i + 1))
    printf "\rIteration $i..."
    printf "%05d\n" $(random-port) >> ports.txt
done

# Then later we check the distribution
sort ports.txt | uniq -c | sort -r
Run Code Online (Sandbox Code Playgroud)


Ber*_*rto 5

如果您不是bash专家,并且希望将其转换为基于Linux的bash脚本中的变量,请尝试以下操作:

VAR=$(shuf -i 200-700 -n 1)

这将使您获得200到700的范围$VAR,包括在内.


Lev*_*sky 5

与红宝石相同:

echo $(ruby -e 'puts rand(20..65)') #=> 65 (inclusive ending)
echo $(ruby -e 'puts rand(20...65)') #=> 37 (exclusive ending)
Run Code Online (Sandbox Code Playgroud)