Generating a POSIX random number?

Hi Guys and gals...

As you know I am getting to grips with POSIX and hit this stumbling block.
Generating two random numbers 0 to 255 POSIXly. Speed in not important hence the 'sleep 1' command.
I have done a demo that works, but it sure is ugly! Is there a better way?

#!/bin/sh
# Random numbers POSIX style.
byteone=0
bytetwo=0
while true
do
	dd if=/dev/urandom of=/tmp/rand bs=1 count=1 > /dev/null 2>&1
	byteone=$( od -tu1 -An < /tmp/rand )
	byteone=$( echo $byteone ) # Ignore this line as it simply strips leading whitespaces.
	dd if=/dev/urandom of=/tmp/rand bs=1 count=1 > /dev/null 2>&1
	bytetwo=$( od -tu1 -An < /tmp/rand )
	bytetwo=$( echo $bytetwo ) # Ignore this line as it simply strips leading whitespaces.
	echo "$byteone $bytetwo"
	sleep 1
done

/dev/urandom is not specified by POSIX. So, besides being ugly, it doesn't really conform to POSIX.

The shell variable RANDOM is not specified by POSIX either, but if you're using ksh or bash , the following code is a LOT faster and simpler:

#!/bin/sh
while true
do
	byteone=$((RANDOM % 255))
	bytetwo=$((RANDOM % 255))
	echo "$byteone $bytetwo"
	sleep 1
done

Note that on many systems, /bin/sh is not a POSIX-conforming shell. And, as you have seen in many of my earlier posts, /bin/awk or /usr/bin/awk might not be a POSIX-conforming awk utility. But as long as /bin/sh is a shell that recognizes Bourne shell syntax, the following usually works on any POSIX-conforming system:

#!/bin/sh
PATH=`getconf PATH`
export PATH
sh <<-"EOF"
	awk '
	BEGIN {	srand()
		while(1)
			printf("%d %d\n", 256 * rand(), 256 * rand())
	}' | while read byteone bytetwo
	do	echo "$byteone $bytetwo"
		sleep 1
	done
EOF

There is other implementation-defined initialization code that is needed on some systems to really set up a POSIX-conforming environment, but the above should work for the minimal features used by this script.

1 Like

Hmmm, I didn't know that and it looks like there are very many on the WWW that don't either as that was where I got my main info from; that means that /dev/random is not POSIX compliant either. Ouch!

I already have this and Shell Check pointed out that RANDOM is undefined that is why I went the direction that I did.

Hmm, awk again, why did I not think of that after all I use it a lot for floating point stuff for CygWin...

You are a star Don thanks, consider my ugly non-compliant code scrubbed I am homing in on your 'awk' example...

This POSIX lark is much more difficult than I expected.

As for your final part about 'awk', if one does not use extensions that are not POSIX compliant then surely that particular 'awk' variant IS still technically conforming to the POSIX _environment_?

---------- Post updated at 11:57 AM ---------- Previous update was at 10:02 AM ----------

As an addendum here I decided to make it look a little clearer for me so I put this into Shell Check...

#!/bin/sh
awk ' BEGIN \
{	srand()
	while(1)
		printf( "%d %d\n", 16 * rand(), 16 * rand() )
}' | while read -r byteone bytetwo
do
        echo "$byteone $bytetwo"
done

And got this _error_:-

$ shellcheck myscript
 
Line 2:
awk ' BEGIN \
             ^-- SC1004: You don't break lines with \ in single quotes, it results in literal backslash-linefeed.

$ 

Do I take it that Shell Check is correct and the backslash is a no-no even inside a completely different language; the above works perfectly for my needs.

Twothree comments:

  • the while(1) inside the awk script will make it generate random numbers until hell freezes, is that needed at all to fill byteone and bytetwo ?
  • Piping into a while loop executing in a subshell will not make the variables available in the parent shell. If you want to use those, a different mechanism is needed.
  • In some awk s, calling srand() is pointless as it preseeds the random generator from the system clock which is done anyway when that awk starts.

So, although some people in these forums rant about one liners - just kidding - you might want to consider this:

