Bash脚本中某个范围的随机数


195

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

PORT=$(($RANDOM%63000+2001)) 如果没有大小限制,它将很好地工作。

有没有人举例说明我如何做到这一点,也许是通过从中提取某些东西/dev/urandom并使其处于一定范围内?

Answers:


397
shuf -i 2000-65000 -n 1

请享用!

编辑:范围包括在内。


7
我认为shuf是相对较新的-在过去的几年中,我已经在Ubuntu系统上看到了它,但是当前的RHEL / CentOS却没有。
卡斯卡贝尔

5
同样,这种用法可能很好,但是我相信shuf确实会置换整个输入。如果您经常生成随机数,那么这将是一个错误的选择。
卡斯卡贝尔

3
@Jefromi:在我的系统上,使用此测试time for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; done并进行比较end=1end=65535结果显示较短范围的距离提高了约25%,在一百万次迭代中相差约4秒。而且它的很多不是执行OP的计算猛砸一百万倍的速度。
暂停,直到另行通知。

8
@Dennis Williamson:-n 1即使使用,运行时的时间差异也可以忽略不计end=4000000000。众所周知,shuf工作很聪明,但并不难:-)
leedm777

6
我的Mac上没有shuf :(
Viren

79

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

jot -r 1  2000 65000

5
在此示例中,jot间隔的最大值和最小值(即2000和65000)具有不公平的分布。换句话说,最小值和最大值将不太频繁地生成。有关详细信息和解决方法,请参见我的简要答案
Clint Pachl 2015年

jot在大多数GNU / Linux发行版中也可用
Thor

43

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

$(((RANDOM<<15)|RANDOM))

由于您的范围不是2的幂,因此简单的模运算几乎几乎可以为您提供均匀的分布,但是具有30位的输入范围和小于16位的输出范围(如您的情况),这确实应该足够接近:

PORT=$(( ((RANDOM<<15)|RANDOM) % 63001 + 2000 ))

1
变量$RANDOM并非总是在所有shell中都可用。寻找其他解决方案
Lukas Liesis '18 -10-31

如果我正确理解这一点,那么您将在1,000,000,000个范围内传播32,000个数字。但是它们只会命中2 ^ 15的倍数—您将跳过2 ^ 15的计数,而不是均匀地填充1到2 ^ 30之间的所有数字。
同构

@isomorphismes注意,代码引用了$RANDOM两次。在支持的shell上$RANDOM,每次引用它都会生成一个新值。因此,此代码用一个$RANDOM值填充位0到14,并用另一个值填充位15到29。假设$RANDOM是统一且独立的,那么它会覆盖从0到2 ** 30-1的所有值,而不会跳过任何内容。
Jesin

41

这是Python的

randport=$(python -S -c "import random; print random.randrange(2000,63000)")

和一个与awk

awk 'BEGIN{srand();print int(rand()*(63000-2000))+2000 }'

6
这个得到我的支持。我为各种系统编写bash脚本,我相信awk可能是完成该工作的最丰富的工具。可以在mac os x和centos上正常工作,我知道它也可以在我的debian机器上运行,也可能在其他任何普通的* nix机器上运行。
约翰·亨特

6
但是,awk的随机种子似乎仅每秒刷新一次,因此您可能想要a)不惜一切代价避免b)重新初始化种子。
约翰·亨特

+1,因为这似乎是唯一未经编译的POSIX可能性:RANDOMPOSIX无法保证,
Ciro Santilli郝海东冠状病六四事件法轮功2015年

使用该-S选项将导致ImportError: No module named random。如果我删除它的作品。不确定ghostdog的意图是什么。
克里斯·约翰逊

1
python -S -c "import random; print random.randrange(2000,63000)"似乎工作正常。但是,当我尝试获得1到2之间的随机数时,我似乎总是得到1...。
HubertLéveilléGauvin

17

想到的最简单的通用方法是perl一线:

perl -e 'print int(rand(65000-2000)) + 2000'

您总是可以只使用两个数字:

PORT=$(($RANDOM + ($RANDOM % 2) * 32768))

您仍然必须裁剪到您的范围。这不是通用的n位随机数方法,但可以满足您的情况,而且全都在bash中。

如果您想变得非常可爱并且可以从/ dev / urandom中阅读,则可以执行以下操作:

od -A n -N 2 -t u2 /dev/urandom

这将读取两个字节并将它们打印为一个无符号的int;您仍然需要剪裁。


我使用了这种技术,并注意到不时会生成数字,只是空白。
PdC

它需要安装perl。我编写了一个脚本,该脚本应该在大多数(如果不是全部)Linux计算机上运行,​​并坚持使用awk另一个答案的版本
Lukas Liesis

