Número aleatorio de un rango en un script Bash

197

Necesito generar un número de puerto aleatorio entre 2000-65000un script de shell. El problema es$RANDOM es un número de 15 bits, ¡así que estoy atascado!

PORT=$(($RANDOM%63000+2001)) funcionaría bien si no fuera por la limitación de tamaño.

¿Alguien tiene un ejemplo de cómo puedo hacer esto, tal vez extrayendo algo /dev/urandomy poniéndolo dentro de un rango?

Jason Gooner
fuente

Respuestas:

398
shuf -i 2000-65000 -n 1

¡Disfrutar!

Editar : el rango es inclusivo.

leedm777
fuente
77
Creo que shufes relativamente reciente: lo he visto en los sistemas Ubuntu en los últimos dos años, pero no en el RHEL / CentOS actual.
Cascabel
55
Además, probablemente esté bien para este uso, pero creo shufque en realidad permuta toda la entrada. Esto lo convierte en una mala elección si está generando números aleatorios con mucha frecuencia.
Cascabel
3
@ Jefromi: En mi sistema, utilizando esta prueba time for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; doney comparando end=1a end=65535mostró aproximadamente una mejora del 25% para la gama más corta que ascendió a aproximadamente 4 segundos diferencia más de un millón iteraciones. Y es mucho más rápido que realizar el cálculo de Bash del OP un millón de veces.
Pausado hasta nuevo aviso.
8
@Dennis Williamson: ejecutar su prueba con -n 1mostró diferencias de tiempo insignificantes, incluso con end=4000000000. Es bueno saber que shuffunciona de manera inteligente, no difícil :-)
leedm777
66
no tengo shuf en mi mac :(
Viren
79

En Mac OS X y FreeBSD también puede usar jot:

jot -r 1  2000 65000
errno
fuente
55
En este ejemplo, jottiene una distribución injusta para el mínimo y el máximo del intervalo (es decir, 2000 y 65000). En otras palabras, el mínimo y el máximo se generarán con menos frecuencia. Vea mi respuesta jota para más detalles y una solución alternativa.
Clint Pachl
jottambién está disponible en la mayoría de las distribuciones de GNU / Linux
Thor
43

Según la página de manual de bash, $RANDOMse distribuye entre 0 y 32767; es decir, es un valor de 15 bits sin signo. Suponiendo que $RANDOMse distribuye uniformemente, puede crear un entero de 30 bits sin signo distribuido uniformemente de la siguiente manera:

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

Debido a que su rango no es una potencia de 2, un simple operación de módulo sólo será casi le dan una distribución uniforme, pero con un rango de entrada de 30 bits y un rango de salida de menos-que-16 bits, ya que tiene en su caso, esto realmente debería estar lo suficientemente cerca:

PORT=$(( ((RANDOM<<15)|RANDOM) % 63001 + 2000 ))
Jesin
fuente
1
La variable $RANDOMno siempre está disponible en todos los shells. Buscando otra solución
Lukas Liesis
Si entiendo esto correctamente, está distribuyendo 32,000 números en un rango de 1,000,000,000. Pero solo golpearán en múltiplos de 2 ^ 15: estás saltando el conteo por 2 ^ 15, no llenando todos los dígitos entre 1 y 2 ^ 30 de manera uniforme, que es lo que es una distribución uniforme.
isomorfismos
@isomorphismes Tenga en cuenta que el código hace referencia $RANDOMdos veces. En los shells que admiten $RANDOM, se genera un nuevo valor cada vez que se hace referencia a él. Entonces, este código llena los bits 0 a 14 con un $RANDOMvalor y los bits 15 a 29 con otro. Suponiendo que $RANDOMsea ​​uniforme e independiente, esto cubre todos los valores de 0 a 2 ** 30-1 sin omitir nada.
Jesin
41

y aquí hay uno con Python

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

y uno con awk

awk 'BEGIN{srand();print int(rand()*(63000-2000))+2000 }'
ghostdog74
fuente
66
Este recibe un voto de mi parte. Escribo scripts de bash para varios sistemas y creo que awk es probablemente la herramienta más abundante para el trabajo. Trabajé en Mac OS X y Centos sin problemas y sé que también funcionará en mi máquina Debian, y probablemente en cualquier otra máquina normal-ish * nix.
John Hunt
66
Sin embargo, la semilla aleatoria de awk solo parece actualizarse una vez por segundo, por lo que es posible que desee a) evitar a toda costa ob) reiniciar la semilla.
John Hunt
+1 porque esta parece ser la única posibilidad POSIX sin compilación: RANDOMPOSIX no lo garantiza,
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
El uso de la -Sopción da como resultado ImportError: No module named random. Funciona si elimino eso. No estoy seguro de cuál era la intención de ghostdog para eso.
Chris Johnson
1
python -S -c "import random; print random.randrange(2000,63000)"Parece funcionar bien. Sin embargo, cuando trato de obtener un número aleatorio entre 1 y 2, parece que siempre obtengo 1 ... ¿Pensamientos?
Hubert Léveillé Gauvin
17

