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


197

J'ai besoin de générer un numéro de port aléatoire entre 2000-65000un script shell. Le problème est $RANDOMun nombre de 15 bits, donc je suis coincé!

PORT=$(($RANDOM%63000+2001)) fonctionnerait bien sans la limitation de taille.

Quelqu'un at-il un exemple de la façon dont je peux le faire, peut-être en extrayant quelque chose /dev/urandomet en le plaçant dans une plage?

Réponses:


398
shuf -i 2000-65000 -n 1

Prendre plaisir!

Edit : la gamme est inclusive.


7
Je pense que shufc'est relativement récent - je l'ai vu sur les systèmes Ubuntu au cours des deux dernières années, mais pas sur le RHEL / CentOS actuel.
Cascabel

5
En outre, c'est probablement bien pour cette utilisation, mais je crois que shufcela permute en fait la totalité de l'entrée. Cela en fait un mauvais choix si vous générez des nombres aléatoires très fréquemment.
Cascabel

3
@Jefromi: Sur mon système, en utilisant ce test time for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; doneet la comparaison end=1à end=65535montré une amélioration de 25% pour la plus courte portée qui se sont élevées à environ 4 secondes différence plus d' un million d' itérations. Et c'est beaucoup plus rapide que d'effectuer le calcul Bash de l'OP un million de fois.
pause jusqu'à nouvel ordre.

8
@Dennis Williamson: L'exécution de votre test avec a -n 1montré des différences de temps négligeables, même avec end=4000000000. Bon à savoir shuffonctionne intelligemment, pas dur :-)
leedm777

6
je n'ai pas shuf sur mon mac :(
Viren

79

Sur Mac OS X et FreeBSD, vous pouvez également utiliser jot:

jot -r 1  2000 65000

5
Dans cet exemple, jotla distribution est injuste pour le minimum et le maximum de l'intervalle (c'est-à-dire 2000 et 65000). En d'autres termes, les valeurs min et max seront générées moins fréquemment. Voir ma réponse jot pour plus de détails et une solution de contournement.
Clint Pachl

jotest également disponible dans la plupart des distributions GNU / Linux
Thor

43

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

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

Étant donné que votre plage n'est pas une puissance de 2, une simple opération modulo ne vous donnera quasiment qu'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 dans votre cas, cela devrait vraiment être assez proche:

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

1
variable $RANDOMn'est pas toujours disponible dans tous les shells. Vous cherchez une autre solution
Lukas Liesis

Si je comprends bien, vous étalez 32 000 numéros sur une plage de 1 000 000 000. Mais ils n'atteindront que des multiples de 2 ^ 15 — vous ne comptez pas par 2 ^ 15, sans remplir tous les chiffres entre 1 et 2 ^ 30 également, ce qui est une distribution uniforme.
isomorphismes

@isomorphismes Notez que le code référence $RANDOMdeux fois. Sur les shells qui prennent en charge $RANDOM, une nouvelle valeur est générée chaque fois qu'elle est référencée. Donc, ce code remplit les bits 0 à 14 avec une $RANDOMvaleur et remplit les bits 15 à 29 avec une autre. En supposant qu'il $RANDOMest uniforme et indépendant, cela couvre toutes les valeurs de 0 à 2 ** 30-1 sans rien sauter.
Jesin

41

et en 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 }'

6
Celui-ci reçoit un upvote de moi. J'écris des scripts bash pour différents systèmes et je pense que awk est probablement l'outil le plus abondant pour le travail. A travaillé sur mac os x et centos sans problème et je sais que cela fonctionnera aussi sur ma machine debian, et probablement sur toute autre machine normal-ish * nix.
John Hunt

6
Cependant, la graine aléatoire d'awk ne semble se rafraîchir qu'une fois / seconde, vous pouvez donc a) éviter à tout prix ou b) réinitialiser la graine.
John Hunt

+1 car cela semble être la seule possibilité POSIX sans compilation: RANDOMn'est pas garanti par POSIX,
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

L'utilisation de l' -Soption entraîne ImportError: No module named random. Fonctionne si je supprime cela. Je ne sais pas quelle était l'intention de ghostdog.
Chris Johnson

1
python -S -c "import random; print random.randrange(2000,63000)"semble bien fonctionner. Cependant, quand j'essaye d'obtenir un nombre aléatoire entre 1 et 2, il me semble que j'obtiens toujours 1 ... Pensées?
Hubert Léveillé Gauvin

17

La manière générale la plus simple qui me vient à l'esprit est un perl one-liner:

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

Vous pouvez toujours simplement utiliser deux nombres:

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

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

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

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

Cela lira deux octets et les imprimera comme un entier non signé; vous devez toujours faire votre coupure.


J'ai utilisé cette technique et j'ai remarqué que de temps en temps, aucun chiffre ne sera généré, simplement un espace vide.
PdC

Il nécessite l'installation de perl. J'écris un script qui devrait fonctionner sur la plupart sinon toutes les machines Linux, en restant avec la awkversion d'une autre réponse
Lukas Liesis

L'ajout de nombres aléatoires favorise les résultats intermédiaires au détriment des valeurs faibles ou élevées. Ce n'est pas uniformément aléatoire.
isomorphismes

