Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting
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

       1 #!/bin/bash
       2
       3 # $RANDOM returns a different random integer at each invocation.
       4 # Nominal range: 0 - 32767 (signed 16-bit integer).
       5
       6 MAXCOUNT=10
       7 count=1
       8
       9 echo
      10 echo "$MAXCOUNT random numbers:"
      11 echo "-----------------"
      12 while [ "$count" -le $MAXCOUNT ]      # Generate 10 ($MAXCOUNT) random integers.
      13 do
      14   number=$RANDOM
      15   echo $number
      16   let "count += 1"  # Increment count.
      17 done
      18 echo "-----------------"
      19
      20 # If you need a random int within a certain range, use the 'modulo' operator.
      21 # This returns the remainder of a division operation.
      22
      23 RANGE=500
      24
      25 echo
      26
      27 number=$RANDOM
      28 let "number %= $RANGE"
      29 #           ^^
      30 echo "Random number less than $RANGE  ---  $number"
      31
      32 echo
      33
      34
      35
      36 #  If you need a random integer greater than a lower bound,
      37 #+ then set up a test to discard all numbers below that.
      38
      39 FLOOR=200
      40
      41 number=0   #initialize
      42 while [ "$number" -le $FLOOR ]
      43 do
      44   number=$RANDOM
      45 done
      46 echo "Random number greater than $FLOOR ---  $number"
      47 echo
      48
      49    # Let's examine a simple alternative to the above loop, namely
      50    #       let "number = $RANDOM + $FLOOR"
      51    # That would eliminate the while-loop and run faster.
      52    # But, there might be a problem with that. What is it?
      53
      54
      55
      56 # Combine above two techniques to retrieve random number between two limits.
      57 number=0   #initialize
      58 while [ "$number" -le $FLOOR ]
      59 do
      60   number=$RANDOM
      61   let "number %= $RANGE"  # Scales $number down within $RANGE.
      62 done
      63 echo "Random number between $FLOOR and $RANGE ---  $number"
      64 echo
      65
      66
      67
      68 # Generate binary choice, that is, "true" or "false" value.
      69 BINARY=2
      70 T=1
      71 number=$RANDOM
      72
      73 let "number %= $BINARY"
      74 #  Note that    let "number >>= 14"    gives a better random distribution
      75 #+ (right shifts out everything except last binary digit).
      76 if [ "$number" -eq $T ]
      77 then
      78   echo "TRUE"
      79 else
      80   echo "FALSE"
      81 fi
      82
      83 echo
      84
      85
      86 # Generate a toss of the dice.
      87 SPOTS=6   # Modulo 6 gives range 0 - 5.
      88           # Incrementing by 1 gives desired range of 1 - 6.
      89           # Thanks, Paulo Marcel Coelho Aragao, for the simplification.
      90 die1=0
      91 die2=0
      92 # Would it be better to just set SPOTS=7 and not add 1? Why or why not?
      93
      94 # Tosses each die separately, and so gives correct odds.
      95
      96     let "die1 = $RANDOM % $SPOTS +1" # Roll first one.
      97     let "die2 = $RANDOM % $SPOTS +1" # Roll second one.
      98     #  Which arithmetic operation, above, has greater precedence --
      99     #+ modulo (%) or addition (+)?
     100
     101
     102 let "throw = $die1 + $die2"
     103 echo "Throw of the dice = $throw"
     104 echo
     105
     106
     107 exit 0

