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?
shuf -i 2000-65000 -n 1
Prendre plaisir!
Edit: La plage est inclusive.
Sous Mac OS X et FreeBSD, vous pouvez également utiliser la commande suivante:
jot -r 1 2000 65000
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 ))
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 }'
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.
$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
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
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 .
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.
Même avec Ruby:
echo $(Ruby -e 'puts Rand(20..65)') #=> 65 (inclusive ending)
echo $(Ruby -e 'puts Rand(20...65)') #=> 37 (exclusive ending)
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 .
Ou sur OS-X, les opérations suivantes fonctionnent pour moi:
$ gsort --random-sort
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!
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}'
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