Advanced Bash-Scripting Guide:
Prev | Chapter 9. Another Look at Variables | Next

# 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] integer in the range 0 - 32767. It should `not` be used to generate an encryption key.

Example 9-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

Example 9-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

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

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

Example 9-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) let "ones += 1";;   # Since die has no "zero", this corresponds to 1.
      1) let "twos += 1";;   # And this to 2.
      2) let "threes += 1";; # Etc.
      3) let "fours += 1";;
      4) let "fives += 1";;
      5) let "sixes += 1";;
    esac
    }

    echo

    while [ "$throw" -lt "$MAXTHROWS" ]
    do
      let "die1 = RANDOM % $PIPS"
      update_count $die1
      let "throw += 1"
    done

    print_result

    exit 0

    #  The scores should distribute fairly evenly, assuming RANDOM is fairly 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] (This mirrors the behavior of the `random()` function in C.)

Example 9-16. Reseeding RANDOM

    #!/bin/bash
    # seeding-random.sh: Seeding the RANDOM variable.

    MAXCOUNT=25       # How many numbers to generate.

    random_numbers ()
    {
    count=0
    while [ "$count" -lt "$MAXCOUNT" ]
    do
      number=$RANDOM
      echo -n "$number "
      let "count += 1"
    done
    }

    echo; echo

    RANDOM=1          # Setting RANDOM seeds the random number generator.
    random_numbers

    echo; echo

    RANDOM=1          # Same seed for RANDOM...
    random_numbers    # ...reproduces the exact same number series.
                      #
                      # When is it useful to duplicate a "random" number series?

    echo; echo

    RANDOM=2          # Trying again, but with a different 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/urandom | od -N 1 | awk '{ print $2 }')
    #  Pseudo-random output fetched
    #+ from /dev/urandom (system pseudo-random device-file),
    #+ then converted to line of printable (octal) numbers by "od",
    #+ finally "awk" retrieves just one number for SEED.
    RANDOM=$SEED
    random_numbers

    echo; echo

    exit 0

| 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-14). There are also other ways to generate pseudorandom numbers in a script. Awk provides a convenient means of doing this. Example 9-17. Pseudorandom numbers, using awk
    #!/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.
The date command also lends itself to generating pseudorandom integer sequences.

### Notes

[1] | 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] | 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.

Prev | Home | Next
Typing variables: declare or typeset | Up | Manipulating Variables