Example 9-12. Picking a random card from a deck

       1 #!/bin/bash
       2 # pick-card.sh
       3
       4 # This is an example of choosing random elements of an array.
       5
       6
       7 # Pick a card, any card.
       8
       9 Suites="Clubs
      10 Diamonds
      11 Hearts
      12 Spades"
      13
      14 Denominations="2
      15 3
      16 4
      17 5
      18 6
      19 7
      20 8
      21 9
      22 10
      23 Jack
      24 Queen
      25 King
      26 Ace"
      27
      28 # Note variables spread over multiple lines.
      29
      30
      31 suite=($Suites)                # Read into array variable.
      32 denomination=($Denominations)
      33
      34 num_suites=${#suite[*]}        # Count how many elements.
      35 num_denominations=${#denomination[*]}
      36
      37 echo -n "${denomination[$((RANDOM%num_denominations))]} of "
      38 echo ${suite[$((RANDOM%num_suites))]}
      39
      40
      41 # $bozo sh pick-cards.sh
      42 # Jack of Clubs
      43
      44
      45 # Thank you, "jipe," for pointing out this use of $RANDOM.
      46 exit 0

Example 9-13. Brownian Motion Simulation

       1 #!/bin/bash
       2 # brownian.sh
       3 # Author: Mendel Cooper
       4 # Reldate: 10/26/07
       5 # License: GPL3
       6
       7 #  ----------------------------------------------------------------
       8 #  This script models Brownian motion:
       9 #+ the random wanderings of tiny particles in a fluid,
      10 #+ as they are buffeted by random currents and collisions.
      11 #+ This is colloquially known as the "Drunkard's Walk."
      12
      13 #  It can also be considered as a stripped-down simulation of a
      14 #+ Galton Board, a slanted board with a pattern of pegs,
      15 #+ down which rolls a succession of marbles, one at a time.
      16 #+ At the bottom is a row of slots or catch basins in which
      17 #+ the marbles come to rest at the end of their journey.
      18 #  Think of it as a kind of bare-bones Pachinko game.
      19 #  As you see by running the script,
      20 #+ most of the marbles cluster around the center slot.
      21 #+ This is consistent with the expected binomial distribution.
      22 #  As a Galton Board simulation, the script
      23 #+ disregards such parameters as
      24 #+ board tilt-angle, rolling friction of the marbles,
      25 #+ angles of impact, and elasticity of the pegs.
      26 #  To what extent does this affect the accuracy of the simulation?
      27 #  ----------------------------------------------------------------
      28
      29 PASSES=500            #  Number of particle interactions / marbles.
      30 ROWS=10               #  Number of "collisions" (or horiz. peg rows).
      31 RANGE=3               #  0 - 2 output range from $RANDOM.
      32 POS=0                 #  Left/right position.
      33 RANDOM=$$             #  Seeds the random number generator from PID
      34                       #+ of script.
      35
      36 declare -a Slots      # Array holding cumulative results of passes.
      37 NUMSLOTS=21           # Number of slots at bottom of board.
      38
      39
      40 Initialize_Slots () { # Zero out all elements of the array.
      41 for i in $( seq $NUMSLOTS )
      42 do
      43   Slots[$i]=0
      44 done
      45
      46 echo                  # Blank line at beginning of run.
      47   }
      48
      49
      50 Show_Slots () {
      51 echo; echo
      52 echo -n " "
      53 for i in $( seq $NUMSLOTS )   # Pretty-print array elements.
      54 do
      55   printf "%3d" ${Slots[$i]}   # Allot three spaces per result.
      56 done
      57
      58 echo # Row of slots:
      59 echo " |__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|"
      60 echo "                                ||"
      61 echo #  Note that if the count within any particular slot exceeds 99,
      62      #+ it messes up the display.
      63      #  Running only(!) 500 passes usually avoids this.
      64   }
      65
      66
      67 Move () {              # Move one unit right / left, or stay put.
      68   Move=$RANDOM         # How random is $RANDOM? Well, let's see ...
      69   let "Move %= RANGE"  # Normalize into range of 0 - 2.
      70   case "$Move" in
      71     0 ) ;;                   # Do nothing, i.e., stay in place.
      72     1 ) ((POS--));;          # Left.
      73     2 ) ((POS++));;          # Right.
      74     * ) echo -n "Error ";;   # Anomaly! (Should never occur.)
      75   esac
      76   }
      77
      78
      79 Play () {                    # Single pass (inner loop).
      80 i=0
      81 while [ "$i" -lt "$ROWS" ]   # One event per row.
      82 do
      83   Move
      84   ((i++));
      85 done
      86
      87 SHIFT=11                     # Why 11, and not 10?
      88 let "POS += $SHIFT"          # Shift "zero position" to center.
      89 (( Slots[$POS]++ ))          # DEBUG: echo $POS
      90
      91 # echo -n "$POS "
      92
      93   }
      94
      95
      96 Run () {                     # Outer loop.
      97 p=0
      98 while [ "$p" -lt "$PASSES" ]
      99 do
     100   Play
     101   (( p++ ))
     102   POS=0                      # Reset to zero. Why?
     103 done
     104   }
     105
     106
     107 # --------------
     108 # main ()
     109 Initialize_Slots
     110 Run
     111 Show_Slots
     112 # --------------
     113
     114 exit $?
     115
     116 #  Exercises:
     117 #  ---------
     118 #  1) Show the results in a vertical bar graph, or as an alternative,
     119 #+    a scattergram.
     120 #  2) Alter the script to use /dev/urandom instead of $RANDOM.
     121 #     Will this make the results more random?
     122 #  3) Provide some sort of "animation" or graphic output
     123 #     for each marble played.

