web-dev-qa-db-fra.com

Nombre aléatoire d'une plage dans un script Bash

J'ai besoin de générer un numéro de port aléatoire entre 2000-65000 à partir d'un script Shell. Le problème est que $RANDOM est un nombre de 15 bits, je suis donc bloqué!

PORT=$(($RANDOM%63000+2001)) fonctionnerait bien s'il n'y avait pas de limite de taille.

Quelqu'un at-il un exemple de ce que je peux faire, peut-être en extrayant quelque chose de /dev/urandom et en l’obtenant dans une plage?

166
Jason Gooner
shuf -i 2000-65000 -n 1

Prendre plaisir!

Edit: La plage est inclusive.

345
leedm777

Sous Mac OS X et FreeBSD, vous pouvez également utiliser la commande suivante:

jot -r 1  2000 65000
75
errno

Selon la page de manuel bash, $RANDOM est distribué entre 0 et 32767; c'est-à-dire qu'il s'agit d'une valeur non signée de 15 bits. En supposant que $RANDOM soit uniformément distribué, vous pouvez créer un entier de 30 bits non signé uniformément distribué comme suit:

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

Puisque votre portée n’est pas une puissance de 2, une simple opération modulo ne presque vous donner une distribution uniforme, mais avec une plage d'entrée de 30 bits et une plage de sortie inférieure à 16 bits, comme vous l'avez fait dans votre cas, cela devrait vraiment être assez proche:

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

et voici un avec Python

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

et un avec awk

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

La manière générale la plus simple qui me vienne à l’esprit est le Perl one-liner:

Perl -e 'print int(Rand(65000-2000)) + 2000'

Vous pouvez toujours utiliser deux chiffres:

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

Vous devez encore couper à votre portée. Ce n'est pas une méthode générale de nombres aléatoires à n bits, mais cela fonctionnera pour votre cas, et tout se trouve dans bash.

Si vous voulez être vraiment mignon et lire dans/dev/urandom, vous pouvez faire ceci:

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

Cela va lire deux octets et les imprimer comme un entier non signé; vous devez encore faire votre coupure.

14
Cascabel

$RANDOM est un nombre compris entre 0 et 32767. Vous souhaitez un port compris entre 2000 et 65000. Il s'agit de 63001 ports possibles. Si nous nous en tenons aux valeurs de $RANDOM + 2000 entre 20 et 5, nous couvrons une plage de 31501 ports. Si nous lançons une pièce de monnaie et que nous ajoutons ensuite conditionnellement 31501 au résultat, nous pouvons obtenir plus de ports, de 501 à 65001. Ensuite, si nous supprimons simplement 65001, nous obtenons la couverture exacte nécessaire, avec une distribution de probabilité uniforme pour tous les ports, semble-t-il.

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
}

Test

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

En voici un autre. Je pensais que ça marcherait sur à peu près n'importe quoi, mais l'option de type aléatoire n'est pas disponible sur ma boîte de centos au travail.

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

Tu peux le faire

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

Si vous avez besoin de plus de détails, voir Générateur de nombres aléatoires de scripts shell .

5
New Exit

Si vous n'êtes pas un expert en bash et cherchez à insérer cela dans une variable d'un script bash basé sur Linux, essayez ceci:

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

Cela vous donne la plage de 200 à 700 dans $VAR, inclus.

5
Berto

Même avec Ruby:

echo $(Ruby -e 'puts Rand(20..65)') #=> 65 (inclusive ending)
echo $(Ruby -e 'puts Rand(20...65)') #=> 37 (exclusive ending)
4
Lev Lukomsky

La documentation de Bash dit que chaque fois que $RANDOM est référencé, un nombre aléatoire compris entre 0 et 32767 est renvoyé. Si nous additionnons deux références consécutives, nous obtenons des valeurs allant de 0 à 65534, ce qui couvre la plage souhaitée de 63001 possibilités pour un nombre aléatoire compris entre 2000 et 65000.

Pour l'ajuster à la plage exacte, nous utilisons la somme modulo 63001, ce qui nous donnera une valeur comprise entre 0 et 63000. Ceci nécessite à son tour un incrément de 2000 pour fournir le nombre aléatoire souhaité, entre 2000 et 65000. Cela peut être résumées comme suit:

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

Test

# 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

Correction du calcul

Voici un test complet de la force brute pour l'exactitude du calcul. Ce programme essaie juste de générer toutes les possibilités différentes de 63 001 différentes de manière aléatoire, en utilisant le calcul sous test. Le paramètre --jobs devrait le rendre plus rapide, mais il n'est pas déterministe (le total des possibilités générées peut être inférieur à 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

Pour déterminer combien d'itérations sont nécessaires pour obtenir une probabilité donnée p/q de toutes les possibilités 63001 générées, je pense que nous pouvons utiliser l'expression ci-dessous. Par exemple, voici le calcul pour une probabilité supérieure à 1/2 , et ici pour plus de 9/1 .

Expression

3
anon

Ou sur OS-X, les opérations suivantes fonctionnent pour moi:

$ gsort --random-sort
2
Chadwick Boggs

PORT=$(($RANDOM%63000+2001)) est proche de ce que vous voulez, je pense.

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001)) contourne la taille limite qui vous gêne. Comme bash ne fait aucune distinction entre une variable numérique et une variable chaîne, cela fonctionne parfaitement. Le "numéro" $RANDOM peut être concaténé comme une chaîne, puis utilisé comme nombre dans un calcul. Incroyable!

2
Wastrel

Vous pouvez obtenir le nombre aléatoire via urandom

head -200 /dev/urandom | cksum

Sortie:

3310670062 52870

Pour récupérer une partie du numéro ci-dessus.

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

Ensuite, la sortie est

3310670062

Pour répondre à vos besoins,

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

0
zangw

C'est comme ça que je génère d'habitude des nombres aléatoires. J'utilise ensuite "NUM_1" comme variable pour le numéro de port que j'utilise. Voici un court exemple de script.

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