Quiconque essaie de produire des nombres aléatoires par des moyens déterministes est bien évidemment en état de péché.
--John von Neumann
$RANDOM est une fonction interne de Bash (pas une constante) qui renvoie un entier pseudo-aléatoire [46] dans l'intervalle 0 - 32767. Il ne faut pas l'utiliser pour générer une clé de chiffrement.
Exemple 9.11. Générer des nombres aléatoires
#!/bin/bash # $RANDOM renvoie un entier différent à chaque appel. # Échelle : 0 - 32767 (entier signé sur 16 bits). NBMAX=10 index=1 echo echo "$NBMAX nombres aléatoires :" echo "-----------------" while [ "$index" -le $NBMAX ] # Génère 10 ($NBMAX) entiers aléatoires. do nombre=$RANDOM echo $nombre let "index += 1" # Incrémente l'index. done echo "-----------------" # Si vous avez besoin d'un entier aléatoire dans une certaine échelle, utilisez #+ l'opérateur 'modulo'. # Il renvoie le reste d'une division. ECHELLE=500 echo nombre=$RANDOM let "nombre %= $ECHELLE" # ^^ echo "Nombre aléatoire inférieur à $ECHELLE --- $nombre" echo # Si vous avez besoin d'un entier aléatoire supérieur à une borne, alors #+ faites un test pour annuler tous les nombres en dessous de cette borne. PLANCHER=200 nombre=0 #initialise while [ "$nombre" -le $PLANCHER ] do nombre=$RANDOM done echo "Nombre aléatoire supérieur à $PLANCHER --- $nombre" echo # Examinons une alternative simple à la boucle ci-dessus # let "nombre = $RANDOM + $PLANCHER" # Ceci éliminerait la boucle while et s'exécuterait plus rapidement. # Mais, il resterait un problème. Lequel ? # Combine les deux techniques pour récupérer un nombre aléatoire # compris entre deux limites. nombre=0 #initialise while [ "$nombre" -le $PLANCHER ] do nombre=$RANDOM let "nombre %= $ECHELLE" # Ramène $nombre dans $ECHELLE. done echo "Nombre aléatoire compris entre $PLANCHER et $ECHELLE --- $nombre" echo # Génère un choix binaire, c'est-à-dire "vrai" ou "faux". BINAIRE=2 T=1 nombre=$RANDOM let "nombre %= $BINAIRE" # Notez que let "nombre >>= 14" donne une meilleure distribution aléatoire # (les décalages droits enlèvent tout sauf le dernier nombre binaire). if [ "$nombre" -eq $T ] then echo "VRAI" else echo "FAUX" fi echo # Peut générer un lancer de dés SPOTS=6 # Modulo 6 donne une échelle de 0 à 5. # Incrémenter de 1 donne l'échelle désirée, de 1 à 6. # Merci, Paulo Marcel Coelho Aragao, pour cette simplification. die1=0 die2=0 # Serait-il mieux de seulement initialiser SPOTS=7 et de ne pas ajouter 1 ? # Pourquoi ou pourquoi pas ? # Jette chaque dé séparément, et donne ainsi une chance correcte. let "die1 = $RANDOM % $SPOTS +1" # Le premier. let "die2 = $RANDOM % $SPOTS +1" # Et le second. # Quelle opération arithmétique ci-dessus a la plus grande précédence # le modulo (%) ou l'addition (+) ? let "throw = $die1 + $die2" echo "Throw of the dice = $throw" echo exit 0
Exemple 9.12. Piocher une carte au hasard dans un tas
#!/bin/bash # pick-card.sh # Ceci est un exemple pour choisir au hasard des éléments d'un tableau. # Prenez une carte, n'importe quelle carte. Suites="Carreau Pique Coeur Trefle" Denominations="2 3 4 5 6 7 8 9 10 Valet Dame Roi As" # Notez que le contenu de la variable continue sur plusieurs lignes. suite=($Suites) # Lire dans une variable de type tableau. denomination=($Denominations) num_suites=${#suite[*]} # Compter le nombre d'éléments. num_denominations=${#denomination[*]} echo -n "${denomination[$((RANDOM%num_denominations))]} of " echo ${suite[$((RANDOM%num_suites))]} # $bozo sh pick-cards.sh # Valet de trèfle # Merci, "jipe", pour m'avoir indiqué cette utilisation de $RANDOM. exit 0
Exemple 9.13. Simulation « Brownian Motion »
#!/bin/bash # brownian.sh # Author: Mendel Cooper # Reldate: 10/26/07 # License: GPL3 # ---------------------------------------------------------------- # This script models Brownian motion: #+ the random wanderings of tiny particles in a fluid, #+ as they are buffeted by random currents and collisions. #+ This is colloquially known as the "Drunkard's Walk." # It can also be considered as a stripped-down simulation of a #+ Galton Board, a slanted board with a pattern of pegs, #+ down which rolls a succession of marbles, one at a time. #+ At the bottom is a row of slots or catch basins in which #+ the marbles come to rest at the end of their journey. # Think of it as a kind of bare-bones Pachinko game. # As you see by running the script, #+ most of the marbles cluster around the center slot. #+ This is consistent with the expected binomial distribution. # As a Galton Board simulation, the script #+ disregards such parameters as #+ board tilt-angle, rolling friction of the marbles, #+ angles of impact, and elasticity of the pegs. # To what extent does this affect the accuracy of the simulation? # ---------------------------------------------------------------- PASSES=500 # Number of particle interactions / marbles. ROWS=10 # Number of "collisions" (or horiz. peg rows). RANGE=3 # 0 - 2 output range from $RANDOM. POS=0 # Left/right position. RANDOM=$$ # Seeds the random number generator from PID #+ of script. declare -a Slots # Array holding cumulative results of passes. NUMSLOTS=21 # Number of slots at bottom of board. Initialize_Slots () { # Zero out all elements of the array. for i in $( seq $NUMSLOTS ) do Slots[$i]=0 done echo # Blank line at beginning of run. } Show_Slots () { echo; echo echo -n " " for i in $( seq $NUMSLOTS ) # Pretty-print array elements. do printf "%3d" ${Slots[$i]} # Allot three spaces per result. done echo # Row of slots: echo " |__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|" echo " ||" echo # Note that if the count within any particular slot exceeds 99, #+ it messes up the display. # Running only(!) 500 passes usually avoids this. } Move () { # Move one unit right / left, or stay put. Move=$RANDOM # How random is $RANDOM? Well, let's see ... let "Move %= RANGE" # Normalize into range of 0 - 2. case "$Move" in 0 ) ;; # Do nothing, i.e., stay in place. 1 ) ((POS--));; # Left. 2 ) ((POS++));; # Right. * ) echo -n "Error ";; # Anomaly! (Should never occur.) esac } Play () { # Single pass (inner loop). i=0 while [ "$i" -lt "$ROWS" ] # One event per row. do Move ((i++)); done SHIFT=11 # Why 11, and not 10? let "POS += $SHIFT" # Shift "zero position" to center. (( Slots[$POS]++ )) # DEBUG: echo $POS # echo -n "$POS " } Run () { # Outer loop. p=0 while [ "$p" -lt "$PASSES" ] do Play (( p++ )) POS=0 # Reset to zero. Why? done } # -------------- # main () Initialize_Slots Run Show_Slots # -------------- exit $? # Exercises: # --------- # 1) Show the results in a vertical bar graph, or as an alternative, #+ a scattergram. # 2) Alter the script to use /dev/urandom instead of $RANDOM. # Will this make the results more random? # 3) Provide some sort of "animation" or graphic output # for each marble played.
Jipe nous a indiqué un autre ensemble de techniques pour générer des nombres aléatoires à l'intérieur d'un intervalle donné.
# Génére des nombres aléatoires entre 6 et 30. rnumber=$((RANDOM%25+6)) # Générer des nombres aléatoires dans le même intervalle de 6 à 30, #+ mais le nombre doit être divisible de façon exacte par 3. rnumber=$(((RANDOM%30/3+1)*3)) # Notez que ceci ne fonctionnera pas tout le temps. # Il échoue si $RANDOM%30 renvoie 0. # Frank Wang suggère l'alternative suivante : rnumber=$(( RANDOM%27/3*3+6 ))
Bill Gradwohl est parvenu à une formule améliorée fonctionnant avec les numéros positifs.
rnumber=$(((RANDOM%(max-min+divisiblePar))/divisiblePar*divisiblePar+min))
Ici, Bill présente une fonction versatile renvoyant un numéro au hasard entre deux valeurs spécifiques.
Exemple 9.14. Un nombre au hasard entre deux valeurs
#!/bin/bash # random-between.sh # Random number between two specified values. # Script by Bill Gradwohl, with minor modifications by the document author. # Corrections in lines 187 and 189 by Anthony Le Clezio. # Used with permission. randomBetween() { # Generates a positive or negative random number #+ between $min and $max #+ and divisible by $divisibleBy. # Gives a "reasonably random" distribution of return values. # # Bill Gradwohl - Oct 1, 2003 syntax() { # Function embedded within function. echo echo "Syntax: randomBetween [min] [max] [multiple]" echo echo -n "Expects up to 3 passed parameters, " echo "but all are completely optional." echo "min is the minimum value" echo "max is the maximum value" echo -n "multiple specifies that the answer must be " echo "a multiple of this value." echo " i.e. answer must be evenly divisible by this number." echo echo "If any value is missing, defaults area supplied as: 0 32767 1" echo -n "Successful completion returns 0, " echo "unsuccessful completion returns" echo "function syntax and 1." echo -n "The answer is returned in the global variable " echo "randomBetweenAnswer" echo -n "Negative values for any passed parameter are " echo "handled correctly." } local min=${1:-0} local max=${2:-32767} local divisibleBy=${3:-1} # Default values assigned, in case parameters not passed to function. local x local spread # Let's make sure the divisibleBy value is positive. [ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy)) # Sanity check. if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o ${min} -eq ${max} ]; then syntax return 1 fi # See if the min and max are reversed. if [ ${min} -gt ${max} ]; then # Swap them. x=${min} min=${max} max=${x} fi # If min is itself not evenly divisible by $divisibleBy, #+ then fix the min to be within range. if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ]; then if [ ${min} -lt 0 ]; then min=$((min/divisibleBy*divisibleBy)) else min=$((((min/divisibleBy)+1)*divisibleBy)) fi fi # If max is itself not evenly divisible by $divisibleBy, #+ then fix the max to be within range. if [ $((max/divisibleBy*divisibleBy)) -ne ${max} ]; then if [ ${max} -lt 0 ]; then max=$((((max/divisibleBy)-1)*divisibleBy)) else max=$((max/divisibleBy*divisibleBy)) fi fi # --------------------------------------------------------------------- # Now, to do the real work. # Note that to get a proper distribution for the end points, #+ the range of random values has to be allowed to go between #+ 0 and abs(max-min)+divisibleBy, not just abs(max-min)+1. # The slight increase will produce the proper distribution for the #+ end points. # Changing the formula to use abs(max-min)+1 will still produce #+ correct answers, but the randomness of those answers is faulty in #+ that the number of times the end points ($min and $max) are returned #+ is considerably lower than when the correct formula is used. # --------------------------------------------------------------------- spread=$((max-min)) # Omair Eshkenazi points out that this test is unnecessary, #+ since max and min have already been switched around. [ ${spread} -lt 0 ] && spread=$((0-spread)) let spread+=divisibleBy randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min)) return 0 # However, Paulo Marcel Coelho Aragao points out that #+ when $max and $min are not divisible by $divisibleBy, #+ the formula fails. # # He suggests instead the following formula: # rnumber = $(((RANDOM%(max-min+1)+min)/divisibleBy*divisibleBy)) } # Let's test the function. min=-14 max=20 divisibleBy=3 # Generate an array of expected answers and check to make sure we get #+ at least one of each answer if we loop long enough. declare -a answer minimum=${min} maximum=${max} if [ $((minimum/divisibleBy*divisibleBy)) -ne ${minimum} ]; then if [ ${minimum} -lt 0 ]; then minimum=$((minimum/divisibleBy*divisibleBy)) else minimum=$((((minimum/divisibleBy)+1)*divisibleBy)) fi fi # If max is itself not evenly divisible by $divisibleBy, #+ then fix the max to be within range. if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; then if [ ${maximum} -lt 0 ]; then maximum=$((((maximum/divisibleBy)-1)*divisibleBy)) else maximum=$((maximum/divisibleBy*divisibleBy)) fi fi # We need to generate only positive array subscripts, #+ so we need a displacement that that will guarantee #+ positive results. disp=$((0-minimum)) for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do answer[i+disp]=0 done # Now loop a large number of times to see what we get. loopIt=1000 # The script author suggests 100000, #+ but that takes a good long while. for ((i=0; i<${loopIt}; ++i)); do # Note that we are specifying min and max in reversed order here to #+ make the function correct for this case. randomBetween ${max} ${min} ${divisibleBy} # Report an error if an answer is unexpected. [ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] \ && echo MIN or MAX error - ${randomBetweenAnswer}! [ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] \ && echo DIVISIBLE BY error - ${randomBetweenAnswer}! # Store the answer away statistically. answer[randomBetweenAnswer+disp]=$((answer[randomBetweenAnswer+disp]+1)) done # Let's check the results for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do [ ${answer[i+disp]} -eq 0 ] \ && echo "We never got an answer of $i." \ || echo "${i} occurred ${answer[i+disp]} times." done exit 0
À quel point $RANDOM est-il aléatoire ? la meilleure façon de le tester est d'écrire un script qui enregistre la suite des nombres « aléatoires » générés par $RANDOM. Faisons tourner $RANDOM plusieurs fois...
Exemple 9.15. Lancement d'un seul dé avec RANDOM
#!/bin/bash # À quel point RANDOM est aléatoire? RANDOM=$$ # Réinitialise le générateur de nombres aléatoires en utilisant #+ le PID du script. PIPS=6 # Un dé a 6 faces. COMPTEURMAX=600# Augmentez ceci si vous n'avez rien de mieux à faire. compteur=0 # Compteur. un=0 # Doit initialiser les comptes à zéro deux=0 # car une variable non initialisée est nulle, et ne vaut pas zéro. trois=0 quatre=0 cinq=0 six=0 Affiche_resultat () { echo echo "un = $un" echo "deux = $deux" echo "trois = $trois" echo "quatre = $quatre" echo "cinq = $cinq" echo "six = $six" echo } mise_a_jour_compteur() { case "$1" in 0) let "un += 1";; # Comme le dé n'a pas de "zéro", ceci correspond à 1. 1) let "deux += 1";; # Et ceci à 2, etc. 2) let "trois += 1";; 3) let "quatre += 1";; 4) let "cinq += 1";; 5) let "six += 1";; esac } echo while [ "$compteur" -lt "$COMPTEURMAX" ] do let "die1 = RANDOM % $PIPS" mise_a_jour_compteur $die1 let "compteur += 1" done Affiche_resultat exit 0 # Les scores devraient être distribués de façon égale en supposant que RANDOM #+ soit correctement aléatoire. # Avec $COMPTEURMAX à 600, tout devrait tourner autour de 100, plus ou moins #+ 20. # # Gardez en tête que RANDOM est un générateur pseudo-aléatoire, # et pas un particulièrement bon. # Le hasard est un sujet profond et complexe. # Des séquences "au hasard" suffisamment longues pourraient exhiber un #+ comportement cahotique et un autre comportement non aléatoire. # Exercice (facile): # ----------------- # Réécrire ce script pour lancer une pièce 1000 fois. # Les choix sont "PILE" ou "FACE".
Comme nous avons vu sur le dernier exemple, il est préférable de réinitialiser le générateur RANDOM à chaque fois qu'il est invoqué. Utiliser le même germe pour RANDOM ne fera que répéter la même série de nombres [47] (ceci reflète le comportement de la fonction C random()).
Exemple 9.16. Réinitialiser RANDOM
#!/bin/bash # seeding-random.sh: Utiliser la variable RANDOM. NBMAX=25 # Combien de nombres à générer. nombres_aleatoires () { compteur=0 while [ "$compteur" -lt "$NBMAX" ] do nombre=$RANDOM echo -n "$nombre " let "compteur += 1" done } echo; echo RANDOM=1 # Initialiser RANDOM met en place le générateur de nombres #+ aléatoires. nombres_aleatoires echo; echo RANDOM=1 # Même élément pour RANDOM... nombres_aleatoires # ...reproduit la même série de nombres. # # Quand est-il utile de dupliquer une série de nombres #+ "aléatoires" ? echo; echo RANDOM=2 # Nouvel essai, mais avec un 'germe' différent... nombres_aleatoires # donne une autre série... echo; echo # RANDOM=$$ initialise RANDOM à partir du PID du script. # Il est aussi possible d'initialiser RANDOM à partir des commandes 'time' et #+ 'date'. # Un peu plus d'amusement... SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }') # Sortie pseudo-aléatoire récupérée de /dev/urandom (fichier périphérique #+ pseudo-aléatoire), #+ puis convertit la ligne en nombres (octal) affichables avec "od". #+ Finalement "awk" récupère un seul nombre pour SEED. RANDOM=$SEED nombres_aleatoires echo; echo exit 0
Le pseudo fichier périphérique /dev/urandom apporte une méthode pour générer des nombres pseudo-aléatoires bien plus « aléatoires » que la variable $RANDOM. dd if=/dev/urandom of=fichier_cible bs=1 count=XX crée un fichier de nombres pseudo-aléatoires bien distribués. Néanmoins, assigner ces nombres à une variable dans un script nécessite un petit travail supplémentaire, tel qu'un filtrage par l'intermédiaire de od (comme dans l'exemple ci-dessus, dans l'Exemple 16.14, « Générer des nombres aléatoires de dix chiffres » et dans Exemple A.36, « Tri d'insertion ») ou même d'envoyer via un tube dans md5sum (voir l'Exemple 36.14, « Un jeu de « courses de chevaux » »).
Il existe encore d'autres moyens pour générer des nombres pseudo aléatoires dans un script. Awk propose une façon agréable de le faire.
Exemple 9.17. Nombres pseudo-aléatoires, en utilisant awk
#!/bin/bash # random2.sh: Renvoie un nombre pseudo-aléatoire compris entre 0 et 1. # Utilise la fonction rand() d'awk. SCRIPTAWK=' { srand(); print rand() } ' # Commande(s) / paramètres passés à awk # Notez que srand() réinitialise le générateur de nombre aléatoire de awk. echo -n "Nombre aléatoire entre 0 et 1 = " echo | awk "$SCRIPTAWK" # Que se passe-t-il si vous oubliez le 'echo' ? exit 0 # Exercices : # ---------- # 1) En utilisant une construction boucle, affichez 10 nombres aléatoires # différents. # (Astuce : vous devez réinitialiser la fonction "srand()" avec une donnée # différente à chaque tour de la boucle. Qu'arrive-t'il si vous échouez à le # faire ?) # 2) En utilisant un multiplicateur entier comme facteur d'échelle, générez des # nombres aléatoires compris entre 10 et 100. # 3) De même que l'exercice #2, ci-dessus, mais en générant des nombres # aléatoires entiers cette fois.
La commande date tend elle-même à générer des séquences d'entiers pseudo-aléatoires.
[46] Le véritable « hasard », si tant est qu'il puisse exister, peut seulement être trouvé dans certains phénomènes naturels qu'on ne comprend qu'en partie, tels que la destruction radioactive. Les ordinateurs ne font que simuler le hasard, et les séquences générées par ordinateur de nombres « aléatoires » sont de ce fait appelées pseudo-aléatoires.
[47] La graine d'une série de nombres pseudo-aléatoires générés par un ordinateur peut être considérée comme une étiquette d'identification. Par exemple, vous pouvez désigner les séries pseudo-aléatoires de graine 23 comme les séries #23.
Une propriété d'une série de nombres pseudo-aléatoires est la longueur du cycle avant qu'il ne commence à se répéter. Un bon générateur pseudo-aléatoire produira des séries avec de très longs cycles.