Générer des nombres aléatoires dans une plage spécifique

84

Après avoir un peu cherché Google, je ne trouvais pas de moyen simple d'utiliser une commande shell pour générer un nombre entier décimal aléatoire inclus dans une plage spécifique, comprise entre un minimum et un maximum.

Je l' ai lu /dev/random, /dev/urandomet $RANDOM, mais aucun d' entre eux peuvent faire ce que je dois.

Existe-t-il une autre commande utile ou un moyen d'utiliser les données précédentes?

BowPark
la source

Réponses:

46

Dans la boîte à outils POSIX, vous pouvez utiliser awk:

awk -v min=5 -v max=10 'BEGIN{srand(); print int(min+rand()*(max-min+1))}'

N'utilisez pas cela comme source pour générer des mots de passe ou des données secrètes, par exemple, comme dans la plupart des awkimplémentations, le nombre peut facilement être deviné en fonction du moment où la commande a été exécutée.

Avec de nombreuses awkimplémentations, cette commande exécutée deux fois dans la même seconde vous donnera généralement le même résultat.

Stéphane Chazelas
la source
1
+1 .. Tel qu'utilisé dans un script pour affecter le résultat à une variable (sans introduire de caractère de nouvelle ligne ni utiliser un remplissage nul pour deux emplacements):x=$(awk -v min=$min_var -v max=$max_var 'BEGIN{srand(); printf("%.2d", int(min+rand()*(max-min+1)))}')
Christopher
C'est basé sur le temps? depuis que j'ai eu la même valeur en 2 manches consécutives ...
Shai Alon
@ShaiAlon, l'heure actuelle est souvent utilisée comme valeur de départ par défaut pour srand (), donc généralement, elle est basée sur l'heure, voir la phrase de Stéphane à ce sujet dans sa réponse. Vous pouvez contourner ce problème en remplaçant la valeur initiale par un nombre aléatoire awk -v min=5 -v max=10 -v seed="$(od -An -N4 -tu4 /dev/urandom)" 'BEGIN{srand(seed+0); print int(min+rand()*(max-min+1))}'. Vous pouvez utiliser $RANDOMau lieu de od ... /dev/randommais votre valeur de départ se situe dans une plage relativement petite de 0 à 32767 et vous obtiendrez donc probablement des répétitions notables dans votre sortie au fil du temps.
Ed Morton
142

Vous pouvez essayer shufdepuis GNU coreutils:

shuf -i 1-100 -n 1
cuonglm
la source
Cela fonctionne également avec Busybox shuf.
cov
Fonctionne également dans Raspbian et GitBash.
nPcomp
30

iota

Sous BSD et OSX, vous pouvez utiliser jot pour renvoyer un seul nombre aléatoire ( -r) de l’intervalle minà max, inclus.

$ min=5
$ max=10
$ jot -r 1 $min $max

Problème de distribution

Malheureusement, la portée et la distribution des nombres générés aléatoirement sont influencées par le fait que jot utilise en interne une arithmétique en virgule flottante à double précision en interne et printf (3) pour le format de sortie, ce qui entraîne des problèmes d’arrondi et de troncature. Par conséquent, les intervalles minet maxsont générés moins fréquemment, comme indiqué:

$ jot -r 100000 5 10 | sort -n | uniq -c
9918  5
20176 6
20006 7
20083 8
19879 9
9938  10

Sous OS X 10.11 (El Capitan), cela semble avoir été corrigé:

$ jot -r 100000 5 10 | sort -n | uniq -c
16692 5
16550 6
16856 7
16579 8
16714 9
16609 10  

et...

$ jot -r 1000000 1 10 | sort -n | uniq -c
100430 1
99965 2
99982 3
99796 4
100444 5
99853 6
99835 7
100397 8
99588 9
99710 10

Résoudre le problème de distribution

Heureusement, il existe plusieurs solutions de contournement pour les anciennes versions d’OS X. La première consiste à utiliser la conversion d’entiers printf (3). Le seul inconvénient est que l'intervalle maximum devient maintenant max+1. En utilisant le formatage entier, nous obtenons une distribution juste sur tout l'intervalle:

$ jot -w %i -r 100000 5 11 | sort -n | uniq -c
16756 5
16571 6
16744 7
16605 8
16683 9
16641 10

La solution parfaite

Enfin, pour obtenir un résultat juste des dés en utilisant la solution de contournement, nous avons:

$ min=5
$ max_plus1=11  # 10 + 1
$ jot -w %i -r 1 $min $max_plus1

Devoir supplémentaire

Voir jot (1) pour les détails mathématiques et le formatage, ainsi que de nombreux autres exemples.

Clint Pachl
la source
15

La $RANDOMvariable n’est normalement pas un bon moyen de générer de bonnes valeurs aléatoires. La sortie de /dev/[u]randombesoin doit également être convertie en premier.