加随机数有利于中间结果,但代价低或高。它不是统一随机的。
同构

@isomorphismes是的,如果您只是在添加两个随机数。但是,假设您在这里指的是第二个表达式,那不是它的作用。它是[0,32767]中的一个随机数,再加上一个独立的下一位随机选择,即0或32768。它是统一的。(尽管您必须通过重新滚动来限制范围,但对于原始问题并不理想。)
卡斯卡贝尔

7

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

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

这样一来,您可以将200到700的范围$VAR包括在内。


5

这是另一个。我以为它几乎可以在任何东西上工作,但是排序的随机选项在我的centos盒中不可用。

 seq 2000 65000 | sort -R | head -n 1

3
sort -R在OS X上也不可用。
Lri 2012年

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
}

测试中

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


5

与红宝石相同:

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

3

Bash文档说,每次$RANDOM引用时,都会返回0到32767之间的随机数。如果我们将两个连续的引用相加,则得到的值从0到65534,这覆盖了2000到65000之间的随机数的63001可能性的期望范围。

要将其调整到正确的范围,我们使用求和模63001,它的取值范围为0到63000。这反过来只需增加2000即可提供所需的2000至65000之间的随机数。总结如下:

port=$((((RANDOM + RANDOM) % 63001) + 2000))

测试中

# 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

计算的正确性

这是一个完整的蛮力测试,用于确定计算的正确性。该程序只是使用被测计算尝试随机生成所有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

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

表达


1
你错了。$RANDOM一个整数。使用您的“技巧”,将有许多永远无法实现的价值。-1
gniourf_gniourf 2012

2
我不确定“是整数”是什么意思,但是正确的是,该算法是错误的。将有限范围内的随机值相乘不会增加范围。我们需要对两个访问进行求和$RANDOM,并且不要将其重构为乘以2,因为$RANDOM应该在每次访问中进行更改。我已经用总和版本更新了答案。

6
这样做RANDOM+RANDOM不会给你一个统一的随机数的0和65534之间的分配
gniourf_gniourf

3
是的,换句话说,并非所有的和都有平等的机会出现。实际上,这与事实相去甚远,如果我们检查图表,它就是一个金字塔!我认为这就是为什么我得到的计算时间比上面公式期望的要大得多的原因。模运算还存在一个问题:从63001到(32767 + 32767)的总和,与其余端口相比,前2534个端口的发生机会加倍。我一直在考虑替代方法,但我认为最好从头开始给出一个新的答案,所以我投票赞成删除。

4
这就像滚动2个六面骰子。从统计上讲,它为您提供了一条钟形曲线:滚动“ 2”或“ 12”的可能性低,而在中间获得“ 7”的可能性最高。
Ogre Psalm33 2013年


2

PORT=$(($RANDOM%63000+2001)) 接近我想要的。

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001))避开困扰您的尺寸限制。由于bash在数字变量和字符串变量之间没有区别,因此效果很好。$RANDOM可以像字符串一样将“数字” 串联起来,然后在计算中用作数字。惊人!


1
我明白你在说什么。我同意分布会有所不同,但是无论如何您都无法获得真正的随机性。有时最好使用$ RANDOM,有时$ RANDOM $ RANDOM和有时$ RANDOM $ RANDOM $ RANDOM以获得更均匀的分布。据我所知,更多的$ RANDOMs支持更高的端口号。
Wastrel

(我删除了原始注释,因为我使用了一些错误的数值,现在编辑注释已经太迟了)。对。x=$(( $n%63000 )大致类似于x=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000
chepner

我不会批评(甚至不会批评)数学。我只是接受了。这就是我的意思:num =($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM); pick = $((($ RANDOM%3)); PORT = $((($ {num [$ pick]}%63000 + 2001)))---似乎很麻烦……
Wastrel 2012年

1

您可以通过获得随机数 urandom

head -200 /dev/urandom | cksum

输出:

3310670062 52870

要检索上述编号的一部分。

head -200 /dev/urandom | cksum | cut -f1 -d " "

然后输出是

3310670062

为了满足您的要求,

head -200 /dev/urandom |cksum | cut -f1 -d " " | awk '{print $1%63000+2001}'


0

这就是我通常生成随机数的方式。然后,将“ NUM_1”用作我使用的端口号的变量。这是一个简短的示例脚本。

#!/bin/bash

clear
echo 'Choose how many digits you want for port# (1-5)'
read PORT

NUM_1="$(tr -dc '0-9' </dev/urandom | head -c $PORT)"

echo "$NUM_1"

if [ "$PORT" -gt "5" ]
then
clear
echo -e "\x1b[31m Choose a number between 1 and 5! \x1b[0m"
sleep 3
clear
exit 0
fi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.