1

I'm trying to generate a random number in between a specified range by using the $RANDOM function in the bash terminal. The problem is that the numbers it is generating don't appear to be random at all. The script I am using is:

RANDOM=$$;

a=$RANDOM
b=9; #Number of scripts in collection -1
c=`expr $a % $b `; #Random number between 0 and b

if (( c==0 ));
then

echo "script 1 (random = $a, mod = $c)";

elif (( c==1 ));
then

echo "stript 2 (random = $a, mod = $c)";

...

else

echo "error (random = $a, mod = $c)";

fi;

If I run this in a for in loop I get, for example:

script 8 (random = 17845, mod = 7)
script 8 (random = 18754, mod = 7)
script 8 (random = 19663, mod = 7)
script 7 (random = 20571, mod = 6)
script 7 (random = 21480, mod = 6)
script 6 (random = 22388, mod = 5)
script 6 (random = 23297, mod = 5)
script 6 (random = 24206, mod = 5)
script 5 (random = 25114, mod = 4)
script 5 (random = 26023, mod = 4)

Which clearly isn't random.

I tried removing the $a and just running

c=`expr $RANDOM % $b`; 

and then altering the code to another variation

c=`expr $a \* $b \/ 32767`;

But these (unsurprisingly) returned the same result. What am I doing wrong? Or is this just a slightly irritating limitation for $RANDOM? Any advice would be greatly appreciated.

advert2013
  • 9,734
  • 3
  • 24
  • 35

2 Answers2

3

You kept seeding RANDOM with the same number. Try not to seed it or seed it with a more random item instead:

RANDOM=$$

Apparently $$ doesn't change always as it's always the main PID (not subshell PID) of your shell. If you're actually calling different shells, probably there isn't much difference since the numbers seeded by every PID increments only by ones. So either you could remove that or get another random seed somewhere like /dev/urandom, etc.

One good way to apply a random seed by /dev/urandom:

RANDOM=$(tr -dc 0-9 < /dev/urandom | head -c10)

Another through nanoseconds (seeding larger numbers than these seems to not give a good effect):

RANDOM=$(date '+%N')

Also to make it look more unique among different subprocesses, add BASHPID (better than $$) to your seed:

RANDOM=$(( BASHPID + $(date '+%N') ))
konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • Thanks for the info on $$ - I just tried RANDOM=$(tr -dc 0-9 < /dev/urandom | head -c10) but I got an 'Illegal byte sequence error' - could that be because I am running this on OSX? – advert2013 Sep 07 '13 at 23:18
  • It's OSX related probably. Wouldn't not seeding RANDOM at all be enough for you? And have you tried `date '+%N'`? – konsolebox Sep 07 '13 at 23:19
  • 1
    it didn't seem to like that either - at least I got the same random variable. Changing the mod sequence to (( c=RANDOM % b )) seemed to fix it actually. Thanks for all your help though! – advert2013 Sep 07 '13 at 23:26
  • $$ is another way to 'getpid' and if you are relaunching a script, you will expect it to increment by one (or by two if an alternate process launched in the interim) – original_username Oct 20 '13 at 06:15
2

I think the explanation is to be found here:

When you use a modulus operation you are selecting information from the low order bits of a number and discarding information from the high order bits... The least significant (right-hand) digits of X are not very random, so decisions based on the number X should always be influenced primarily by the most significant digits.

And using this does work better for me (though I only tested a few times):

c=$(($a * $b / 32768))

Here's the revised script:

#!/bin/bash

RANDOM=$$;
a=$RANDOM

b=9; #Number of scripts in collection -1

c=$(($a * $b / 32768))

if (( c==0 )); then
    echo "script 1 (random = $a, mod = $c)";
elif (( c==1 )); then
    echo "script 2 (random = $a, mod = $c)";
else
    echo "error (random = $a, mod = $c)";
fi;

Hope this helps.

original_username
  • 2,398
  • 20
  • 24
  • I did see this actually but it didn't seem to alter too much (but I will use the /32767 version) - the solution seems to be using (( c=RANDOM % b )) or (( c=a*b/32767)), both of which cause a number plenty random enough for my purposes. – advert2013 Sep 07 '13 at 23:33