Je me demandais quelle serait la meilleure façon d'obtenir un bon caractère aléatoire en bash, c'est-à-dire quelle serait la procédure pour obtenir un entier positif aléatoire entre MIN
et MAX
tel que
- La plage peut être arbitrairement grande (ou au moins, disons, jusqu'à 2 32 -1);
- Les valeurs sont réparties uniformément (c.-à-d., Pas de biais);
- C'est efficace.
Un moyen efficace d'obtenir un caractère aléatoire dans bash est d'utiliser la $RANDOM
variable. Cependant, cela n'échantillonne qu'une valeur comprise entre 0 et 2 15 -1, qui peut ne pas être suffisamment grande pour tous les usages. Les gens utilisent généralement un modulo pour le mettre dans la plage qu'ils souhaitent, par exemple,
MIN=0
MAX=12345
rnd=$(( $RANDOM % ($MAX + 1 - $MIN) + $MIN ))
De plus, cela crée un biais, sauf s'il $MAX
arrive de diviser 2 15 -1 = 32767. Par exemple, si $MIN
est 0 et $MAX
est 9, alors les valeurs 0 à 7 sont légèrement plus probables que les valeurs 8 et 9, comme $RANDOM
jamais 32768 ou 32769. Ce biais s'aggrave à mesure que la plage augmente, par exemple, si $MIN
est 0 et $MAX
est 9999, puis les chiffres de 0 à 2767 ont une probabilité de 4 / 32767 , tandis que les numéros 2768 à 9999 ont seulement une probabilité de 3 / 32767 .
Ainsi, bien que la méthode ci-dessus remplisse la condition 3, elle ne remplit pas les conditions 1 et 2.
La meilleure méthode que j'ai trouvée jusqu'à présent pour essayer de satisfaire aux conditions 1 et 2 était d'utiliser la méthode /dev/urandom
suivante:
MIN=0
MAX=1234567890
while
rnd=$(cat /dev/urandom | tr -dc 0-9 | fold -w${#MAX} | head -1 | sed 's/^0*//;')
[ -z $rnd ] && rnd=0
(( $rnd < $MIN || $rnd > $MAX ))
do :
done
Fondamentalement, il suffit de collecter le caractère aléatoire à partir de /dev/urandom
(pourrait envisager d'utiliser à la /dev/random
place si un générateur de nombres pseudo-aléatoires cryptographiquement fort est souhaité, et si vous avez beaucoup de temps, ou bien peut-être un générateur de nombres aléatoires matériel), supprimez chaque caractère qui n'est pas un chiffre décimal, pliez la sortie à la longueur $MAX
et couper les 0 en tête. S'il nous arrivait de n'obtenir que des 0, alors il $rnd
est vide, alors dans ce cas, réglez rnd
sur 0
. Vérifiez si le résultat est en dehors de notre plage et si oui, répétez. J'ai forcé le "corps" de la boucle while dans le garde ici afin de forcer l'exécution du corps au moins une fois, dans l'esprit d'émuler une do ... while
boucle, car il rnd
n'est pas défini pour commencer.
Je pense que j'ai rempli les conditions 1 et 2 ici, mais maintenant j'ai foiré la condition 3. C'est un peu lent. Ça prend environ une seconde (dixième de seconde quand j'ai de la chance). En fait, la boucle n'est même pas garantie de se terminer (bien que la probabilité de résiliation converge vers 1 lorsque le temps augmente).
Existe-t-il un moyen efficace d'obtenir des entiers aléatoires non biaisés, dans une plage prédéfinie et potentiellement large, en bash? (Je continuerai d'enquêter lorsque le temps le permettra, mais en attendant, je pensais que quelqu'un ici pourrait avoir une idée sympa!)
Tableau des réponses
L'idée la plus fondamentale (et donc portable) est de générer une chaîne de bits aléatoire juste assez longtemps. Il existe différentes façons de générer une chaîne de bits aléatoire, en utilisant la
$RANDOM
variable intégrée de bash ou en utilisantod
et/dev/urandom
(ou/dev/random
). Si le nombre aléatoire est supérieur à$MAX
, recommencez.- Solution bash complète pour des plages arbitraires utilisant soit
$RANDOM
ou/dev/urandom
- L'idée générale
- Obtenez une chaîne de bits aléatoire en utilisant soit
openssl
ouod
avec/dev/urandom
. Embellissez avectr
. - Obtenez une chaîne de bits aléatoire en utilisant
od
avec/dev/random
. Embellissez avecawk
.
- Solution bash complète pour des plages arbitraires utilisant soit
Alternativement, il est possible d'utiliser des outils externes.
- La solution Perl
- Pro: assez portable, simple, flexible
- Contra: pas pour les très grands nombres supérieurs à 2 32 -1
- La solution Python
- Pro: simple, flexible, fonctionne même pour les grands nombres
- Contra: moins portable
- La solution zsh
- Pro: bon pour les personnes qui utilisent quand même zsh
- Contra: probablement encore moins portable
- La solution Perl
la source
rand=$(command)
fasse sicommand
retourne un ieger qui répond à vos exigences?dd if=/dev/urandom 2>/dev/null
et en canalisant celaod -t d
(évite le détour par la base64), mais je ne sais pas comment la conversion se produit et si elle est effectivement impartiale. Si vous pouvez développer votre idée en un script efficace et fonctionnel et expliquer pourquoi il n'y a pas de parti pris, cela constituerait une excellente réponse. :)python
ouperl
ou votre langue préférée, mais ce n'est pas disponible partout. Je préfère quelque chose de plus portable. Eh bien,awk
la fonction aléatoire de ce serait bien, je suppose. Mais plus c'est portable, mieux c'est :)perl -e 'print int(rand(2**32-1))');
. C'est sacrément portable et ce sera très rapide. Awk ne le coupera pas car la plupart des implémentations partent de la même graine. Vous obtenez donc le même nombre aléatoire lors des exécutions suivantes. Il ne change que dans le même cycle.Réponses:
Je vois une autre méthode intéressante d' ici .
Celui- ci semble également être une bonne option. Il lit 4 octets du périphérique aléatoire et les formate comme un entier non signé entre
0
et2^32-1
.la source
/dev/urandom
sauf si vous savez que vous en avez besoin/dev/random
;/dev/random
blocs sous Linux.od
commandes sont-elles différentes. Les deux affichent simplement des entiers non signés de 4 octets: 1er - depuis openssl, 2e - depuis/dev/random
./dev/urandom
place de/dev/random
- je ne vois aucune raison d'utiliser/dev/random
, et cela peut être très cher / lent, ou ralentir d'autres parties du système. (N'hésitez pas à revenir en arrière et à expliquer si cela est vraiment nécessaire.)I
signifie quesizeof(int)
cela peut être moins qu'en4
principe. btw,od -DAn
échoue(2**32-1)
maisod -N4 -tu4 -An
continue de fonctionner.Merci à tous pour vos excellentes réponses. Je me suis retrouvé avec la solution suivante, que je voudrais partager.
Avant d'entrer dans les détails du pourquoi et du comment, voici le tl; dr : mon nouveau script brillant :-)
Enregistrez cela dans
~/bin/rand
et vous avez à votre disposition une fonction aléatoire douce dans bash qui peut échantillonner un entier dans une plage arbitraire donnée. La plage peut contenir des nombres entiers négatifs et positifs et peut avoir une longueur maximale de 2 60 -1:Toutes les idées des autres répondeurs étaient excellentes. Les réponses de terdon , JF Sebastian et jimmij ont utilisé des outils externes pour effectuer la tâche de manière simple et efficace. Cependant, j'ai préféré une vraie solution bash pour une portabilité maximale, et peut-être un peu, simplement par amour pour bash;)
Réponses de Ramesh et l0b0 utilisées
/dev/urandom
ou/dev/random
combinées avecod
. C'est bien, cependant, leurs approches avaient l'inconvénient de ne pouvoir échantillonner que des nombres entiers aléatoires compris entre 0 et 2 8n -1 pour certains n, car cette méthode échantillonne les octets, c'est-à-dire les chaînes de bits de longueur 8. Ce sont de très gros sauts avec croissant n.Enfin, la réponse de Falco décrit l'idée générale de la façon dont cela pourrait être fait pour des plages arbitraires (pas seulement des puissances de deux). Fondamentalement, pour une plage donnée
{0..max}
, nous pouvons déterminer quelle est la puissance suivante de deux, c'est-à-dire exactement combien de bits sont nécessaires pour représentermax
comme une chaîne de bits. Ensuite, nous pouvons échantillonner juste autant de bits et voir si cet enregistrement, en tant qu'entier, est supérieur àmax
. Si oui, répétez. Puisque nous échantillonnons autant de bits que nécessaire pour représentermax
, chaque itération a une probabilité supérieure ou égale à 50% de réussite (50% dans le pire des cas, 100% dans le meilleur des cas). C'est donc très efficace.Mon script est essentiellement une implémentation concrète de la réponse de Falco, écrite en bash pur et très efficace car elle utilise les opérations bit à bit intégrées de bash pour échantillonner des chaînes de bits de la longueur souhaitée. Il honore en outre une idée d' Eliah Kagan qui suggère d'utiliser la
$RANDOM
variable intégrée en concaténant les chaînes de bits résultant des invocations répétées de$RANDOM
. J'ai en fait implémenté à la fois les possibilités d'utilisation/dev/urandom
et$RANDOM
. Par défaut, le script ci-dessus utilise$RANDOM
. (Et ok, si vous utilisez,/dev/urandom
nous avons besoin de od et tr , mais ceux-ci sont soutenus par POSIX.)Alors, comment ça marche?
Avant d'entrer dans le détail, deux observations:
Il s'avère que bash ne peut pas gérer des entiers supérieurs à 2 63 -1. Voir par vous-même:
Il semblerait que bash utilise en interne des entiers signés 64 bits pour stocker des entiers. Donc, à 2 63, il «s'enroule» et nous obtenons un entier négatif. Nous ne pouvons donc pas espérer obtenir une plage supérieure à 2 63 -1 avec la fonction aléatoire que nous utilisons. Bash ne peut tout simplement pas le gérer.
Chaque fois que nous voulons échantillonner une valeur dans une plage arbitraire entre
min
etmax
avec éventuellementmin != 0
, nous pouvons simplement échantillonner une valeur entre0
et à lamax-min
place, puis ajoutermin
au résultat final. Cela fonctionne même simin
et peut-être aussimax
sont négatifs , mais nous devons faire attention à échantillonner une valeur entre0
et la valeur absolue demax-min
. Ainsi, nous pouvons nous concentrer sur la façon d'échantillonner une valeur aléatoire entre0
et un entier positif arbitrairemax
. Le reste est facile.Étape 1: déterminer le nombre de bits nécessaires pour représenter un entier (le logarithme)
Donc, pour une valeur donnée
max
, nous voulons savoir exactement combien de bits sont nécessaires pour la représenter comme une chaîne de bits. C'est ainsi que plus tard, nous pouvons échantillonner au hasard seulement autant de bits que nécessaire, ce qui rend le script si efficace.Voyons voir. Comme avec les
n
bits, nous pouvons représenter jusqu'à la valeur 2 n -1, alors le nombren
de bits nécessaires pour représenter une valeur arbitrairex
est plafond (log 2 (x + 1)). Donc, nous avons besoin d'une fonction pour calculer le plafond d'un logarithme à la base 2. Elle est plutôt explicite:Nous avons besoin de la condition,
n>0
donc si elle devient trop grande, s'enroule et devient négative, la boucle est garantie de se terminer.Étape 2: échantillonner une chaîne binaire aléatoire de longueur
n
Les idées les plus portables sont soit d'utiliser
/dev/urandom
(ou même/dev/random
s'il y a une bonne raison) soit la$RANDOM
variable intégrée de bash . Voyons d'abord comment le faire$RANDOM
.Option A: utilisation
$RANDOM
Cela utilise l' idée mentionnée par Eliah Kagan. Fondamentalement, puisque
$RANDOM
échantillonne un entier de 15 bits, nous pouvons utiliser$((RANDOM<<15|RANDOM))
pour échantillonner un entier de 30 bits. Cela signifie, décaler une première invocation de$RANDOM
15 bits vers la gauche et appliquer au niveau du bit ou avec une seconde invocation de$RANDOM
, concaténant efficacement deux chaînes de bits échantillonnées indépendamment (ou au moins aussi indépendantes que le bash intégré de$RANDOM
va).Nous pouvons répéter ceci pour obtenir un entier de 45 bits ou 60 bits. Après cela, bash ne peut plus le gérer, mais cela signifie que nous pouvons facilement échantillonner une valeur aléatoire entre 0 et 2 60 -1. Donc, pour échantillonner un entier de n bits, nous répétons la procédure jusqu'à ce que notre chaîne de bits aléatoire, dont la longueur augmente par pas de 15 bits, ait une longueur supérieure ou égale à n. Enfin, nous coupons les bits qui sont trop en décalant de façon appropriée au niveau du bit vers la droite, et nous nous retrouvons avec un entier aléatoire de n bits.
Option B: utilisation
/dev/urandom
Alternativement, nous pouvons utiliser
od
et/dev/urandom
pour échantillonner un entier de n bits.od
lira des octets, c'est-à-dire des chaînes de bits de longueur 8. De la même manière que dans la méthode précédente, nous échantillonnons juste autant d'octets que le nombre équivalent de bits échantillonnés est supérieur ou égal à n, et coupons les bits qui sont trop.Le plus petit nombre d'octets nécessaires pour obtenir au moins n bits est le plus petit multiple de 8 supérieur ou égal à n, c'est-à-dire étage ((n + 7) / 8).
Cela ne fonctionne que jusqu'à 56 bits. L'échantillonnage d'un octet supplémentaire nous donnerait un entier 64 bits, c'est-à-dire une valeur jusqu'à 2 64 -1, que bash ne peut pas gérer.
Assembler les morceaux: Obtenez des nombres entiers aléatoires dans des plages arbitraires
Nous pouvons
n
maintenant échantillonner des chaînes de bits, mais nous voulons échantillonner des entiers dans une plage de0
àmax
, uniformément au hasard , oùmax
peut être arbitraire, pas nécessairement une puissance de deux. (Nous ne pouvons pas utiliser modulo car cela crée un biais.)Tout ce pourquoi nous avons essayé si dur d'échantillonner autant de bits que nécessaire pour représenter la valeur
max
, c'est que nous pouvons maintenant utiliser en toute sécurité (et efficacement) une boucle pour échantillonner de manière répétée unen
chaîne de bits -bit jusqu'à ce que nous échantillonnions une valeur qui est inférieure ou égal àmax
. Dans le pire des cas (max
est une puissance de deux), chaque itération se termine avec une probabilité de 50%, et dans le meilleur des cas (max
est une puissance de deux moins un), la première itération se termine avec certitude.Envelopper les choses
Enfin, nous voulons échantillonner des entiers entre
min
etmax
, oùmin
etmax
peut être arbitraire, voire négatif. Comme mentionné précédemment, cela est désormais trivial.Mettons tout cela dans un script bash. Faites des analyses d'arguments ... Nous voulons deux arguments
min
etmax
, ou un seul argumentmax
, parmin
défaut0
.... et, enfin, pour échantillonner uniformément au hasard une valeur entre
min
etmax
, nous échantillonnons un entier aléatoire entre0
et la valeur absolue demax-min
, et ajoutonsmin
au résultat final. :-)Inspiré par cela , je pourrais essayer d'utiliser dieharder pour tester et comparer ce PRNG, et mettre mes résultats ici. :-)
la source
sizeof(int) == 8
(64 bits) en raison de--format=u
random.Random
classe utilise 53bit? générateur pour renvoyer de grands nombres aléatoires arbitraires (invocations multiples),random.SystemRandom
fait de même en utilisantos.urandom()
qui peut être implémenté en utilisant/dev/urandom
.--format=u8
alors je coder en dur l'hypothèsesizeof(int)==8
. D'un autre côté, si utilisation--format=uL
il n'y a pas de problème: je ne pense pas qu'il existe une plate-forme qui a des entiers 64 bits mais définit toujours les entiers longs comme quelque chose de plus bas. Donc, fondamentalement, je dirais qu'il--format=uL
permet plus de flexibilité. Quelles sont vos pensées?long long
peut y avoir 64 bits tandis que int = long = 32 bits sur certaines plates-formes. Vous ne devez pas revendiquer une plage de 0..2 ** 60 si vous ne pouvez pas la garantir sur toutes les plates-formes. D'un autre côté, bash pourrait ne pas prendre en charge cette plage elle-même sur de telles plates-formes (je ne sais pas, peut-être qu'elle utilise maxint_t, puis u8 est plus correct si vous souhaitez affirmer la plage fixe (od
ne prend pas en charge la spécification de maxint si la plage de votre est quelle que soit la plage de bash dépendante de la plate-forme?). Si la plage de bash dépend de la taille de long, alors uL pourrait être plus approprié). Voulez-vous la gamme complète prise en charge par bash sur tous les systèmes d'exploitation ou une plage fixe?Peut-il être zsh?
Vous pouvez également utiliser des semences avec
rand48(seed)
. Voirman zshmodules
etman 3 erand48
pour une description détaillée si vous êtes intéressé.la source
python
est disponible sur Redhat, sur les systèmes basés sur Debian.la source
Si vous voulez un nombre de 0 à (2 ^ n) -1 où n mod 8 = 0, vous pouvez simplement obtenir n / 8 octets
/dev/random
. Par exemple, pour obtenir la représentation décimale d'un aléatoire,int
vous pouvez:Si vous voulez prendre seulement n bits, vous pouvez d'abord prendre des octets de plafond (n / 8) et passer à droite à la quantité souhaitée. Par exemple, si vous voulez 15 bits:
Si vous êtes absolument sûr que vous ne vous souciez pas de la qualité du caractère aléatoire et que vous souhaitez garantir un temps d'exécution minimal, vous pouvez utiliser à la
/dev/urandom
place de/dev/random
. Assurez-vous de savoir ce que vous faites avant d'utiliser/dev/urandom
!la source
n
des octets aléatoires/dev/urandom
et formatez à l'aide deod
. Similaire dans l'esprit que cette réponse . Les deux sont tout aussi bons :) Bien que les deux aient l'inconvénient d'avoir une plage fixe de 0 à 2 ^ (n * 8) -1 bits, où n est le nombre d'octets. Je préférerais une méthode pour une plage arbitraire , jusqu'à 2 ^ 32-1, mais aussi quelque chose de plus bas. Cela crée une difficulté de biais./dev/urandom
place de/dev/random
- je ne vois aucune raison d'utiliser/dev/random
, et cela peut être très cher / lent ou ralentir d'autres parties du système. (N'hésitez pas à revenir en arrière et à expliquer si cela est vraiment nécessaire.)/dev/urandom
résultats sont bien pires/dev/random
que l'urandom n'est pas utilisable dans la plupart des cas. Une fois/dev/urandom
est initialisé (au début du système); ses résultats sont aussi bons que/dev/random
pour presque toutes les applications sous Linux. Sur certains systèmes, aléatoire et urandom sont identiques.--format=u
devrait être remplacé par--format=u4
carsizeof(int)
peut être inférieur4
à la théorie./dev/random
et ne/dev/urandom
sont pas satisfaisants, et que "Linux devrait ajouter un RNG sécurisé qui bloque jusqu'à ce qu'il ait collecté l'entropie de semences adéquate et se comporte ensuite commeurandom
."En supposant que vous ne vous opposez pas à l'utilisation d'outils externes, cela devrait répondre à vos besoins:
Il utilise la
rand
fonction de perl qui prend une limite supérieure comme paramètre. Vous pouvez le régler à votre guise. La proximité de ce phénomène avec le vrai hasard dans la définition mathématique abstraite dépasse le cadre de ce site, mais cela devrait être correct, sauf si vous en avez besoin pour un cryptage extrêmement sensible ou similaire. Peut-être même là-bas, mais je ne m'aventurerai pas.la source
1^32-1
mais vous devez le modifier pour un plus grand nombre.Vous devriez obtenir le plus proche (2 ^ X) -1 égal ou râpe que votre maximum souhaité et obtenir le nombre de bits. Ensuite, il suffit d'appeler / dev / random plusieurs fois et d'ajouter tous les bits ensemble jusqu'à ce que vous en ayez assez, en tronquant tous les bits qui sont trop. Si le nombre résultant est supérieur à votre répétition max. Dans le pire des cas, vous avez plus de 50% de chances d'obtenir un nombre aléatoire inférieur à votre maximum, donc (dans ce pire cas), vous prendrez deux appels en moyenne.
la source
/dev/urandom
, mais dans les deux réponses , il est toujours un multiple de 8 bits. Tronquer les bits qui sont trop pour les plages inférieures avant de formater en décimal avecod
est une bonne idée pour améliorer l'efficacité, car la boucle n'a qu'un nombre attendu de 2 itérations, comme vous l'expliquez bien. Ceci, combiné avec l'une ou l'autre des réponses mentionnées, est probablement la voie à suivre.Votre réponse est intéressante mais assez longue.
Si vous voulez des nombres arbitrairement grands, vous pouvez joindre plusieurs nombres aléatoires dans une aide:
Si le problème est un biais, supprimez-le.
Joindre ces fonctions ensemble
la source