@isomorphismes Oui, si vous ajoutez littéralement deux nombres aléatoires. Mais, en supposant que vous faites référence à la deuxième expression ici, ce n'est pas ce qu'elle fait. C'est un nombre aléatoire dans [0,32767] plus un choix aléatoire indépendant pour le bit suivant, c'est-à-dire 0 ou 32768. Il est uniforme. (Ce n'est pas idéal pour la question d'origine, car vous devez couper la plage avec un nouveau défilement.)
Cascabel

7

Si vous n'êtes pas un expert bash et que vous cherchiez à l'intégrer dans une variable dans un script bash basé sur Linux, essayez ceci:

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

Cela vous fait passer de 200 à 700 $VAR, inclus.


5

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

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

3
sort -Rn'est pas non plus disponible sur OS X.
Lri

5

$RANDOMest un nombre compris entre 0 et 32767. Vous voulez un port entre 2000 et 65000. Ce sont 63001 ports possibles. Si nous nous en tenons à des valeurs $RANDOM + 2000comprises entre 2000 et 33500 , nous couvrons une gamme de 31501 ports. Si nous lançons une pièce puis ajoutons conditionnellement 31501 au résultat, nous pouvons obtenir plus de ports, de 33501 à 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
}

Essai

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

Même chose avec le rubis:

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

3

La documentation de Bash indique que chaque fois qu'il $RANDOMest 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 de 0 à 65534, ce qui couvre la plage souhaitée de 63001 possibilités pour un nombre aléatoire entre 2000 et 65000.

Pour l'ajuster à la plage exacte, nous utilisons la somme modulo 63001, qui nous donnera une valeur de 0 à 63000. Cela à son tour a juste besoin d'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))

Essai

# 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

Exactitude du calcul

Voici un test complet de force brute pour l'exactitude du calcul. Ce programme essaie simplement de générer toutes les 63001 possibilités différentes au hasard, en utilisant le calcul sous test. Le --jobsparamètre devrait le faire fonctionner plus rapidement, 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 que p/qtoutes les possibilités 63001 aient été 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 supérieure à 9/10 .

Expression


1
Vous vous trompez. $RANDOMest un entier . Avec votre "astuce", il existe de nombreuses valeurs qui ne seront jamais atteintes. -1.
gniourf_gniourf

2
Je ne sais pas ce que vous voulez dire par "est un entier", mais correct, l'algorithme était incorrect. La multiplication d'une valeur aléatoire dans une plage limitée n'augmentera pas la plage. Nous devons additionner deux accès à la $RANDOMplace, et ne pas refactoriser cela en une multiplication par deux, car il $RANDOMest censé changer à chaque accès. J'ai mis à jour la réponse avec la version somme.

6
Faire RANDOM+RANDOMne vous donnera pas une distribution uniforme de nombres aléatoires entre 0 et 65534.
gniourf_gniourf

3
En effet, toutes les sommes n'ont pas toutes les mêmes chances de se produire. En fait, c'est loin de ça, si on regarde le graphique c'est une pyramide! Je pense que c'est pourquoi j'ai obtenu des temps de calcul considérablement plus longs que ceux attendus par la formule ci-dessus. Il y a aussi un problème avec l'opération modulo: les sommes de 63001 à (32767 + 32767) doublent les chances d'occurrence pour les premiers 2534 ports par rapport au reste des ports. J'ai pensé à des alternatives, mais je pense qu'il vaut mieux recommencer à zéro avec une nouvelle réponse, donc je vote celle-ci pour suppression.

4
C'est comme lancer 2 dés à six faces. Statistiquement, il vous donne une courbe en cloche: faible probabilité de rouler un "2" ou "12", avec la plus forte probabilité d'obtenir un "7" au milieu.
Ogre Psalm33


2

PORT=$(($RANDOM%63000+2001)) est proche de ce que tu veux je pense.

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001))contourne la limitation de taille qui vous dérange. Étant donné que bash ne fait aucune distinction entre une variable numérique et une variable chaîne, cela fonctionne parfaitement bien. Le "nombre" $RANDOMpeut être concaténé comme une chaîne, puis utilisé comme nombre dans un calcul. Incroyable!


1
Je vois ce que tu dis. Je suis d'accord que la distribution sera différente, mais de toute façon, vous ne pouvez pas obtenir un véritable hasard. Il peut être préférable d'utiliser parfois $ RANDOM, parfois $ RANDOM $ RANDOM et parfois $ RANDOM $ RANDOM $ RANDOM pour obtenir une distribution plus uniforme. Plus de $ RANDOMs favorisent des numéros de port plus élevés, pour autant que je sache.
Wastrel

(J'ai supprimé mon commentaire d'origine, car j'ai utilisé des valeurs numériques erronées et il était trop tard pour modifier le commentaire). Droite. x=$(( $n%63000 )est à peu près similaire à x=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000.
chepner

Je n'allais pas critiquer (ni même faire) les mathématiques. Je l'ai simplement accepté. Voici ce que je voulais dire: num = ($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM); pick = $ (($ RANDOM% 3)); PORT = $ (($ {num [$ pick]}% 63000 + 2001)) --- cela semble être un
vrai

1

Vous pouvez obtenir le nombre aléatoire via urandom

head -200 /dev/urandom | cksum

Production:

3310670062 52870

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

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

Ensuite, la sortie est

3310670062

Pour répondre à votre besoin,

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


0

C'est ainsi que je génère habituellement des nombres aléatoires. Ensuite, j'utilise "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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.