La forma general más simple que me viene a la mente es una línea perl:

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

Siempre puedes usar dos números:

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

Todavía tiene que recortar a su rango. No es un método general de números aleatorios de n bits, pero funcionará para su caso, y todo está dentro de bash.

Si quieres ser realmente lindo y leer desde / dev / urandom, puedes hacer esto:

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

Eso leerá dos bytes y los imprimirá como un int sin signo; todavía tienes que hacer tu recorte.

Cascabel
fuente
Utilicé esta técnica y noté que de vez en cuando no se generará ningún número, simplemente espacio en blanco.
PdC
Requiere perl instalado. Escribo un script que debería ejecutarse en la mayoría de las máquinas Linux, si no en todas, awk
siguiendo la
Agregar números aleatorios favorece los resultados medios a expensas de bajo o alto. No es uniformemente al azar.
isomorfismos
@isomorphismes Sí, si literalmente solo agrega dos números aleatorios. Pero, suponiendo que se refiera a la segunda expresión aquí, eso no es lo que está haciendo. Es un número aleatorio en [0,32767] más una elección aleatoria independiente para el siguiente bit, es decir, 0 o 32768. Es uniforme. (Sin embargo, no es ideal para la pregunta original, ya que debe recortar el rango con el rerollamiento).
Cascabel
7

Si no eres un experto en bash y estabas buscando convertir esto en una variable en un script bash basado en Linux, prueba esto:

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

Eso te da el rango de 200 a 700 $VAR, inclusive.

Berto
fuente
5

Aqui hay otro más. Pensé que funcionaría en casi cualquier cosa, pero la opción aleatoria de ordenar no está disponible en mi caja de centos en el trabajo.

 seq 2000 65000 | sort -R | head -n 1
valadil
fuente
3
sort -Rtampoco está disponible en OS X.
Lri
5

$RANDOMes un número entre 0 y 32767. Desea un puerto entre 2000 y 65000. Estos son 63001 puertos posibles. Si nos atenemos a valores $RANDOM + 2000entre 2000 y 33500 , cubrimos un rango de 31501 puertos. Si lanzamos una moneda y luego agregamos condicionalmente 31501 al resultado, podemos obtener más puertos, de 33501 a 65001 . Entonces, si simplemente dejamos caer 65001, obtenemos la cobertura exacta necesaria, con una distribución de probabilidad uniforme para todos los puertos, parece.

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
}

Pruebas

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
Renato Silva
fuente
5

Puedes hacerlo

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

Si necesita más detalles, consulte Shell Script Random Number Generator .

Nueva salida
fuente
Casi. Esto le da un rango de 2000 a 67000.
Ogre Psalm33
5

Lo mismo con el rubí:

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

La documentación de Bash dice que cada vez$RANDOM se hace referencia, se devuelve un número aleatorio entre 0 y 32767. Si sumamos dos referencias consecutivas, obtenemos valores de 0 a 65534, que cubren el rango deseado de 63001 posibilidades para un número aleatorio entre 2000 y 65000.

Para ajustarlo al rango exacto, utilizamos el módulo de suma 63001, que nos dará un valor de 0 a 63000. Esto a su vez solo necesita un incremento en 2000 para proporcionar el número aleatorio deseado, entre 2000 y 65000. Esto puede ser resumido de la siguiente manera:

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

Pruebas

# 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

Corrección del cálculo.

