XXX 9.3. $RANDOM: generate random integer¶
Anyone who attempts to generate random numbers by deterministic means is, of course, living in a state of sin.
–John von Neumann
`` $RANDOM `` is an internal Bash function (not a constant) that returns a pseudorandom ` [1] <randomvar.html#FTN.AEN5817>`__ integer in the range 0 - 32767. It should `` not `` be used to generate an encryption key.
Exemple 11. Generating random numbers¶
#!/bin/bash
# $RANDOM returns a different random integer at each invocation.
# Nominal range: 0 - 32767 (signed 16-bit integer).
MAXCOUNT=10
count=1
echo
echo "$MAXCOUNT random numbers:"
echo "-----------------"
while [ "$count" -le $MAXCOUNT ] # Generate 10 ($MAXCOUNT) random integers.
do
number=$RANDOM
echo $number
let "count += 1" # Increment count.
done
echo "-----------------"
# If you need a random int within a certain range, use the 'modulo' operator.
# This returns the remainder of a division operation.
RANGE=500
echo
number=$RANDOM
let "number %= $RANGE"
# ^^
echo "Random number less than $RANGE --- $number"
echo
# If you need a random integer greater than a lower bound,
#+ then set up a test to discard all numbers below that.
FLOOR=200
number=0 #initialize
while [ "$number" -le $FLOOR ]
do
number=$RANDOM
done
echo "Random number greater than $FLOOR --- $number"
echo
# Let's examine a simple alternative to the above loop, namely
# let "number = $RANDOM + $FLOOR"
# That would eliminate the while-loop and run faster.
# But, there might be a problem with that. What is it?
# Combine above two techniques to retrieve random number between two limits.
number=0 #initialize
while [ "$number" -le $FLOOR ]
do
number=$RANDOM
let "number %= $RANGE" # Scales $number down within $RANGE.
done
echo "Random number between $FLOOR and $RANGE --- $number"
echo
# Generate binary choice, that is, "true" or "false" value.
BINARY=2
T=1
number=$RANDOM
let "number %= $BINARY"
# Note that let "number >>= 14" gives a better random distribution
#+ (right shifts out everything except last binary digit).
if [ "$number" -eq $T ]
then
echo "TRUE"
else
echo "FALSE"
fi
echo
# Generate a toss of the dice.
SPOTS=6 # Modulo 6 gives range 0 - 5.
# Incrementing by 1 gives desired range of 1 - 6.
# Thanks, Paulo Marcel Coelho Aragao, for the simplification.
die1=0
die2=0
# Would it be better to just set SPOTS=7 and not add 1? Why or why not?
# Tosses each die separately, and so gives correct odds.
let "die1 = $RANDOM % $SPOTS +1" # Roll first one.
let "die2 = $RANDOM % $SPOTS +1" # Roll second one.
# Which arithmetic operation, above, has greater precedence --
#+ modulo (%) or addition (+)?
let "throw = $die1 + $die2"
echo "Throw of the dice = $throw"
echo
exit 0
Exemple 12. Picking a random card from a deck¶
#!/bin/bash
# pick-card.sh
# This is an example of choosing random elements of an array.
# Pick a card, any card.
Suites="Clubs
Diamonds
Hearts
Spades"
Denominations="2
3
4
5
6
7
8
9
10
Jack
Queen
King
Ace"
# Note variables spread over multiple lines.
suite=($Suites) # Read into array variable.
denomination=($Denominations)
num_suites=${#suite[*]} # Count how many elements.
num_denominations=${#denomination[*]}
echo -n "${denomination[$((RANDOM%num_denominations))]} of "
echo ${suite[$((RANDOM%num_suites))]}
# $bozo sh pick-cards.sh
# Jack of Clubs
# Thank you, "jipe," for pointing out this use of $RANDOM.
exit 0
Exemple 13. Brownian Motion Simulation¶
#!/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 points out a set of techniques for generating random numbers within a range.
# Generate random number between 6 and 30.
rnumber=$((RANDOM%25+6))
# Generate random number in the same 6 - 30 range,
#+ but the number must be evenly divisible by 3.
rnumber=$(((RANDOM%30/3+1)*3))
# Note that this will not work all the time.
# It fails if $RANDOM%30 returns 0.
# Frank Wang suggests the following alternative:
rnumber=$(( RANDOM%27/3*3+6 ))
Bill Gradwohl came up with an improved formula that works for positive numbers.
rnumber=$(((RANDOM%(max-min+divisibleBy))/divisibleBy*divisibleBy+min))
Here Bill presents a versatile function that returns a random number between two specified values.
Exemple 14. Random between values¶
#!/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
Just how random is `` $RANDOM `` ? The best way to test this is to write a script that tracks the distribution of “random” numbers generated by `` $RANDOM `` . Let’s roll a `` $RANDOM `` die a few times . . .
Exemple 15. Rolling a single die with RANDOM¶
#!/bin/bash
# How random is RANDOM?
RANDOM=$$ # Reseed the random number generator using script process ID.
PIPS=6 # A die has 6 pips.
MAXTHROWS=600 # Increase this if you have nothing better to do with your time.
throw=0 # Number of times the dice have been cast.
ones=0 # Must initialize counts to zero,
twos=0 #+ since an uninitialized variable is null, NOT zero.
threes=0
fours=0
fives=0
sixes=0
print_result ()
{
echo
echo "ones = $ones"
echo "twos = $twos"
echo "threes = $threes"
echo "fours = $fours"
echo "fives = $fives"
echo "sixes = $sixes"
echo
}
update_count()
{
case "$1" in
0) ((ones++));; # Since a die has no "zero", this corresponds to 1.
1) ((twos++));; # And this to 2.
2) ((threes++));; # And so forth.
3) ((fours++));;
4) ((fives++));;
5) ((sixes++));;
esac
}
echo
while [ "$throw" -lt "$MAXTHROWS" ]
do
let "die1 = RANDOM % $PIPS"
update_count $die1
let "throw += 1"
done
print_result
exit $?
# The scores should distribute evenly, assuming RANDOM is random.
# With $MAXTHROWS at 600, all should cluster around 100,
#+ plus-or-minus 20 or so.
#
# Keep in mind that RANDOM is a ***pseudorandom*** generator,
#+ and not a spectacularly good one at that.
# Randomness is a deep and complex subject.
# Sufficiently long "random" sequences may exhibit
#+ chaotic and other "non-random" behavior.
# Exercise (easy):
# ---------------
# Rewrite this script to flip a coin 1000 times.
# Choices are "HEADS" and "TAILS."
As we have seen in the last example, it is best to reseed the `` RANDOM `` generator each time it is invoked. Using the same seed for `` RANDOM `` repeats the same series of numbers. ` [2] <randomvar.html#FTN.AEN5857>`__ (This mirrors the behavior of the `` random() `` function in C .)
Exemple 16. Reseeding RANDOM¶
#!/bin/bash
# seeding-random.sh: Seeding the RANDOM variable.
# v 1.1, reldate 09 Feb 2013
MAXCOUNT=25 # How many numbers to generate.
SEED=
random_numbers ()
{
local count=0
local number
while [ "$count" -lt "$MAXCOUNT" ]
do
number=$RANDOM
echo -n "$number "
let "count++"
done
}
echo; echo
SEED=1
RANDOM=$SEED # Setting RANDOM seeds the random number generator.
echo "Random seed = $SEED"
random_numbers
RANDOM=$SEED # Same seed for RANDOM . . .
echo; echo "Again, with same random seed ..."
echo "Random seed = $SEED"
random_numbers # . . . reproduces the exact same number series.
#
# When is it useful to duplicate a "random" series?
echo; echo
SEED=2
RANDOM=$SEED # Trying again, but with a different seed . . .
echo "Random seed = $SEED"
random_numbers # . . . gives a different number series.
echo; echo
# RANDOM=$$ seeds RANDOM from process id of script.
# It is also possible to seed RANDOM from 'time' or 'date' commands.
# Getting fancy...
SEED=$(head -1 /dev/urandomod -N 1 | awk '{ print $2 }'| sed s/^0*//)
# Pseudo-random output fetched
#+ from /dev/urandom (system pseudo-random device-file),
#+ then converted to line of printable (octal) numbers by "od",
#+ then "awk" retrieves just one number for SEED,
#+ finally "sed" removes any leading zeros.
RANDOM=$SEED
echo "Random seed = $SEED"
random_numbers
echo; echo
exit 0
|Note
The `` /dev/urandom `` pseudo-device file provides a method of generating much more “random” pseudorandom numbers than the `` $RANDOM `` variable. `` dd if=/dev/urandom of=targetfile bs=1 count=XX `` creates a file of well-scattered pseudorandom numbers. However, assigning these numbers to a variable in a script requires a workaround, such as filtering through od (as in above example, Example 16-14 , and Example A-36 ), or even piping to md5sum (see Example 36-16 ).
There are also other ways to generate pseudorandom numbers in a script. Awk provides a convenient means of doing this.
Exemple 17. Pseudorandom numbers, using awk¶
#!/bin/bash
# random2.sh: Returns a pseudorandom number in the range 0 - 1,
#+ to 6 decimal places. For example: 0.822725
# Uses the awk rand() function.
AWKSCRIPT=' { srand(); print rand() } '
# Command(s)/parameters passed to awk
# Note that srand() reseeds awk's random number generator.
echo -n "Random number between 0 and 1 = "
echoawk "$AWKSCRIPT"
# What happens if you leave out the 'echo'?
exit 0
# Exercises:
# ---------
# 1) Using a loop construct, print out 10 different random numbers.
# (Hint: you must reseed the srand() function with a different seed
#+ in each pass through the loop. What happens if you omit this?)
# 2) Using an integer multiplier as a scaling factor, generate random numbers
#+ in the range of 10 to 100.
# 3) Same as exercise #2, above, but generate random integers this time.
The date command also lends itself to generating pseudorandom integer sequences .
#!/bin/bash
# random2.sh: Returns a pseudorandom number in the range 0 - 1,
#+ to 6 decimal places. For example: 0.822725
# Uses the awk rand() function.
AWKSCRIPT=' { srand(); print rand() } '
# Command(s)/parameters passed to awk
# Note that srand() reseeds awk's random number generator.
echo -n "Random number between 0 and 1 = "
echoawk "$AWKSCRIPT"
# What happens if you leave out the 'echo'?
exit 0
# Exercises:
# ---------
# 1) Using a loop construct, print out 10 different random numbers.
# (Hint: you must reseed the srand() function with a different seed
#+ in each pass through the loop. What happens if you omit this?)
# 2) Using an integer multiplier as a scaling factor, generate random numbers
#+ in the range of 10 to 100.
# 3) Same as exercise #2, above, but generate random integers this time.
#!/bin/bash
# random2.sh: Returns a pseudorandom number in the range 0 - 1,
#+ to 6 decimal places. For example: 0.822725
# Uses the awk rand() function.
AWKSCRIPT=' { srand(); print rand() } '
# Command(s)/parameters passed to awk
# Note that srand() reseeds awk's random number generator.
echo -n "Random number between 0 and 1 = "
echoawk "$AWKSCRIPT"
# What happens if you leave out the 'echo'?
exit 0
# Exercises:
# ---------
# 1) Using a loop construct, print out 10 different random numbers.
# (Hint: you must reseed the srand() function with a different seed
#+ in each pass through the loop. What happens if you omit this?)
# 2) Using an integer multiplier as a scaling factor, generate random numbers
#+ in the range of 10 to 100.
# 3) Same as exercise #2, above, but generate random integers this time.
Notes¶
` [1] <randomvar.html#AEN5817>`__
True “randomness,” insofar as it exists at all, can only be found in certain incompletely understood natural phenomena, such as radioactive decay. Computers only simulate randomness, and computer-generated sequences of “random” numbers are therefore referred to as pseudorandom .
` [2] <randomvar.html#AEN5857>`__
The seed of a computer-generated pseudorandom number series can be considered an identification label. For example, think of the pseudorandom series with a seed of 23 as `` Series #23 `` .
A property of a pseurandom number series is the length of the cycle before it starts repeating itself. A good pseurandom generator will produce series with very long cycles.