**Библиотека сайта rus-linux.net**

# 9.3. $RANDOM: generate random integer

`$RANDOM`

is an internal Bash function (not a constant) that
returns a *pseudorandom*
[1]
integer in the range 0 - 32767. It should

be used to generate an encryption
key.*not*

**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 -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 } 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? |

*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. # 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+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 . . .

**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 # Throw count. 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, etc. 2) let "threes += 1";; 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

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
*random()**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 There are also other ways to generate pseudorandom
numbers in a script.
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 |

[2] | The 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. |