: ########################################################################## # Shellscript: rand - return random number # Author : Heiner Steven # Date : 1995-09-02 # Requires : bc, od # Category : Desktop # SCCS-Id. : @(#) rand 1.9 04/02/18 ########################################################################## # Description # o Prints a random number. Uses existing /dev/urandom for good # random numbers, otherwise date and time is used. # o If /dev/urandom is available, arbitrary large random numbers # are supported. # # Notes # o $Max should be used to find out, how many byte should be # read from /dev/urandom # # Changes # 1995-09-28 stv Random numbers cannot be maxvalue+1 (0.2) # 2000-01-28 stv numbers were too sequential (because of seconds) (1.2) # 2001-12-07 stv use $RANDOM variable, if present (1.3) # 2002-07-01 stv use /dev/urandom, if present (1.4) # 2002-07-02 stv fixed bug (numbers were always multiple of 256) (1.5) # 2002-07-16 stv random numbers were not evenly distributed (1.8) ########################################################################## PN=`basename "$0"` # program name VER='1.9' # Device returning random bytes. With Solaris 9 /dev/random may block, # therefore we prefer the non-blocking /dev/urandom RandomDevice=/dev/urandom #MaxRand=4294967295 # 2^32 = 4 GB # Some shell commands (e.g. Linux "expr") have problems with integer values # greater than 2^31 MaxRand=32767 Usage () { echo >&2 "$PN - return random number, $VER (stv '95) usage: $PN [maxvalue] Prints a random value (1 <= random <= maxvalue) to standard output. If no maximum value is specified, `echo \"$MaxRand + 1\" | bc` is the default" exit 1 } Msg () { for i do echo "$PN: $i" >&2 done } Fatal () { Msg "$@"; exit 1; } joinlines () { # "bc" splits very long output lines using the following format: # "a\ # b" tr -d '\134\012' # remove backslash and trailing line-feed character echo # terminate line using line-feed } while [ $# -gt 0 ] do case "$1" in --) shift; break;; -h) Usage;; -*) Usage;; *) break;; # First file name esac shift done if [ $# -gt 1 ] then Usage elif [ $# -eq 1 ] then case "$1" in *[!0-9]*) Fatal "illegal number: $1";; *) Max=$1;; esac fi : ${Max:=$MaxRand} # Use default value # Try different ways of getting a random number if [ -c $RandomDevice ] then # Read 4 bytes from the random device (dd), and interpret them as a # 32 bit long unsigned integer (od -t u4). #n=`dd if=/dev/urandom bs=1 count=4 2>/dev/null | # od -t u4 | awk 'NR==1 {print $2}'` # Uncomment the following to use 64 bit random numbers #n=`dd if=/dev/urandom bs=1 count=8 2>/dev/null | # od -t u8 | awk 'NR==1 {print $2}'` # Uncomment the lines below to get a random number with $ndigit # digits. Calculate the number of bytes needed as follows: # bytes= ... # round log256(10^$ndigit) # -> log256(10^x) = x * log256(10) = x * ln(10)/ln(256) # = x * .41524101186092029348 # Example: # How many bytes are needed to represent 10 decimal digits: # 10 * .41524101186092029348 = 4.1524101186092029348 # -> 5 bytes needed to represent all 10 digit decimal values # from 0..9999999999 #ndigit=10 # 0..9999999999 #bytes=5 ndigit=`expr "$Max" : '.*'` # string length # The following calculation may return a result that is one to large # because we do not attempt to properly round the value to the next # largest integer value. bytes=`echo "$ndigit * l(10)/l(256) + 1" | bc -l | cut -d. -f1` hexdigits=`echo "$bytes * 2" | bc` #echo >&2 "DEBUG: ndigit=<$ndigit> bytes=<$bytes> hexdigits=<$hexdigits>" n=`(echo ibase=16; dd if=/dev/urandom bs=1 count=$bytes 2>/dev/null | od -tx1 | # write as hex byte stream sed -n '$q;p' | # remove last line cut -d ' ' -f2- | # remove offsets tr -d ' ' | joinlines | cut -c1-$hexdigit | tr '[a-f]' '[A-F]') | # "bc" needs upper case hex chars bc | joinlines` elif [ -n "$RANDOM" ] then # This shell has a built-in random function (ksh, bash, zsh), which # probably generates better distributed values than our "date" # approach. n=$RANDOM else set -- `date '+%H %M %S'` [ $# -ne 3 ] && Fatal "could not invoke program date" n=`echo "$$ * $1 * $2 * $3 + $3" | bc` fi # Some "expr" commands have problems with large integer values echo "$n % $Max + 1" | bc | joinlines