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 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.

Here Bill presents a versatile function that returns
a random number between two specified values.

Example 9-26. 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.
# 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 "Expects up to 3 passed parameters, but all are completely optional."
echo "min is the minimum value"
echo "max is the maximum value"
echo "multiple specifies that the answer must be 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 "Successful completion returns 0, unsuccessful completion returns"
echo "function syntax and 1."
echo "The answer is returned in the global variable randomBetweenAnswer"
echo "Negative values for any passed parameter are 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))
[ ${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.
displacement=$((0-minimum))
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
answer[i+displacement]=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+displacement]=$((answer[randomBetweenAnswer+displacement]+1))
done
# Let's check the results
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
[ ${answer[i+displacement]} -eq 0 ] && echo "We never got an answer of $i." || echo "${i} occurred ${answer[i+displacement]} 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 . . .

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]
(This mirrors the behavior of the
random() function in
C.)

The /dev/urandom 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 and Example 12-13), or using dd (see Example 12-55),
or even piping to md5sum
(see Example 33-14).

There are also other ways to generate pseudorandom
numbers in a script. Awk provides a
convenient means of doing this.

#!/bin/bash
# random2.sh: Returns a pseudorandom number in the range 0 - 1.
# 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 = "
echo | awk "$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 fail to do this?)
# 2) Using an integer multiplier as a scaling factor, generate random numbers
#+ in the range between 10 and 100.
# 3) Same as exercise #2, above, but generate random integers this time.

Notes

True "randomness," insofar as
it exists at all, can only be found in certain
incompletely understood natural phenomena such as
radioactive decay. Computers can only simulate
randomness, and computer-generated sequences of
"random" numbers are therefore referred to as
pseudorandom.