TMP=$(awk ' BEGIN {printf( "%d %d\n", 16 * rand(), 16 * rand() )}')
byteone=${TMP% *}; bytetwo=${TMP#* }

Except that I tried removing while(1) , (I assumed while(true) ) and it disabled the Ctrl-C on this MBP. I could not exit the program.
I will stick up for Don here it worked perfectly for my needs but I will try your method and see how it works, watch this space...

I assume MBP stands for MacBook Pro?

I've got some difficulty to understand why Ctrl-C should be disabled. Anyway, above (post#4) would supply exactly two random bytes, no need to interrupt nor exit.

EDIT: while(true) in awk shouldn't do anything as true is not defined, evaluates to 0 and thus FALSE.

Addendum to Rudis' post:-
MacBook Pro 13 inch, circa August 2012, OSX 10.7.5, default terminal calling OSX's 'sh'.
Test code, not conforming entirely to POSIX!

#!/bin/sh
for n in {1..4}
do
	TMP=$(awk ' BEGIN {printf( "%d %d\n", 16 * rand(), 16 * rand() )}')
	byteone=${TMP% *}; bytetwo=${TMP#* }
	echo "$byteone $bytetwo"
done

Results:-

Last login: Sun Jul 17 13:31:28 on ttys000
AMIGA:barrywalker~> cd Desktop/Code/Shell
AMIGA:barrywalker~/Desktop/Code/Shell> ./RAND2.sh
13 6
13 6
13 6
13 6
AMIGA:barrywalker~/Desktop/Code/Shell> ./RAND2.sh
13 6
13 6
13 6
13 6
AMIGA:barrywalker~/Desktop/Code/Shell> ./RAND2.sh
13 6
13 6
13 6
13 6
AMIGA:barrywalker~/Desktop/Code/Shell> _

So I assume 'srand()' is needed if only to change the seed per call...
EDIT:
Using 'srand()' and without 'sleep 1' then with 'sleep 1'...
I do test on various *nix style platforms including CygWin.

#!/bin/sh
for n in {1..4}
do
	TMP=$(awk ' BEGIN { srand(); printf( "%d %d\n", 16 * rand(), 16 * rand() ); }')
	byteone=${TMP% *}; bytetwo=${TMP#* }
	echo "$byteone $bytetwo"
	#sleep 1
done

Results:-

AMIGA:barrywalker~/Desktop/Code/Shell> ./RAND2.sh
12 13
12 13
12 13
12 13
AMIGA:barrywalker~/Desktop/Code/Shell> ./RAND2.sh
3 9
1 15
7 6
13 12
AMIGA:barrywalker~/Desktop/Code/Shell> ./RAND2.sh
11 14
11 14
11 14
11 14
AMIGA:barrywalker~/Desktop/Code/Shell> ./RAND2.sh
3 2
1 9
14 7
12 6
AMIGA:barrywalker~/Desktop/Code/Shell> _

So to conclude the 'srand()' function is only accurate to the _timer's_ second value and not 'milli' or 'micro' second values. Hence the 'sleep 1' to advance the _timer_ by 1 second.
Don's code works with or without 'sleep', hence I can now understand why 'while(1)' is being used.

Appreciated. And sorry. I'm not a Mac person. Did you try it with the seed call?

1 Like

1) There is no need to apologise, you gave a good method and I found its foibles.
2) We have both learnt something from this thread, me moreso than you.
3) Yes I did try it with 'srand()' and now I think understand why 'while(1)' is used. See EDIT: in post #7.
4) It shouldn't matter about the platform if the code is fully 'sh' compliant.

Boy oh boy, keeping everything POSIX compliant is difficult; especially as I like to do animations and hit the hardware, (bang the metal <wink>).

All I want now is the answer to edited section in my post #3.

In most implementations of awk , random number sequences start are seeded with a constant value (not with the current time) unless you call srand() before calling rand() the first time. (So, as you saw when invoking awk once a second to just produce two random numbers without calling srand() you frequently end up with with the same pair of random numbers every time you call awk. Furthermore, calling srand() with no operand supplies the number of Seconds since the Epoch as an integer number of seconds as the seed value in all but one implementation of awk that I know of. So, if awk is invoked to generate two random numbers calling srand() but without sleeping between invocations of awk you may get the same pair of random numbers a few times until the time in seconds changes.

While it is true that the while(1) loop in the following awk script is an infinite loop, it will only go through that loop a few hundred times before the pipe to the shell fills up (putting awk to sleep for a while). And, yes, byteone and bytetwo are only defined in the while do loop, it was my assumption that whatever you want to do with those variables will be placed in that loop replacing the echo command.

	awk '
	BEGIN {	srand()
		while(1)
			printf("%d %d\n", 256 * rand(), 256 * rand())
	}' | while read byteone bytetwo
	do	echo "$byteone $bytetwo"
		sleep 1
	done

But, obviously, if a known number of pairs of random numbers are wanted, the while(1) can be changed to a while(cnt++<num) loop, or the shell's while read loop can break or exit when it is done instead of using sleep or just continuing to process the next pair of random numbers.

Hi Don...

Thanks for the info, it is all logged down in the old grey matter for future use.

As for while(1) I assumed that it was filling up a buffer of some kind but never guessed that it was the pipe, | , that it was filling up.
Clever idea that one. (I love lateral thinking.)

I assumed also - like in Python - that while(1) is the equivalent of while(True): . Both (1) and (True) are valid in Python even in Python's latest guise.
Python has not deprecated the (1) which goes back to the start of the Python project.

LBNL, I got the idea of my original ugly code from the WWW from many sources and no-one considered that '/dev/urandom' was NOT in the POSIX specifications. Even the WWW can give answers that are effectively false, so......

......I now consider this thread _solved_ and thanks again Don, you saved the day.

Hello wisecracker,

In Python3.4 you could try following and let me know if this helps you.

cat random_numbers.py
import random
print("%s" % '[1, 100]:')
for i in range(3):
    print("%d" % random.randint(1, 100))
print("%s" % '\n[-5, 5]:')
for i in range(3):
    print("%d" % random.randint(-5, 5))

Following will be output on same.

python3 random_numbers.py
[1, 100]:
88
94
55
[-5, 5]:
1
3
3

Thanks,
R. Singh

Hi RavinderSingh13...

I can't use Python in any guise as I have no idea if it is part of ALL *NIX installations.
Similarly Perl, but I have no knowledge of Perl programming anyhow.

It had to be purely POSIX 'sh' compliant and it became much more difficult than I had imagined. As Don so succinctly put it WRT my Post #1:-

There is also the fact that, is Python subject to POSIX compliance?

Again, I have no idea, so rather than use it I avoided it...

In fact I have all but abandoned Python in preference to shell scripting now.

And finally, all my Python code that I have uploaded to the WWW usually works form Python 1.4.0, (Classic AMIGA 1200(HD)), to Python 3.4.3, my latest on various current platforms without modification. This is also seriously difficult to do!

However thanks for your input...