Jipe points out a set of techniques for generating random numbers within a range.

       1 #  Generate random number between 6 and 30.
       2    rnumber=$((RANDOM%25+6))
       3
       4 #  Generate random number in the same 6 - 30 range,
       5 #+ but the number must be evenly divisible by 3.
       6    rnumber=$(((RANDOM%30/3+1)*3))
       7
       8 #  Note that this will not work all the time.
       9 #  It fails if $RANDOM%30 returns 0.
      10
      11 #  Frank Wang suggests the following alternative:
      12    rnumber=$(( RANDOM%27/3*3+6 ))

Bill Gradwohl came up with an improved formula that works for positive numbers.

       1 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

       1 #!/bin/bash
       2 # random-between.sh
       3 # Random number between two specified values.
       4 # Script by Bill Gradwohl, with minor modifications by the document author.
       5 # Corrections in lines 187 and 189 by Anthony Le Clezio.
       6 # Used with permission.
       7
       8
       9 randomBetween() {
      10    #  Generates a positive or negative random number
      11    #+ between $min and $max
      12    #+ and divisible by $divisibleBy.
      13    #  Gives a "reasonably random" distribution of return values.
      14    #
      15    #  Bill Gradwohl - Oct 1, 2003
      16
      17    syntax() {
      18    # Function embedded within function.
      19       echo
      20       echo    "Syntax: randomBetween [min] [max] [multiple]"
      21       echo
      22       echo -n "Expects up to 3 passed parameters, "
      23       echo    "but all are completely optional."
      24       echo    "min is the minimum value"
      25       echo    "max is the maximum value"
      26       echo -n "multiple specifies that the answer must be "
      27       echo     "a multiple of this value."
      28       echo    "    i.e. answer must be evenly divisible by this number."
      29       echo
      30       echo    "If any value is missing, defaults area supplied as: 0 32767 1"
      31       echo -n "Successful completion returns 0, "
      32       echo     "unsuccessful completion returns"
      33       echo    "function syntax and 1."
      34       echo -n "The answer is returned in the global variable "
      35       echo    "randomBetweenAnswer"
      36       echo -n "Negative values for any passed parameter are "
      37       echo    "handled correctly."
      38    }
      39
      40    local min=${1:-0}
      41    local max=${2:-32767}
      42    local divisibleBy=${3:-1}
      43    # Default values assigned, in case parameters not passed to function.
      44
      45    local x
      46    local spread
      47
      48    # Let's make sure the divisibleBy value is positive.
      49    [ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy))
      50
      51    # Sanity check.
      52    if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o  ${min} -eq ${max} ]; then
      53       syntax
      54       return 1
      55    fi
      56
      57    # See if the min and max are reversed.
      58    if [ ${min} -gt ${max} ]; then
      59       # Swap them.
      60       x=${min}
      61       min=${max}
      62       max=${x}
      63    fi
      64
      65    #  If min is itself not evenly divisible by $divisibleBy,
      66    #+ then fix the min to be within range.
      67    if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ]; then
      68       if [ ${min} -lt 0 ]; then
      69          min=$((min/divisibleBy*divisibleBy))
      70       else
      71          min=$((((min/divisibleBy)+1)*divisibleBy))
      72       fi
      73    fi
      74
      75    #  If max is itself not evenly divisible by $divisibleBy,
      76    #+ then fix the max to be within range.
      77    if [ $((max/divisibleBy*divisibleBy)) -ne ${max} ]; then
      78       if [ ${max} -lt 0 ]; then
      79          max=$((((max/divisibleBy)-1)*divisibleBy))
      80       else
      81          max=$((max/divisibleBy*divisibleBy))
      82       fi
      83    fi
      84
      85    #  ---------------------------------------------------------------------
      86    #  Now, to do the real work.
      87
      88    #  Note that to get a proper distribution for the end points,
      89    #+ the range of random values has to be allowed to go between
      90    #+ 0 and abs(max-min)+divisibleBy, not just abs(max-min)+1.
      91
      92    #  The slight increase will produce the proper distribution for the
      93    #+ end points.
      94
      95    #  Changing the formula to use abs(max-min)+1 will still produce
      96    #+ correct answers, but the randomness of those answers is faulty in
      97    #+ that the number of times the end points ($min and $max) are returned
      98    #+ is considerably lower than when the correct formula is used.
      99    #  ---------------------------------------------------------------------
     100
     101    spread=$((max-min))
     102    #  Omair Eshkenazi points out that this test is unnecessary,
     103    #+ since max and min have already been switched around.
     104    [ ${spread} -lt 0 ] && spread=$((0-spread))
     105    let spread+=divisibleBy
     106    randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min))
     107
     108    return 0
     109
     110    #  However, Paulo Marcel Coelho Aragao points out that
     111    #+ when $max and $min are not divisible by $divisibleBy,
     112    #+ the formula fails.
     113    #
     114    #  He suggests instead the following formula:
     115    #    rnumber = $(((RANDOM%(max-min+1)+min)/divisibleBy*divisibleBy))
     116
     117 }
     118
     119 # Let's test the function.
     120 min=-14
     121 max=20
     122 divisibleBy=3
     123
     124
     125 #  Generate an array of expected answers and check to make sure we get
     126 #+ at least one of each answer if we loop long enough.
     127
     128 declare -a answer
     129 minimum=${min}
     130 maximum=${max}
     131    if [ $((minimum/divisibleBy*divisibleBy)) -ne ${minimum} ]; then
     132       if [ ${minimum} -lt 0 ]; then
     133          minimum=$((minimum/divisibleBy*divisibleBy))
     134       else
     135          minimum=$((((minimum/divisibleBy)+1)*divisibleBy))
     136       fi
     137    fi
     138
     139
     140    #  If max is itself not evenly divisible by $divisibleBy,
     141    #+ then fix the max to be within range.
     142
     143    if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; then
     144       if [ ${maximum} -lt 0 ]; then
     145          maximum=$((((maximum/divisibleBy)-1)*divisibleBy))
     146       else
     147          maximum=$((maximum/divisibleBy*divisibleBy))
     148       fi
     149    fi
     150
     151
     152 #  We need to generate only positive array subscripts,
     153 #+ so we need a displacement that that will guarantee
     154 #+ positive results.
     155
     156 disp=$((0-minimum))
     157 for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
     158    answer[i+disp]=0
     159 done
     160
     161
     162 # Now loop a large number of times to see what we get.
     163 loopIt=1000   #  The script author suggests 100000,
     164               #+ but that takes a good long while.
     165
     166 for ((i=0; i<${loopIt}; ++i)); do
     167
     168    #  Note that we are specifying min and max in reversed order here to
     169    #+ make the function correct for this case.
     170
     171    randomBetween ${max} ${min} ${divisibleBy}
     172
     173    # Report an error if an answer is unexpected.
     174    [ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] \
     175    && echo MIN or MAX error - ${randomBetweenAnswer}!
     176    [ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] \
     177    && echo DIVISIBLE BY error - ${randomBetweenAnswer}!
     178
     179    # Store the answer away statistically.
     180    answer[randomBetweenAnswer+disp]=$((answer[randomBetweenAnswer+disp]+1))
     181 done
     182
     183
     184
     185 # Let's check the results
     186
     187 for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
     188    [ ${answer[i+disp]} -eq 0 ] \
     189    && echo "We never got an answer of $i." \
     190    || echo "${i} occurred ${answer[i+disp]} times."
     191 done
     192
     193
     194 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

       1 #!/bin/bash
       2 # How random is RANDOM?
       3
       4 RANDOM=$$       # Reseed the random number generator using script process ID.
       5
       6 PIPS=6          # A die has 6 pips.
       7 MAXTHROWS=600   # Increase this if you have nothing better to do with your time.
       8 throw=0         # Number of times the dice have been cast.
       9
      10 ones=0          #  Must initialize counts to zero,
      11 twos=0          #+ since an uninitialized variable is null, NOT zero.
      12 threes=0
      13 fours=0
      14 fives=0
      15 sixes=0
      16
      17 print_result ()
      18 {
      19 echo
      20 echo "ones =   $ones"
      21 echo "twos =   $twos"
      22 echo "threes = $threes"
      23 echo "fours =  $fours"
      24 echo "fives =  $fives"
      25 echo "sixes =  $sixes"
      26 echo
      27 }
      28
      29 update_count()
      30 {
      31 case "$1" in
      32   0) ((ones++));;   # Since a die has no "zero", this corresponds to 1.
      33   1) ((twos++));;   # And this to 2.
      34   2) ((threes++));; # And so forth.
      35   3) ((fours++));;
      36   4) ((fives++));;
      37   5) ((sixes++));;
      38 esac
      39 }
      40
      41 echo
      42
      43
      44 while [ "$throw" -lt "$MAXTHROWS" ]
      45 do
      46   let "die1 = RANDOM % $PIPS"
      47   update_count $die1
      48   let "throw += 1"
      49 done
      50
      51 print_result
      52
      53 exit $?
      54
      55 #  The scores should distribute evenly, assuming RANDOM is random.
      56 #  With $MAXTHROWS at 600, all should cluster around 100,
      57 #+ plus-or-minus 20 or so.
      58 #
      59 #  Keep in mind that RANDOM is a ***pseudorandom*** generator,
      60 #+ and not a spectacularly good one at that.
      61
      62 #  Randomness is a deep and complex subject.
      63 #  Sufficiently long "random" sequences may exhibit
      64 #+ chaotic and other "non-random" behavior.
      65
      66 # Exercise (easy):
      67 # ---------------
      68 # Rewrite this script to flip a coin 1000 times.
      69 # 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

       1 #!/bin/bash
       2 # seeding-random.sh: Seeding the RANDOM variable.
       3 # v 1.1, reldate 09 Feb 2013
       4
       5 MAXCOUNT=25       # How many numbers to generate.
       6 SEED=
       7
       8 random_numbers ()
       9 {
      10 local count=0
      11 local number
      12
      13 while [ "$count" -lt "$MAXCOUNT" ]
      14 do
      15   number=$RANDOM
      16   echo -n "$number "
      17   let "count++"
      18 done
      19 }
      20
      21 echo; echo
      22
      23 SEED=1
      24 RANDOM=$SEED      # Setting RANDOM seeds the random number generator.
      25 echo "Random seed = $SEED"
      26 random_numbers
      27
      28
      29 RANDOM=$SEED      # Same seed for RANDOM . . .
      30 echo; echo "Again, with same random seed ..."
      31 echo "Random seed = $SEED"
      32 random_numbers    # . . . reproduces the exact same number series.
      33                   #
      34                   # When is it useful to duplicate a "random" series?
      35
      36 echo; echo
      37
      38 SEED=2
      39 RANDOM=$SEED      # Trying again, but with a different seed . . .
      40 echo "Random seed = $SEED"
      41 random_numbers    # . . . gives a different number series.
      42
      43 echo; echo
      44
      45 # RANDOM=$$  seeds RANDOM from process id of script.
      46 # It is also possible to seed RANDOM from 'time' or 'date' commands.
      47
      48 # Getting fancy...
      49 SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }'| sed s/^0*//)
      50 #  Pseudo-random output fetched
      51 #+ from /dev/urandom (system pseudo-random device-file),
      52 #+ then converted to line of printable (octal) numbers by "od",
      53 #+ then "awk" retrieves just one number for SEED,
      54 #+ finally "sed" removes any leading zeros.
      55 RANDOM=$SEED
      56 echo "Random seed = $SEED"
      57 random_numbers
      58
      59 echo; echo
      60
      61 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-16).

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

       1 #!/bin/bash
       2 #  random2.sh: Returns a pseudorandom number in the range 0 - 1,
       3 #+ to 6 decimal places. For example: 0.822725
       4 #  Uses the awk rand() function.
       5
       6 AWKSCRIPT=' { srand(); print rand() } '
       7 #           Command(s)/parameters passed to awk
       8 # Note that srand() reseeds awk's random number generator.
       9
      10
      11 echo -n "Random number between 0 and 1 = "
      12
      13 echo | awk "$AWKSCRIPT"
      14 # What happens if you leave out the 'echo'?
      15
      16 exit 0
      17
      18
      19 # Exercises:
      20 # ---------
      21
      22 # 1) Using a loop construct, print out 10 different random numbers.
      23 #      (Hint: you must reseed the srand() function with a different seed
      24 #+     in each pass through the loop. What happens if you omit this?)
      25
      26 # 2) Using an integer multiplier as a scaling factor, generate random numbers
      27 #+   in the range of 10 to 100.
      28
      29 # 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