Un moyen plus simple consiste à utiliser des langages de niveau supérieur, comme par exemple python:

Pour générer une variable entière aléatoire comprise entre 5 et 10 (5 <= N <= 10), utilisez

python -c "import random; print random.randint(5,10)"

Ne l'utilisez pas pour des applications cryptographiques.

jofel
la source
C'était utile. Merci beaucoup :) Comme cela a souvent été signé avec Unix-> Solaris 10, les utilitaires GNU ne sont pas intégrés par défaut. Cela a fonctionné cependant.
propatience
11

Vous pouvez obtenir le nombre aléatoire à travers 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

zangw
la source
head -200(ou son équivalent POSIX head -n 100) renvoie les 200 premières lignes de /dev/urandom. /dev/urandomn’est pas un fichier texte, cette commande peut retourner de 200 octets (tous les 0x0a, LF en ASCII) à l’infini (si l’octet 0xa n’est jamais renvoyé), mais les valeurs entre 45k et 55k sont les plus probables. cksum renvoie un nombre 32 bits, il est donc inutile d’obtenir plus de 4 octets /dev/urandom.
Stéphane Chazelas
7

Pour générer une variable entière aléatoire comprise entre 5 et 10 (incluant les deux), utilisez

echo $(( RANDOM % (10 - 5 + 1 ) + 5 ))

% travaille comme opérateur modulo.

Il existe probablement de meilleurs moyens de convertir la variable aléatoire $RANDOMen une plage spécifique. Ne l'utilisez pas pour des applications cryptographiques ou dans les cas où vous avez besoin de variables aléatoires réelles, à distribution égale (comme pour les simulations).

jofel
la source
3
Dans beaucoup d'implémentations de shell qui ont $ RANDOM (surtout les plus anciennes, en particulier bash), ce n'est pas très aléatoire. Dans la plupart des coques, le nombre est compris entre 0 et 65535. Ainsi, toute plage dont la largeur n'est pas une puissance de deux aura une différence de distribution de probabilité. (dans ce cas, les nombres 5 à 8 auront une probabilité de 10923/65536, tandis que les 9 et 10 auront une probabilité de 10922/65536). Plus la plage est large, plus la divergence est grande.
Stéphane Chazelas
Désolé, il s'agit de 32767 et non de 65535, le calcul ci-dessus est donc faux (et c'est en fait pire).
Stéphane Chazelas
@ StéphaneChazelas J'avais ceci à l'esprit en écrivant qu'il y a de meilleures façons de ...
jofel
5

# echo $(( $RANDOM % 256 )) produira un nombre "aléatoire" compris entre 0 et 255 dans les dialectes modernes * sh.

Qrkourier
la source
Ressemble à cette autre réponse d'il y a 2 ans.
Jeff Schaller
1
Ceci est sujet au problème du casier (TL; DR: certains résultats sont plus probables que d’autres) si, au lieu de 256, vous utilisez une valeur qui ne divise pas 32768 de manière égale.
toon81
4

Peut-être que UUID(sous Linux) peut être utilisé pour récupérer le nombre aléatoire

$ cat /proc/sys/kernel/random/uuid
cdd52826-327d-4355-9737-895f58ad11b4

Pour obtenir le nombre aléatoire entre 70et100

POSIXLY_CORRECT=1 awk -F - '{print(("0x"$1) % 30 + 70)}
   ' /proc/sys/kernel/random/uuid
zangw
la source
3
cat /dev/urandom | tr -dc 'a-fA-F0-9' | fold -w 8 | head -n 1

Cela générera un nombre hexadécimal long de 8 chiffres.

Tom Cooper
la source
1

Pour rester entièrement dans bash et utiliser la variable $ RANDOM mais éviter la distribution inégale:

#!/bin/bash
range=10 
floor=20

if [ $range -gt 32768 ]; then
echo 'range outside of what $RANDOM can provide.' >&2
exit 1 # exit before entering infinite loop
fi 

max_RANDOM=$(( 2**15/$range*$range ))
r=$RANDOM
until [ $r -lt $max_RANDOM ]; do
r=$RANDOM
done
echo $(( r % $range + $floor ))

Cet exemple fournirait des nombres aléatoires de 20 à 29.

Than Angell
la source
$rdoit être initialisé à 65535 ou à une autre valeur élevée pour éviter une [: -lt: unary operator expectederreur.
LawrenceC
Correction de l'initialisation $ r avant le test de la boucle. Merci @LawrenceC!
Than Angell le
0

La distribution est bonne:

pour ((i = 1; i <= 100000; i ++)) echo $ ((RANDOM% (20 - 10 + 1) + 10)); fait | trier -n | uniq -c

compter la valeur

9183 10

9109 11

8915 12

9037 13

9100 14

9138 15

9125 16

9261 17

9088 18

8996 19

9048 20

SteveK
la source