Guida avanzata di scripting Bash: Un'approfondita esplorazione dell'arte dello scripting di shell | ||
---|---|---|
Indietro | Capitolo 9. Variabili riviste | Avanti |
$RANDOM
è una funzione interna di Bash (non
una costante) che restituisce un intero pseudocasuale
[1]
nell'intervallo 0 - 32767. Non dovrebbe essere
utilizzata per generare una chiave di cifratura.
Esempio 9-25. Generare numeri casuali
#!/bin/bash # $RANDOM restituisce un intero casuale diverso ad ogni chiamata. # Intervallo nominale: 0 - 32767 (intero con segno di 16-bit). NUM_MASSIMO=10 contatore=1 echo echo "$NUM_MASSIMO numeri casuali:" echo "-----------------" while [ "$contatore" -le $NUM_MASSIMO ] # Genera 10 ($NUM_MASSIMO) #+ interi casuali. do numero=$RANDOM echo $numero let "contatore += 1" # Incrementa il contatore. done echo "-----------------" # Se è necessario un intero casuale entro un dato intervallo, si usa #+ l'operatore 'modulo', che restituisce il resto di una divisione. INTERVALLO=500 echo numero=$RANDOM let "numero %= $INTERVALLO" # ^^ echo "Il numero casuale è inferiore a $INTERVALLO --- $numero" echo # Se è necessario un intero casuale non inferiore a un certo limite, #+ occorre impostare una verifica per eliminare tutti i numeri al di #+ sotto di tale limite. LIMITE_INFERIORE=200 numero=0 # inizializzazione while [ "$numero" -le $LIMITE_INFERIORE ] do numero=$RANDOM done echo "Numero casuale maggiore di $LIMITE_INFERIORE --- $numero" echo # Prendiamo in considerazione una semplice alternativa al ciclo precedente, #+ in particolare # let "numero = $RANDOM + $LIMITE_INFERIORE" # Elimineremmo il ciclo while e l'esecuzione sarebbe più veloce. # Ma ci sarebbe un problema. Quale? # Combiniamo le due tecniche precedenti per ottenere un #+ numero compreso tra due limiti. numero=0 # inizializzazione while [ "$numero" -le $LIMITE_INFERIORE ] do numero=$RANDOM let "numero %= $INTERVALLO" # Riduce $numero entro $INTERVALLO. done echo "Numero casuale tra $LIMITE_INFERIORE e $INTERVALLO --- $numero" echo # Genera una scelta binaria, vale a dire, il valore "vero" o "falso". BINARIO=2 T=1 numero=$RANDOM let "numero %= $BINARIO" # Da notare che let "numero >>= 14" dà una migliore distribuzione casuale #+ (lo scorrimento a destra elimina tutto tranne l'ultima cifra binaria). if [ "$numero" -eq $T ] then echo "VERO" else echo "FALSO" fi echo # Si può simulare il lancio dei dadi. MODULO=6 # Modulo 6 per un intervallo 0 - 5. # Aumentandolo di 1 si ottiene il desiderato intervallo 1 - 6. # Grazie a Paulo Marcel Coelho Aragao per la semplificazione. dado1=0 dado2=0 # Sarebbe stato meglio impostare semplicemente MODULO=7 e non aggiungere 1? # Perché o perché no? # Si lancia ciascun dado separatamente in modo da ottenere la corretta #+ probabilità. let "dado1 = $RANDOM % $MODULO +1" # Lancio del primo dado. let "dado2 = $RANDOM % $MODULO +1" # Lancio del secondo dado. # Quale, tra le precedenti operazioni aritmetiche, ha la precedenza -- #+ modulo (%) o addizione (+)? let "punteggio = $dado1 + $dado2" echo "Lancio dei dadi = $punteggio" echo exit 0 |
Esempio 9-26. Scegliere una carta a caso dal mazzo
#!/bin/bash # pick-card.sh # Esempio di scelta a caso di elementi di un array. # Sceglie una carta, una qualsiasi. Semi="Fiori Quadri Cuori Picche" Denominazioni="2 3 4 5 6 7 8 9 10 Fante Donna Re Asso" # Notate le variabili elencate su più righe. seme=($Semi) # Inizializza l'array. denominazione=($Denominazioni) num_semi=${#seme[*]} # Conta gli elementi dell'array. num_denominazioni=${#denominazione[*]} echo -n "${denominazione[$((RANDOM%num_denominazioni))]} di " echo ${seme[$((RANDOM%num_semi))]} # $bozo sh pick-cards.sh # Fante di Fiori # Grazie, "jipe," per aver puntualizzato quest'uso di $RANDOM. exit 0 |
Jipe ha evidenziato una serie di tecniche per generare numeri casuali in un intervallo dato.
# Generare un numero casuale compreso tra 6 e 30. numeroc=$((RANDOM%25+6)) # Generare un numero casuale, sempre nell'intervallo 6 - 30, #+ ma che deve essere divisibile per 3. numeroc=$(((RANDOM%30/3+1)*3)) # È da notare che questo non sempre funziona. # Fallisce quando $RANDOM%30 restituisce 0. # Frank Wang suggerisce la seguente alternativa: numeroc=$(( RANDOM%27/3*3+6 )) |
Bill Gradwohl ha elaborato una formula più perfezionata che funziona con i numeri positivi.
numeroc=$(((RANDOM%(max-min+divisibilePer))/divisibilePer*divisibilePer+min)) |
Qui Bill presenta una versatile funzione che restituisce un numero casuale compreso tra due valori specificati.
Esempio 9-27. Numero casuale in un intervallo dato
#!/bin/bash # random-between.sh # Numero casuale compreso tra due valori specificati. # Script di Bill Gradwohl, con modifiche di secondaria importanza fatte #+ dall'autore del libro. # Utilizzato con il permesso dell'autore. interCasuale() { # Genera un numero casuale positivo o negativo #+ compreso tra $min e $max #+ e divisibile per $divisibilePer. # Restituisce una distribuzione di valori "ragionevolmente casuale". # # Bill Gradwohl - 1 Ott, 2003 sintassi() { # Funzione all'interno di una funzione. echo echo "Sintassi: interCasuale [min] [max] [multiplo]" echo echo "Si aspetta che vengano passati fino a 3 parametri," echo "tutti però opzionali." echo "min è il valore minimo" echo "max è il valore massimo" echo "multiplo specifica che il numero generato deve essere un" echo "multiplo di questo valore." echo " cioè divisibile esattamente per questo numero." echo echo "Se si omette qualche valore, vengono usati" echo "quelli preimpostati: 0 32767 1" echo "L'esecuzione senza errori restituisce 0, altrimenti viene" echo "richiamata la funzione sintassi e restituito 1." echo "Il numero generato viene restituito nella variabile globale" echo "interCasualeNum" echo "Valori negativi passati come parametri vengono gestiti" echo "anch'essi correttamente." } local min=${1:-0} local max=${2:-32767} local divisibilePer=${3:-1} # Assegnazione dei valori preimpostati, nel caso di mancato passaggio #+ dei parametri alla funzione. local x local intervallo # Verifica che il valore di divisibilePer sia positivo. [ ${divisibilePer} -lt 0 ] && divisibilePer=$((0-divisibilePer)) # Controllo di sicurezza. if [ $# -gt 3 -o ${divisibilePer} -eq 0 -o ${min} -eq ${max} ]; then sintassi return 1 fi # Verifica se min e max sono scambiati. if [ ${min} -gt ${max} ]; then # Li scambia. x=${min} min=${max} max=${x} fi # Se min non è esattamente divisibile per $divisibilePer, #+ viene ricalcolato. if [ $((min/divisibilePer*divisibilePer)) -ne ${min} ]; then if [ ${min} -lt 0 ]; then min=$((min/divisibilePer*divisibilePer)) else min=$((((min/divisibilePer)+1)*divisibilePer)) fi fi # Se max non è esattamente divisibile per $divisibilePer, #+ viene ricalcolato. if [ $((max/divisibilePer*divisibilePer)) -ne ${max} ]; then if [ ${max} -lt 0 ]; then max=$((((max/divisibilePer)-1)*divisibilePer)) else max=$((max/divisibilePer*divisibilePer)) fi fi # ----------------------------------------------------------------------- # Ora il lavoro vero. # E' da notare che per ottenere una corretta distribuzione dei valori #+ estremi, si deve agire su un intervallo che va da 0 a #+ abs(max-min)+divisibilePer, non semplicemente abs(max-min)+1. # Il leggero incremento produrrà la giusta distribuzione per i #+ valori limite. # Se si cambia la formula e si usa abs(max-min)+1 si otterranno ancora #+ dei risultati corretti, ma la loro casualità sarà falsata #+ dal fatto che il numero di volte in cui verranno restituiti gli estremi #+ ($min e $max) sarà considerevolmente inferiore a quella ottenuta #+ usando la formula corretta. # ----------------------------------------------------------------------- intervallo=$((max-min)) # Omair Eshkenazi sottolinea che la verifica seguente non è necessaria, #+ perché max e min sono già stati scambiati. [ ${intervallo} -lt 0 ] && intervallo=$((0-intervallo)) let intervallo+=divisibilePer interCasualeNum=$(((RANDOM%intervallo)/divisibilePer*divisibilePer+min)) return 0 # Tuttavia, Paulo Marcel Coelho Aragao sottolinea che #+ quando $max e $min non sono divisibili per $divisibilePer, #+ la formula sbaglia. # # Suggerisce invece la seguente: # numeroc = $(((RANDOM%(max-min+1)+min)/divisibilePer*divisibilePer)) } # Verifichiamo la funzione. min=-14 max=20 divisibilePer=3 # Genera un array e controlla che si sia ottenuto almeno uno dei risultati #+ possibili, se si effettua un numero sufficiente di tentativi. declare -a risultati minimo=${min} massimo=${max} if [ $((minimo/divisibilePer*divisibilePer)) -ne ${minimo} ]; then if [ ${minimo} -lt 0 ]; then minimo=$((minimo/divisibilePer*divisibilePer)) else minimo=$((((minimo/divisibilePer)+1)*divisibilePer)) fi fi # Se max non è esattamente divisibile per $divisibilePer, #+ viene ricalcolato. if [ $((massimo/divisibilePer*divisibilePer)) -ne ${massimo} ]; then if [ ${massimo} -lt 0 ]; then massimo=$((((massimo/divisibilePer)-1)*divisibilePer)) else massimo=$((massimo/divisibilePer*divisibilePer)) fi fi # Poiché gli indici degli array possono avere solo valori positivi, #+ è necessario uno spiazzamento che garantisca il raggiungimento #+ di questo risultato. spiazzamento=$((0-minimo)) for ((i=${minimo}; i<=${massimo}; i+=divisibilePer)); do risultati[i+spiazzamento]=0 done # Ora si esegue per un elevato numero di volte, per vedere cosa si ottiene. nr_volte=1000 # L'autore dello script suggeriva 100000, #+ ma sarebbe occorso veramente molto tempo. for ((i=0; i<${nr_volte}; ++i)); do # Notate che qui min e max sono specificate in ordine inverso #+ per vedere, in questo caso, il corretto comportamento della funzione. interCasuale ${max} ${min} ${divisibilePer} # Riporta un errore se si verifica un risultato inatteso. [ ${interCasualeNum} -lt ${min} -o ${interCasualeNum} -gt ${max} ] \ && echo errore MIN o MAX - ${interCasualeNum}! [ $((interCasualeNum%${divisibilePer})) -ne 0 ] \ && echo DIVISIBILE PER errore - ${interCasualeNum}! # Registra i risultati statisticamente. risultati[interCasualeNum+spiazzamento]=\ $((risultati[interCasualeNum+spiazzamento]+1)) done # Controllo dei risultati for ((i=${minimo}; i<=${massimo}; i+=divisibilePer)); do [ ${risultati[i+spiazzamento]} -eq 0 ] && echo "Nessun risultato per $i." \ || echo "${i} generato ${risultati[i+spiazzamento]} volte." done exit 0 |
Ma, quant'è casuale $RANDOM
? Il modo migliore
per verificarlo è scrivere uno script che mostri la
distribuzione dei numeri "casuali" generati da
$RANDOM
. Si lancia alcune volte un dado con
$RANDOM
. . .
Esempio 9-28. Lanciare un dado con RANDOM
#!/bin/bash # Quant'è casuale RANDOM? RANDOM=$$ # Cambia il seme del generatore di numeri #+ casuali usando l'ID di processo dello script. FACCE=6 # Un dado ha 6 facce. NUMMAX_LANCI=600 # Aumentatelo se non avete nient'altro di meglio da fare. lanci=0 # Contatore dei lanci. tot_uno=0 # I contatori devono essere inizializzati a 0 perché tot_due=0 #+ una variabile non inizializzata ha valore nullo, non zero. tot_tre=0 tot_quattro=0 tot_cinque=0 tot_sei=0 visualizza_risultati () { echo echo "totale degli uno = $tot_uno" echo "totale dei due = $tot_due" echo "totale dei tre = $tot_tre" echo "totale dei quattro = $tot_quattro" echo "totale dei cinque = $tot_cinque" echo "totale dei sei = $tot_sei" echo } aggiorna_contatori() { case "$1" in 0) let "tot_uno += 1";; # Poiché un dado non ha lo "zero", #+ lo facciamo corrispondere a 1. 1) let "tot_due += 1";; # 1 a 2, ecc. 2) let "tot_tre += 1";; 3) let "tot_quattro += 1";; 4) let "tot_cinque += 1";; 5) let "tot_sei += 1";; esac } echo while [ "$lanci" -lt "$NUMMAX_LANCI" ] do let "dado1 = RANDOM % $FACCE" aggiorna_contatori $dado1 let "lanci += 1" done visualizza_risultati exit 0 # I punteggi dovrebbero essere distribuiti abbastanza equamente, nell'ipotesi #+ che RANDOM sia veramente casuale. # Con $NUMMAX_LANCI impostata a 600, la frequenza di ognuno dei sei numeri #+ dovrebbe aggirarsi attorno a 100, più o meno 20 circa. # # Ricordate che RANDOM è un generatore pseudocasuale, e neanche #+ particolarmente valido. # La casualità è un argomento esteso e complesso. # Sequenze "casuali" sufficientemente lunghe possono mostrare #+ un andamento caotico e "non-casuale". # Esercizio (facile): # ------------------ # Riscrivete lo script per simulare il lancio di una moneta 1000 volte. # Le possibilità sono "TESTA" o "CROCE". |
Come si è visto nell'ultimo esempio, è
meglio ricalcolare il seme del generatore
RANDOM
ogni volta che viene invocato.
Utilizzando lo stesso seme, RANDOM
ripete le stesse serie di numeri.
[2]
(Rispecchiando il comportamento della funzione
random()
del C.)
Esempio 9-29. Cambiare il seme di RANDOM
#!/bin/bash # seeding-random.sh: Cambiare il seme della variabile RANDOM. MAX_NUMERI=25 # Quantità di numeri che devono essere generati. numeri_casuali () { contatore=0 while [ "$contatore" -lt "$MAX_NUMERI" ] do numero=$RANDOM echo -n "$numero " let "contatore += 1" done } echo; echo RANDOM=1 # Impostazione del seme di RANDOM. numeri_casuali echo; echo RANDOM=1 # Stesso seme... numeri_casuali # ...riproduce esattamente la serie precedente. # # Ma, quant'è utile duplicare una serie di numeri "casuali"? echo; echo RANDOM=2 # Altro tentativo, ma con seme diverso... numeri_casuali # viene generata una serie differente. echo; echo # RANDOM=$$ imposta il seme di RANDOM all'id di processo dello script. # È anche possibile usare come seme di RANDOM i comandi 'time' o 'date'. # Ancora più elegante... SEME=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }') # Output pseudocasuale prelevato da /dev/urandom (file di #+ dispositivo di sistema pseudo-casuale), quindi convertito #+ con "od" in una riga di numeri (ottali) visualizzabili, #+ infine "awk" ne recupera solamente uno per SEME. RANDOM=$SEME numeri_casuali echo; echo exit 0 |
Il file di dispositivo /dev/urandom
fornisce un metodo per generare numeri pseudocasuali molto più
"casuali" che non la variabile Esistono altri modi per generare numeri pseudocasuali in uno script. Awk ne fornisce uno molto comodo. Esempio 9-30. Numeri pseudocasuali utilizzando awk
Anche il comando date si presta a generare sequenze di interi pseudocasuali. |
[1] | La reale "casualità," per quanto esista veramente, la si può trovare solo in alcuni fenomeni naturali ancora non completamente compresi come il decadimento radioattivo. I computer possono solamente simularla, di conseguenza ci si riferisce alle sequenze "casuali" da essi generate col termine di numeri pseudocasuali. |
[2] | Il seme di una serie di numeri pseudocasuali generata dal computer può essere considerata un'etichetta identificativa. Per esempio, si pensi a una serie pseudocasuale con seme 23 come serie #23. Una proprietà della serie di numeri pseudocasuali è rappresentata dall'ampiezza del ciclo prima che la serie incominci a ripetersi. Un buon generatore di numeri pseudocasuali produce serie con cicli molto grandi. |