old-www/LDP/LG/issue55/tag/4.html

436 lines
18 KiB
HTML

<!--startcut ======================================================= -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<html>
<head>
<META NAME="generator" CONTENT="lgazmail v1.3D.k">
<TITLE>The Answer Guy 55: Getting Random Values in sh</TITLE>
</HEAD><BODY BGCOLOR="#FFFFFF" TEXT="#000000"
LINK="#3366FF" VLINK="#A000A0">
<!-- ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->
<P> <hr>
<!-- *** BEGIN navbar *** :::::::::::::::::::::::::::::::::::::::::::::::: -->
<p align="center">
<A HREF="../lg_bytes55.html"><IMG ALT="[ Prev ]"
SRC="../../gx/navbar/prev.jpg"
WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A>
<IMG ALT="" SRC="../../gx/navbar/left.jpg"
WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom" >
<A HREF="../index.html"><IMG ALT="[ Table of Contents ]"
SRC="../../gx/navbar/toc.jpg"
WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A>
<A HREF="../../index.html"><IMG ALT="[ Front Page ]"
SRC="../../gx/navbar/frontpage.jpg"
WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A>
<A HREF="../../faq/index.html"><IMG ALT="[ FAQ ]"
SRC="../../gx/navbar/faq.jpg"
WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A>
<IMG ALT="" SRC="../../gx/navbar/right.jpg"
WIDTH="15" HEIGHT="45" ALIGN="bottom" >
<A HREF="../lg_tips55.html"><IMG ALT="[ Next ]"
SRC="../../gx/navbar/next.jpg"
WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A>
<!-- *** END navbar *** :::::::::::::::::::::::::::::::::::::::::::::::::: -->
</p>
<P> <hr> <P>
<!-- ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->
<center>
<H1><A NAME="answer">
<img src="../../gx/dennis/qbubble.gif" alt="(?)"
border="0" align="middle">
<font color="#B03060">The Answer Guy</font>
<img src="../../gx/dennis/bbubble.gif" alt="(!)"
border="0" align="middle">
</A></H1>
<BR>
<H4>By James T. Dennis,
<a href="mailto:linux-questions-only@ssc.com">linux-questions-only@ssc.com</a><BR>
LinuxCare,
<A HREF="http://www.linuxcare.com/">http://www.linuxcare.com/</A>
</H4>
</center>
<p><hr><p>
<!-- endcut ======================================================= -->
<!-- begin 4 -->
<H3 align="left"><img src="../../gx/dennis/qbubble.gif"
height="50" width="60" alt="(?) " border="0"
>Getting Random Values in sh</H3>
<p><strong>From Devil Man on Thu, 08 Jun 2000
</strong></p>
<!-- ::
Getting Random Values in sh
~~~~~~~~~~~~~~~~~~~~~~~~~~~
:: -->
<P><STRONG>
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?
</STRONG></P>
<P><STRONG>
Thanks randomly speaking
</STRONG></P>
<BLOCKQUOTE><IMG SRC="../../gx/dennis/bbub.gif" ALT="(!)"
HEIGHT="28" WIDTH="50" BORDER="0"
>
Well the easiest way, under bash is to simply use the predefined
"magic" shell variable: $RANDOM. So the following might work for
you:
</BLOCKQUOTE>
<pre><BLOCKQuote>
RANDOM=$$$(date %+s)
function d20 () {
d=$[ ( $RANDOM % 20 ) + 1 ]
}
</BLOCKQUOTE></pre>
<BLOCKQUOTE>
... 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).
</BLOCKQUOTE>
<BLOCKQUOTE>
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 <TT>/dev/urandom</TT> or <TT>/dev/random</TT>
devices (depending on the relative importance of speed versus
"quality of entropy" required).
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
The classic "Dungeons &amp; 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.
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
Of course my example here depends on bash. So it's not very
portable.
</BLOCKQUOTE>
<BLOCKQUOTE>
Here's a method that's somewhat more portable:
</BLOCKQUOTE>
<pre><BLOCKQuote>
r=`fortune | cksum | cut -f1 -d" "`
d=`expr \( $r % 20 \) + 1 `
</BLOCKQuote></pre>
<BLOCKQUOTE>
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).
</BLOCKQUOTE>
<BLOCKQUOTE>
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).
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
Here's a link to a discussion of "simple psuedo-random
number generation":
</BLOCKQUOTE>
<BLOCKQUOTE><BLOCKQuote>
<A HREF="http://www.sct.gu.edu.au/~anthony/info/C/RandomNumbers"
>http://www.sct.gu.edu.au/~anthony/info/C/RandomNumbers</A>
</BLOCKQuote></BLOCKQUOTE>
<BLOCKQUOTE>
The examples can be adapted to sh pretty easily:
</BLOCKQUOTE>
<BLOCKQUOTE>
Set initial value:
</BLOCKQUOTE>
<BLOCKQUOTE><BLOCKQUOTE><CODE>
seed=`( echo $$ ; time ps ; w ; date ) | cksum | cut -f1 -d" " `
</CODE></BLOCKQUOTE></BLOCKQUOTE>
<BLOCKQUOTE>
Use it:
</BLOCKQUOTE>
<BLOCKQUOTE><BLOCKQUOTE><CODE>
echo $seed
<BR>seed=` expr \( $seed \* 9301 + 4929 \) % 233280 `
</CODE></BLOCKQUOTE></BLOCKQUOTE>
<BLOCKQUOTE>
... note I have to escape my "*" 'expr' operator to prevent it
from being expanded (into a list of files) due to shell globbing.
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
I came across another nice article on the "linear congruential"
calculation of psuedo-random numbers at:
</BLOCKQUOTE>
<BLOCKQUOTE><BLOCKQuote>
<A HREF="http://www.acm.inf.ethz.ch/ProblemSetArchive/B_US_NorthCen/1996/prob_f.html"
>http://www.acm.inf.ethz.ch/ProblemSetArchive/B_US_NorthCen/1996/prob_f.html</A>
</BLOCKQuote></BLOCKQUOTE>
<BLOCKQUOTE>
This apparently was in the context of a programming assignment,
challenge or contest of some sort.
</BLOCKQUOTE>
<BLOCKQUOTE>
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.
</BLOCKQUOTE>
<BLOCKQUOTE>
Frankly I think all that stuff is two complicated. So I'm
glad I use Linux where I can just use:
</BLOCKQUOTE>
<BLOCKQUOTE><BLOCKQUOTE><CODE>
dd if=/dev/urandom count=1 2&gt; /dev/null | cksum | cut -f1 -d" "
</CODE></BLOCKQUOTE></BLOCKQUOTE>
<BLOCKQUOTE>
... to get all the randomness I want.
</BLOCKQUOTE>
<BLOCKQUOTE>
So, I hope that's more than you wanted to know about generating
psuedo-random numbers using the shell.
</BLOCKQUOTE>
<!-- end 4 -->
<!-- . . . . . . . . . . . . . . . . . . . -->
<HR WIDTH="40%" ALIGN="center">
<!-- begin 4 -->
<H3 align="left"><img src="../../gx/dennis/qbubble.gif"
height="50" width="60" alt="(?) " border="0"
>Random Numbers</H3>
<p><strong>From Devil Man on Mon, 12 Jun 2000
</strong></p>
<P><STRONG>
Just a Note see below.
</STRONG></P>
<P><STRONG>
And thanks for all the wonderful info and the quick response...
</STRONG></P>
<P><STRONG>
--- The Linux Gazette Answer Guy &lt;<A HREF="mailto:linux-questions-only@ssc.com"
>linux-questions-only@ssc.com</A>&gt; wrote:
</STRONG></P>
<P><STRONG><FONT COLOR="#000066"><EM><BLOCKQuote>
Getting Random Values in sh
</BLOCKQuote></EM></FONT></STRONG></P>
<P><STRONG><FONT COLOR="#000099"><EM>
<BR>&gt;Hello answer guy I was wondering and have been unable to find
<BR>&gt;any info about a shell scripting utility or command that can be
<BR>&gt;used to generate a random number such as if I wanted to create a
<BR>&gt;shell script to generate a random number between 1-20 or so. It
<BR>&gt;dose not have to be a all in one basically how do you generate
<BR>&gt;random numbers and the command line?
</EM></FONT></STRONG></P>
<P><STRONG><FONT COLOR="#000099"><EM>
<BR>&gt;Thanks randomly speaking
</EM></FONT></STRONG></P>
<P><STRONG><FONT COLOR="#000066"><EM>
Well the easiest way, under bash is to simply use the predefined
"magic" shell variable: $RANDOM. So the following might work for
you:
</EM></FONT></STRONG></P>
<P><STRONG><FONT COLOR="#000066"><EM><BLOCKQuote>
RANDOM=$$$(date %+s)
</BLOCKQuote></EM></FONT></STRONG></P>
<P><STRONG>
shouldn't the date command be (date +%s)
</STRONG></P>
<BLOCKQUOTE><IMG SRC="../../gx/dennis/bbub.gif" ALT="(!)"
HEIGHT="28" WIDTH="50" BORDER="0"
>
Yep. That was a typo.
</BLOCKQUOTE>
<!-- end 4 -->
<!--startcut ======================================================= -->
<P> <hr> </p>
<H5 align="center"><a href="http://www.linuxgazette.com/copying.html"
>Copyright &copy;</a> 2000, James T. Dennis
<BR>Published in <I>The Linux Gazette</I> Issue 55 July 2000</H5>
<H6 ALIGN="center">HTML transformation by
<A HREF="mailto:star@tuxtops.com">Heather Stern</a> of
Tuxtops, Inc.,
<A HREF="http://www.tuxtops.com/">http://www.tuxtops.com/</A>
</H6>
<P> <hr>
<!-- begin tagnav ::::::::::::::::::::::::::::::::::::::::::::::::::-->
<p align="center">
<table width="100%" border="0"><tr>
<td align="right" valign="center"
><IMG ALT="" SRC="../../gx/navbar/left.jpg"
WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="middle" border="0">
<A HREF="../lg_answer55.html"
><IMG SRC="../../gx/dennis/answertoc.jpg" align="middle"
ALT="[ Answer Guy Current Index ]" border="0"></A></td>
<td align="center" valign="center"><A HREF="../lg_answer55.html#greeting"><img align="middle"
src="../../gx/dennis/smily.gif" alt="greetings" border="0"></A> &nbsp;
<A HREF="1.html">1</A> &nbsp;
<A HREF="2.html">2</A> &nbsp;
<A HREF="3.html">3</A> &nbsp;
<A HREF="4.html">4</A> &nbsp;
<A HREF="5.html">5</A> &nbsp;
<A HREF="6.html">6</A> &nbsp;
<A HREF="7.html">7</A> &nbsp;
<A HREF="8.html">8</A> &nbsp;
<A HREF="9.html">9</A> &nbsp;
<A HREF="10.html">10</A> &nbsp;
<A HREF="11.html">11</A> &nbsp;
<A HREF="12.html">12</A> &nbsp;
<A HREF="13.html">13</A> &nbsp;
<br>
<A HREF="14.html">14</A> &nbsp;
<A HREF="15.html">15</A> &nbsp;
<A HREF="16.html">16</A> &nbsp;
<A HREF="17.html">17</A> &nbsp;
<A HREF="18.html">18</A> &nbsp;
<A HREF="19.html">19</A> &nbsp;
<A HREF="20.html">20</A> &nbsp;
<A HREF="21.html">21</A> &nbsp;
<A HREF="22.html">22</A> &nbsp;
</td>
<td align="left" valign="center"><A HREF="../../tag/kb.html"
><IMG SRC="../../gx/dennis/answerpast.jpg" align="middle"
ALT="[ Index of Past Answers ]" border="0"></A>
<IMG ALT="" SRC="../../gx/navbar/right.jpg" align="middle"
WIDTH="14" HEIGHT="45" BORDER="0"></td></tr></table>
</p>
<!-- end tagnav ::::::::::::::::::::::::::::::::::::::::::::::::::::-->
<P> <hr>
<!-- *** BEGIN navbar *** :::::::::::::::::::::::::::::::::::::::::::::::: -->
<p align="center">
<A HREF="../lg_bytes55.html"><IMG ALT="[ Prev ]"
SRC="../../gx/navbar/prev.jpg"
WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A>
<IMG ALT="" SRC="../../gx/navbar/left.jpg"
WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom" >
<A HREF="../index.html"><IMG ALT="[ Table of Contents ]"
SRC="../../gx/navbar/toc.jpg"
WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A>
<A HREF="../../index.html"><IMG ALT="[ Front Page ]"
SRC="../../gx/navbar/frontpage.jpg"
WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A>
<A HREF="../../faq/index.html"><IMG ALT="[ FAQ ]"
SRC="../../gx/navbar/faq.jpg"
WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A>
<IMG ALT="" SRC="../../gx/navbar/right.jpg"
WIDTH="15" HEIGHT="45" ALIGN="bottom" >
<A HREF="../lg_tips55.html"><IMG ALT="[ Next ]"
SRC="../../gx/navbar/next.jpg"
WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A>
<!-- *** END navbar *** :::::::::::::::::::::::::::::::::::::::::::::::::: -->
</p>
<!-- ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->
</BODY></HTML>
<!--endcut ========================================================= -->