$RANDOM est une fonction interne Bash (pas une constante) renvoyant un entier pseudo-aléatoire [39] dans l'intervalle 0 - 32767. Il ne devrait pas être utilisé pour générer une clé de chiffrement.
Exemple 9.26. 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.27. 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.28. 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 -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 } 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?
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.29. Un nombre au hasard entre deux valeurs
#!/bin/bash # random-between.sh # Nombre aléatoire entre deux valeurs spécifiées. # Script par Bill Gradwohl, avec des modifications mineures par l'auteur du document. # Utilisé avec les droits. aleatoireEntre() { # Génère un numéro aléatoire positif ou négatif #+ entre $min et $max #+ et divisible par $divisiblePar. # Donne une distribution "raisonnablement aléatoire" des valeurs renvoyées. # # Bill Gradwohl - 1er octobre 2003 syntax() { # Fonction imbriquée dans la fonction. echo echo "Syntax: aleatoireEntre [min] [max] [multiple]" echo echo "Attend au plus trois paramètres mais tous sont complètement optionnels." echo "min est la valeur minimale" echo "max est la valeur maximale" echo "multiple spécifie que la réponse est un multiple de cette valeur." echo " c'est-à-dire qu'une réponse doit être divisible de manière entière" echo " par ce numéro." echo echo "Si cette valeur manque, l'aire par défaut supportée est : 0 32767 1" echo "Un résultat avec succès renvoie 0. Sinon, la syntaxe de la fonction" echo "est renvoyée avec un 1." echo "La réponse est renvoyée dans la variable globale aleatoireEntreAnswer" echo "Les valeurs négatives pour tout paramètre passé sont gérées correctement." } local min=${1:-0} local max=${2:-32767} local divisiblePar=${3:-1} # Valeurs par défaut affectées, au cas où les paramètres ne sont pas passés à la #+ fonction. local x local spread # Assurez-vous que la valeur divisiblePar est positive. [ ${divisiblePar} -lt 0 ] && divisiblePar=$((0-divisiblePar)) # Vérification. if [ $# -gt 3 -o ${divisiblePar} -eq 0 -o ${min} -eq ${max} ]; then syntax return 1 fi # Vérifiez si min et max ne sont pas inversés. if [ ${min} -gt ${max} ]; then # Les inversez. x=${min} min=${max} max=${x} fi # Si min est lui-même non divisible par $divisiblePar, #+ alors corrigez le min pour être à l'échelle. if [ $((min/divisiblePar*divisiblePar)) -ne ${min} ]; then if [ ${min} -lt 0 ]; then min=$((min/divisiblePar*divisiblePar)) else min=$((((min/divisiblePar)+1)*divisiblePar)) fi fi # Si max est lui-même non divisible par $divisiblePar, #+ alors corrigez le max pour être à l'échelle. if [ $((max/divisiblePar*divisiblePar)) -ne ${max} ]; then if [ ${max} -lt 0 ]; then max=$((((max/divisiblePar)-1)*divisiblePar)) else max=$((max/divisiblePar*divisiblePar)) fi fi # --------------------------------------------------------------------- # Maintenant, pour faire le vrai travail. # Notez que pour obtenir une distribution correcte pour les points finaux, #+ l'échelle des valeurs aléatoires doit être autorisée pour aller entre 0 et #+ abs(max-min)+divisiblePar, et non pas seulement abs(max-min)+1. # La légère augmentation produira une distribution correcte des points finaux. # Changer la formule pour utiliser abs(max-min)+1 produira toujours des réponses #+ correctes mais le côté aléatoire des réponses est erroné dans le fait que le #+ nombre de fois où les points finaux ($min et $max) sont renvoyés est #+ considérablement plus petit que lorsque la formule correcte est utilisée. # --------------------------------------------------------------------- spread=$((max-min)) # Omair Eshkenazi indique que ce test n'est pas nécessaire #+ car max et min ont déjà été basculés. [ ${spread} -lt 0 ] && spread=$((0-spread)) let spread+=divisiblePar aleatoireEntreAnswer=$(((RANDOM%spread)/divisiblePar*divisiblePar+min)) return 0 # Néanmoins, Paulo Marcel Coelho Aragao indique que #+ quand $max et $min ne sont pas divisibles par $divisiblePar, #+ la formule échoue. # # Il suggère à la place la formule suivante : # rnumber = $(((RANDOM%(max-min+1)+min)/divisiblePar*divisiblePar)) } # Testons la fonction. min=-14 max=20 divisiblePar=3 # Génère un tableau des réponses attendues et vérifie pour s'assurer que nous obtenons #+ au moins une réponse si nous bouclons assez longtemps. declare -a reponse minimum=${min} maximum=${max} if [ $((minimum/divisiblePar*divisiblePar)) -ne ${minimum} ]; then if [ ${minimum} -lt 0 ]; then minimum=$((minimum/divisiblePar*divisiblePar)) else minimum=$((((minimum/divisiblePar)+1)*divisiblePar)) fi fi # Si max est lui-même non divisible par $divisiblePar, #+ alors corrigez le max pour être à l'échelle. if [ $((maximum/divisiblePar*divisiblePar)) -ne ${maximum} ]; then if [ ${maximum} -lt 0 ]; then maximum=$((((maximum/divisiblePar)-1)*divisiblePar)) else maximum=$((maximum/divisiblePar*divisiblePar)) fi fi # Nous avons besoin de générer seulement les sous-scripts de tableaux positifs, #+ donc nous avons besoin d'un déplacement qui nous garantie des résultats positifs. deplacement=$((0-minimum)) for ((i=${minimum}; i<=${maximum}; i+=divisiblePar)); do reponse[i+deplacement]=0 done # Maintenant, bouclons avec un gros nombre de fois pour voir ce que nous obtenons. loopIt=1000 # L'auteur du script suggère 100000, #+ mais cela prend beaucoup de temps. for ((i=0; i<${loopIt}; ++i)); do # Notez que nous spécifions min et max en ordre inverse ici pour s'assurer que les #+ fonctions sont correctes dans ce cas. aleatoireEntre ${max} ${min} ${divisiblePar} # Rapporte une erreur si une réponse est inattendue. [ ${aleatoireEntreAnswer} -lt ${min} -o ${aleatoireEntreAnswer} -gt ${max} ] \ && echo MIN or MAX error - ${aleatoireEntreAnswer}! [ $((aleatoireEntreAnswer%${divisiblePar})) -ne 0 ] \ && echo DIVISIBLE BY error - ${aleatoireEntreAnswer}! # Stocke la réponse statistiquement. reponse[aleatoireEntreAnswer+deplacement]=$((reponse[aleatoireEntreAnswer+deplacement]+1)) done # Vérifions les résultats. for ((i=${minimum}; i<=${maximum}; i+=divisiblePar)); do [ ${reponse[i+deplacement]} -eq 0 ] \ && echo "We never got an reponse of $i." \ || echo "${i} occurred ${reponse[i+deplacement]} 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.30. 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 [40] (ceci reflète le comportement de la fonction C random()).
Exemple 9.31. 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 15.14, « Générer des nombres aléatoires de dix chiffres » et dans Exemple A.38, « Tri d'insertion ») ou tel que l'utilisation de dd (voir l'Exemple 15.59, « Effacer les fichiers de façon sûre ») ou même d'envoyer via un tube dans md5sum (voir l'Exemple 33.14, « Un jeu de « courses de chevaux » »).
Il existe aussi 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.32. 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.
[39] Un vrai « hasard », si tant est qu'il puisse exister, peut seulement être trouvé dans certains phénomènes naturels compris partiellement tels que la destruction radioactive. Les ordinateurs simulent le hasard et les séquences générées par ordinateur de nombres « aléatoires » sont du coup appelés pseudo-aléatoires.
[40] La graine d'une série de nombres pseudo-aléatoires générés par un ordinateur peut être considérée comme un label d'identification. Par exemple, pensez à la série pseudo-aléatoire avec une graine de 23 comme la série #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.