Aquí hay una prueba completa de fuerza bruta para la exactitud del cálculo. Este programa solo intenta generar todas las 63001 posibilidades diferentes al azar, utilizando el cálculo bajo prueba. El --jobsparámetro debería hacer que se ejecute más rápido, pero no es determinista (el total de posibilidades generadas puede ser inferior a 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

Para determinar cuántas iteraciones se necesitan para obtener una probabilidad dada p/qde que se hayan generado todas las posibilidades de 63001, creo que podemos usar la expresión a continuación. Por ejemplo, aquí está el cálculo para una probabilidad mayor que 1/2 , y aquí para mayor que 9/10 .

Expresión


fuente
1
Te equivocas. $RANDOMes un número entero . Con su "truco" hay muchos valores que nunca se alcanzarán. -1.
gniourf_gniourf
2
No estoy seguro de lo que quieres decir con "es un número entero", pero correcto, el algoritmo estaba equivocado. Multiplicar un valor aleatorio de un rango limitado no aumentará el rango. Necesitamos sumar dos accesos a $RANDOM, y no refactorizar eso en una multiplicación por dos, ya que $RANDOMse supone que cambia en cada acceso. He actualizado la respuesta con la versión de suma.
66
Hacer RANDOM+RANDOMno te dará una distribución uniforme de números aleatorios entre 0 y 65534.
gniourf_gniourf
3
Correcto, en otras palabras, no todas las sumas tienen la misma posibilidad de ocurrir. De hecho, es un pedo a partir de eso, si verificamos el gráfico es una pirámide. Creo que esta es la razón por la que he estado obteniendo tiempos de cálculo considerablemente mayores que los esperados por la fórmula anterior. También hay un problema con la operación del módulo: las sumas desde 63001 a (32767 + 32767) duplican las posibilidades de ocurrencia para los primeros 2534 puertos en comparación con el resto de puertos. He estado pensando en alternativas, pero creo que es mejor comenzar desde cero con una nueva respuesta, así que voy a votar por esta para eliminarla.
44
Es como tirar 2 dados de seis lados. Estadísticamente le da una curva de campana: baja probabilidad de obtener un "2" o "12", con la mayor probabilidad de obtener un "7" en el medio.
Ogre Psalm33
2

O en OS-X, lo siguiente funciona para mí:

$ gsort --random-sort
Chadwick Boggs
fuente
2

PORT=$(($RANDOM%63000+2001)) está cerca de lo que quieres, creo.

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001))evita la limitación de tamaño que te preocupa. Como bash no hace distinciones entre una variable numérica y una variable de cadena, esto funciona perfectamente bien. El "número" $RANDOMpuede concatenarse como una cadena, y luego usarse como un número en un cálculo. ¡Asombroso!

Derrochador
fuente
1
Ya veo lo que dices. Estoy de acuerdo en que la distribución será diferente, pero de todos modos no se puede obtener una aleatoriedad real. Puede ser mejor usar a veces $ RANDOM, a veces $ RANDOM $ RANDOM y, a veces, $ RANDOM $ RANDOM $ RANDOM para obtener una distribución más uniforme. Más $ RANDOMs favorece números de puerto más altos, por lo que puedo decir.
Wastrel
(Eliminé mi comentario original, ya que usé algunos valores numéricos incorrectos y era demasiado tarde para editar el comentario). Correcto. x=$(( $n%63000 )es más o menos similar a x=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000.
chepner
No iba a criticar (ni siquiera hacer) las matemáticas. Simplemente lo acepté. Esto es lo que quise decir: num = ($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM); pick = $ (($ RANDOM% 3)); PORT = $ (($ {num [$ pick]}% 63000 + 2001)) --- eso parece ser un gran problema ...
Wastrel
1

Puede obtener el número aleatorio a través de urandom

head -200 /dev/urandom | cksum

Salida:

3310670062 52870

Para recuperar la parte del número anterior.

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

Entonces la salida es

3310670062

Para cumplir con sus requisitos,

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

zangw
fuente
0

Así es como generalmente genero números aleatorios. Luego uso "NUM_1" como la variable para el número de puerto que uso. Aquí hay un breve script de ejemplo.

#!/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
Yokai
fuente