From Devil Man on Thu, 08 Jun 2000
Hello answer guy I was wondering and have been unable to find any info about a shell scripting utility or command that can be used to generate a random number such as if I wanted to create a shell script to generate a random number between 1-20 or so. It dose not have to be a all in one basically how do you generate random numbers and the command line?
Thanks randomly speaking
Well the easiest way, under bash is to simply use the predefined "magic" shell variable: $RANDOM. So the following might work for you:
RANDOM=$$$(date %+s) function d20 () { d=$[ ( $RANDOM % 20 ) + 1 ] }
... The first line just seeds bash' random number generator using your current process ID (PID) and the current time/date expressed as the number of seconds since 1970 (the UNIX epoch). This should prevent RANDOM from generating the same predictable sequence every time you run it. (You can set bash' RANDOM to new seed values, but if you ever 'unset' it --- it will lose its special "magic" property for the life of that shell/process. This is true of a couple of bash' "magic" variables).
Note that this form of random seeding is common but not adequate for proper cryptography, or high stakes gambling. For that we probably wouldn't be using the shell, we certainly wouldn't be storing things in environment variables, and we'd probably want to read a bit of entropy out of the Linux /dev/urandom or /dev/random devices (depending on the relative importance of speed versus "quality of entropy" required).
Shell function, which I've named after the gamer's conventional abbreviation for their favorite polyhedral (dice), simply takes a $RANDOM value modulo 20 (modulus is the remainder of a division, and thus gives us a number between 0 and 19) and then I add one to just this from the range 0-19 up to 1-20.
This method (take a modulus of a number and add a base) is commonly used by programmers to get random values within a specific range. If you want the numbers to follow a specific curve you can use additional arithmetic operations and additional random values. For example to get a nice bell curve that reasonably approximates a natural population where lots of entities are "average", a few are "exceptional" or "bad" and a very few are "super" or "woeful" you can use a sum of several random numbers.
The classic "Dungeons & Dragons (TM)" 3d6 gives such a curve which is why they don't simply use a single d20 for each ability score. It's also why simple percentile rolling on a pair of d10s or d20s doesn't give the "right" distribution of results.
You can get some really wacky curves if you take one random value and divide it with another (round down to the nearest int). For example a d6/d4 gives a number from 0 to 6 with only a 1 in 24 chance of getting a 6, and 25% chance of getting nothing, just over %30 of getting a 1, etc. But I digress.
Of course my example here depends on bash. So it's not very portable.
Here's a method that's somewhat more portable:
r=`fortune | cksum | cut -f1 -d" "` d=`expr \( $r % 20 \) + 1 `
Those are shell backtick (command substitution) operators. That's an older syntax which is supported by very old shells (and is still supported by new ones). I use that on the command line sometimes, but I prefer to use the newer syntax $( ... ) in scripts and when explaining shell programming. It's easier to read and it's easier to write clearly on a whiteboard. (Of course both forms mean the same thing, execute the enclosed command(s) capturing the output from them, and paste that output into parent expression as a replace for the whole "backtick" expression).
The 'fortune' command is included with most versions of UNIX and is commonly installed. It's just a little program that randomly chooses a "fortune cookie" --- a random quotation or aphorism --- and prints it. Lots of people see those every time they log in, and some of the X screensavers (like Mr Nose) use them. In this case we get a random phrase and feed it to the cksum (BSD/SysV checksum program). The checksum of a random strings should be random. (I don't have a rigorous mathematical proof of that handy --- but I'm pretty sure it's true; though it may not give a very even distribution). (That's another advantage to the $(...) form. It's nestable, you can have $( foo $( bar ... ) ) without ambiguity or error).
So I use another line and the old 'expr' command to scale $r to the desired range. I have to use two lines in this case, since the old "backtick" form cannot be "nested" (or at least the kinds of quoting tricks that might allow one to nest such a beast would probably not be very portable and would certainly be less readable.
Note that the 'expr' command is fairly picky --- so we must separate our operands and operators with spaces so that it sees each as a separate command line argument. Also note that I must quote/escape the parentheses in my arguments to 'expr' since I need for 'expr' to see them, so I have to prevent the shell (specifically the subshell that's executing my backtick command) from seeing those parentheses as a "subshell" operator. You could also wrap each of those parens. in quotes, single (hard) or double (soft). However you should NOT try to just wrap the whole expression in single or double quotes, because then 'expr' will see it all as one big (string) argument rather than as a sequence of numbers and operators. Sorry that's so complicated. That's how 'expr' works. In general it's much easier to use a more recent ksh, bash, or zsh which supports the internal 'let' command as well as $(( .... )) and/or $[ ... ] syntax for arithmetic operations.
Obviously if you want you script to be very portable, and you can't guarantee that your users will have a 'fortune' command installed, or that they'll have a recent version of a decent shell then you'll have to work at some other way to get a random number.
As long as you have expr, cksum, and ps (and/or w and/or who), date (and/or the time command), cut (or awk) it should be possible to cook up small random numbers suitable for dice games, etc.
The trick is to run some of those commands in a subshell, piping their combined output into cksum and cut out the checksum value. Any commands that are very likely to give different, even slightly different information when run from one second to the next are suitable as input to your checksum. Thus one new process or one that dies or changes state give different ps output. Every second the idle time reported by the 'w' (who) command will be updated. Of course the 'date' command will be different every second, as well.
Of course once you have a seed value (based on something non-deterministic, or something that is usually going to be different each time your program runs) then you can use your own arithmetic operations to perturb that seed value.
Here's a link to a discussion of "simple psuedo-random number generation":
https://www.sct.gu.edu.au/~anthony/info/C/RandomNumbers
The examples can be adapted to sh pretty easily:
Set initial value:
seed=`( echo $$ ; time ps ; w ; date ) | cksum | cut -f1 -d" " `
Use it:
echo $seed
seed=` expr \( $seed \* 9301 + 4929 \) % 233280 `
... note I have to escape my "*" 'expr' operator to prevent it from being expanded (into a list of files) due to shell globbing.
Also note that this must be run in the current shell context --- putting the seed=... line in a shell script wouldn't work because the shell script runs in its own shell, updates its own value of the seed, and then exits. That would leave our copy of the seed unchanged.
So, if this calculation (the linear congruential method) is to be stored in a shell script it must be invoked with the shell's "dot operator" or the 'source' built-in command. That will execute it within the context of the current shell, allowing the lines therein to modify the values of your current shell's variables.
I came across another nice article on the "linear congruential" calculation of psuedo-random numbers at:
https://www.acm.inf.ethz.ch/ProblemSetArchive/B_US_NorthCen/1996/prob_f.html
This apparently was in the context of a programming assignment, challenge or contest of some sort.
It should be noted that the values of your L, I, and M (the numbers you multiple, increment and modulo your current/seed value with at each iteration) can't be arbitrarily chosen. There are some some values for these that give "good" psuedo-randomness (an even distribution of return values across the spectrum of available numbers) while others will give very bad numbers.
Frankly I think all that stuff is two complicated. So I'm glad I use Linux where I can just use:
dd if=/dev/urandom count=1 2> /dev/null | cksum | cut -f1 -d" "
... to get all the randomness I want.
So, I hope that's more than you wanted to know about generating psuedo-random numbers using the shell.
From Devil Man on Mon, 12 Jun 2000
Just a Note see below.
And thanks for all the wonderful info and the quick response...
--- The Linux Gazette Answer Guy <tag@lists.linuxgazette.net> wrote:
Getting Random Values in sh
>Hello answer guy I was wondering and have been unable to find
>any info about a shell scripting utility or command that can be
>used to generate a random number such as if I wanted to create a
>shell script to generate a random number between 1-20 or so. It
>dose not have to be a all in one basically how do you generate
>random numbers and the command line?
>Thanks randomly speaking
Well the easiest way, under bash is to simply use the predefined "magic" shell variable: $RANDOM. So the following might work for you:
RANDOM=$$$(date %+s)
shouldn't the date command be (date +%s)
Yep. That was a typo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14 15 16 17 18 19 20 21 22 |