Very simple random number generators in bash

So today I went ahead and finished my thought experiment, of how i could use bash to make a fairly random choice...here is my "proof of concept" for my random website selector idea as a psuedo random number generator.

So this script works so that each time a 0,1,2,3,4,5,6,7,8,9 is pulled out of the system clock (using sed to choose the last digit), then it gets stored in a variable value so that we can see the distribution of numbers chosen.

Code:
#!/bin/bash
#simple PRNG in bash

a=0
b=0
c=0
d=0
e=0
f=0
g=0
h=0
i=0
j=0

now=$(date +%s)
digit=$(echo $now | sed 's/\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\)/\4/g')

for ((k=0; k<=30; k++)); do
    if [ "$digit" = 0 ]; then
        sleep 1
        ((a++))
        clear
        echo "Occurences of 0: $a"
        echo "Occurences of 1: $b"
        echo "Occurences of 2: $c"
        echo "Occurences of 3: $d"
        echo "Occurences of 4: $e"
        echo "Occurences of 5: $f"
        echo "Occurences of 6: $g"
        echo "Occurences of 7: $h"
        echo "Occurences of 8: $i"
        echo "Occurences of 9: $j"
        now=$(date +%s)
        digit=$(echo $now | sed 's/\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\)/\4/g')
    elif [ "$digit" = 1 ]; then
        sleep 1
        ((b++))
        clear
        echo "Occurences of 0: $a"
        echo "Occurences of 1: $b"
        echo "Occurences of 2: $c"
        echo "Occurences of 3: $d"
        echo "Occurences of 4: $e"
        echo "Occurences of 5: $f"
        echo "Occurences of 6: $g"
        echo "Occurences of 7: $h"
        echo "Occurences of 8: $i"
        echo "Occurences of 9: $j"
        now=$(date +%s)
        digit=$(echo $now | sed 's/\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\)/\4/g')
    elif [ "$digit" = 2 ]; then
        sleep 1
        ((c++))
        clear
        echo "Occurences of 0: $a"
        echo "Occurences of 1: $b"
        echo "Occurences of 2: $c"
        echo "Occurences of 3: $d"
        echo "Occurences of 4: $e"
        echo "Occurences of 5: $f"
        echo "Occurences of 6: $g"
        echo "Occurences of 7: $h"
        echo "Occurences of 8: $i"
        echo "Occurences of 9: $j"
        now=$(date +%s)
        digit=$(echo $now | sed 's/\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\)/\4/g')
    elif [ "$digit" = 3 ]; then
        sleep 1
        ((d++))
        clear
        echo "Occurences of 0: $a"
        echo "Occurences of 1: $b"
        echo "Occurences of 2: $c"
        echo "Occurences of 3: $d"
        echo "Occurences of 4: $e"
        echo "Occurences of 5: $f"
        echo "Occurences of 6: $g"
        echo "Occurences of 7: $h"
        echo "Occurences of 8: $i"
        echo "Occurences of 9: $j"
        now=$(date +%s)
        digit=$(echo $now | sed 's/\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\)/\4/g')   
    elif [ "$digit" = 4 ]; then
        sleep 1
        ((e++))
        clear
        echo "Occurences of 0: $a"
        echo "Occurences of 1: $b"
        echo "Occurences of 2: $c"
        echo "Occurences of 3: $d"
        echo "Occurences of 4: $e"
        echo "Occurences of 5: $f"
        echo "Occurences of 6: $g"
        echo "Occurences of 7: $h"
        echo "Occurences of 8: $i"
        echo "Occurences of 9: $j"
        now=$(date +%s)
        digit=$(echo $now | sed 's/\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\)/\4/g')
    elif [ "$digit" = 5 ]; then
        sleep 1
        ((f++))
        clear
        echo "Occurences of 0: $a"
        echo "Occurences of 1: $b"
        echo "Occurences of 2: $c"
        echo "Occurences of 3: $d"
        echo "Occurences of 4: $e"
        echo "Occurences of 5: $f"
        echo "Occurences of 6: $g"
        echo "Occurences of 7: $h"
        echo "Occurences of 8: $i"
        echo "Occurences of 9: $j"
        now=$(date +%s)
        digit=$(echo $now | sed 's/\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\)/\4/g')
    elif [ "$digit" = 6 ]; then
        sleep 1
        ((g++))
        clear
        echo "Occurences of 0: $a"
        echo "Occurences of 1: $b"
        echo "Occurences of 2: $c"
        echo "Occurences of 3: $d"
        echo "Occurences of 4: $e"
        echo "Occurences of 5: $f"
        echo "Occurences of 6: $g"
        echo "Occurences of 7: $h"
        echo "Occurences of 8: $i"
        echo "Occurences of 9: $j"
        now=$(date +%s)
        digit=$(echo $now | sed 's/\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\)/\4/g')   
    elif [ "$digit" = 7 ]; then
        sleep 1
        ((h++))
        clear
        echo "Occurences of 0: $a"
        echo "Occurences of 1: $b"
        echo "Occurences of 2: $c"
        echo "Occurences of 3: $d"
        echo "Occurences of 4: $e"
        echo "Occurences of 5: $f"
        echo "Occurences of 6: $g"
        echo "Occurences of 7: $h"
        echo "Occurences of 8: $i"
        echo "Occurences of 9: $j"
        now=$(date +%s)
        digit=$(echo $now | sed 's/\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\)/\4/g')
    elif [ "$digit" = 8 ]; then
        sleep 1
        ((i++))
        clear
        echo "Occurences of 0: $a"
        echo "Occurences of 1: $b"
        echo "Occurences of 2: $c"
        echo "Occurences of 3: $d"
        echo "Occurences of 4: $e"
        echo "Occurences of 5: $f"
        echo "Occurences of 6: $g"
        echo "Occurences of 7: $h"
        echo "Occurences of 8: $i"
        echo "Occurences of 9: $j"
        now=$(date +%s)
        digit=$(echo $now | sed 's/\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\)/\4/g')
    else
        sleep 1
        ((j++))
        clear
        echo "Occurences of 0: $a"
        echo "Occurences of 1: $b"
        echo "Occurences of 2: $c"
        echo "Occurences of 3: $d"
        echo "Occurences of 4: $e"
        echo "Occurences of 5: $f"
        echo "Occurences of 6: $g"
        echo "Occurences of 7: $h"
        echo "Occurences of 8: $i"
        echo "Occurences of 9: $j"
        now=$(date +%s)
        digit=$(echo $now | sed 's/\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\{3\}\)\([0-9]\)/\4/g')
    fi
done

The results of running this a few times in sequence, which I put into a text file and labeled according to nth time I ran the script, shows that with each time you run the script, it starts on a different number and has an equal and predictable distribution. This is just a psuedo random number generator, but it has the "truly random" aspect in the sense that it can't favor one particular number more so than the others, even though anyone looking at the system clock would know the exact outcome of the generator...so you definetly couldn't use this for "cyptographic security", because anyone could figure out that this is based on a system clock, even if they couldn't see the algorithm in bare form:

Code:
TRIAL 1:

Occurences of 0: 3
Occurences of 1: 3
Occurences of 2: 3
Occurences of 3: 3
Occurences of 4: 3
Occurences of 5: 3
Occurences of 6: 3
Occurences of 7: 3
Occurences of 8: 3
Occurences of 9: 4

TRIAL 2:

Occurences of 0: 3
Occurences of 1: 3
Occurences of 2: 3
Occurences of 3: 4
Occurences of 4: 3
Occurences of 5: 3
Occurences of 6: 3
Occurences of 7: 3
Occurences of 8: 3
Occurences of 9: 3

TRIAL 3:

Occurences of 0: 3
Occurences of 1: 3
Occurences of 2: 3
Occurences of 3: 3
Occurences of 4: 3
Occurences of 5: 4
Occurences of 6: 3
Occurences of 7: 3
Occurences of 8: 3
Occurences of 9: 3

SO IN OTHER WORDS, the first time this thing was executed, 9 was the first number selected by sed, then 3, then 5. You could use this to select possible options in a script randomly, but it predictably counts from 0 to 9 by the second.

I think one way you could turn this into something truly random would be to have a clock that is broken down into nano or milliseconds, because both of those time units are imperceptible to all humans. You would never be able to pinpoint the exact millisecond of a day...no matter how hard you try.
 


Written with the utmost and sincere respect:

(a) The consistent distributions in three separate small tests may indicate that the PRNG is more deterministic than you expected. Ask yourself why none of your tests had digits with a count of "1" or "2". It could be "Murphy's Law" or something else.

(b) While it is true that humans cannot pinpoint exact milliseconds or nanoseconds, things are different when computers are involved. The internal timing of how the processor reads the clock is not as random as you may believe, especially at the LSB (least significant bit) level.

Many people rejoiced when Intel added a true RNG to its microprocessors, which was based on thermal noise. Some were suspicious of how Intel eliminated bias in the raw random data. There is a long story that goes with it, but I will save that for another day.
 
Here is a crude bash script that generates cryptographically strong random digits:

Code:
#!/bin/bash

# A crude bash script to generate random digits from 0 through 9 using /dev/urandom

ran=65535    # Force at least one pass through the loop
# Eliminate 65530-65535 to achieve an even distribution of final digits
until [ $ran -lt 65530 ]
do
    # Get a random unsigned int (2 bytes)
    ran=$(od -vAn -N2 -tu2 < /dev/urandom)
done

# Take only the final digit, use modulo 10:
digit=$((ran%10))
echo $digit
exit 0

You can replace the non-blocking "/dev/urandom" with /dev/random. I doubt that /dev/random will ever block for this use, but I try to write safe code.
 
Written with the utmost and sincere respect:

(a) The consistent distributions in three separate small tests may indicate that the PRNG is more deterministic than you expected. Ask yourself why none of your tests had digits with a count of "1" or "2". It could be "Murphy's Law" or something else.

(b) While it is true that humans cannot pinpoint exact milliseconds or nanoseconds, things are different when computers are involved. The internal timing of how the processor reads the clock is not as random as you may believe, especially at the LSB (least significant bit) level.

Many people rejoiced when Intel added a true RNG to its microprocessors, which was based on thermal noise. Some were suspicious of how Intel eliminated bias in the raw random data. There is a long story that goes with it, but I will save that for another day.
No i agree that it isn't very good, i said that but 1 or 2 was not included in part because i only tested it 3 times. It is like a diceroll in the sense that it takes the constantly changing last digit of the system clock, yet it is very much not cryptographically secure. Ill look at your RNG later.
 
@sphen:

Interestingly, first time running my script today it chose 2 as the first number:

Code:
Occurences of 0: 3
Occurences of 1: 3
Occurences of 2: 4
Occurences of 3: 3
Occurences of 4: 3
Occurences of 5: 3
Occurences of 6: 3
Occurences of 7: 3
Occurences of 8: 3
Occurences of 9: 3

However, I realized that it does seem to have a bias towards the higher numbers, so i changed the script again to start with whatever the system clock's digit happens to be when I run it. The problem with it before is that since i was putting the delay in there before the script generated the numbers, i'm incrementing each outcome by 1: "Anyone who considers arithmetical methods of producing random digits is, of course, in a state of sin."

Your script appears to work pretty well, i ran it 8 times (give that it didn't have anything like "rm -rf /" in there, LOL), and these are the numbers i got:

Code:
1
1
0
0
9
9
4
1

I will have to look through each part of it to try to understand it better sometime.
 
I'm sorry, but I have been looking at your output rather than your code. I was right, the output is evenly distributed. After reading your code, I understand why.

Unix time counts seconds since January 1, 1970. Each second, the time increases by 1. As I write this, the current Unix time is "1677009612"

Each time through the loop, your code sleeps for one second, then it gets the Unix time, which has incremented by 1. If the first Unix time happens to end with a "7", then it will sleep for one second, and the next time will end with an "8", then a "9", then cycle around to end with a "0", a "1", a "2"... etc.

Your digits are not random, they are more like a counter that loops around. It explains why you have an equal number of digits in every run.

-> Add this line to your code at the bottom between "fi" and "done" and you should see what is happening:

Code:
echo "Unix time is: $now" $'\n\n'

I hope this helps.
 
I'm sorry, but I have been looking at your output rather than your code. I was right, the output is evenly distributed. After reading your code, I understand why.

Unix time counts seconds since January 1, 1970. Each second, the time increases by 1. As I write this, the current Unix time is "1677009612"

Each time through the loop, your code sleeps for one second, then it gets the Unix time, which has incremented by 1. If the first Unix time happens to end with a "7", then it will sleep for one second, and the next time will end with an "8", then a "9", then cycle around to end with a "0", a "1", a "2"... etc.

Your digits are not random, they are more like a counter that loops around. It explains why you have an equal number of digits in every run.

-> Add this line to your code at the bottom between "fi" and "done" and you should see what is happening:

Code:
echo "Unix time is: $now" $'\n\n'

I hope this helps.
The trick in the end is just something un-expected, so a random number generator based on seconds of the system clock is about the best i can do...that opens up 10 possibilities rather than 2 as with my even/odd coin flip thingy. There could be more ways of manipulating the system clock to come up with a larger range of mathematical permutations, but i ain't no mathematician.

Having something cryptographically secure is a pretty high bar and just doesn't work if you only want a script to have a few possible outcomes, crytographic security would mean an astronomically high number of possibilities that would make figuring it out impossible if you don't know how it works. I don't think someone making small bash scripts would be able to do any better than just typing "echo $RANDOM". RNGs are not at all easy to make because computers always produce algorithmically certain outcomes.

I'd think each online poker company would not divulge their secrets to outsiders, it could be based on radioactivity APIs or something like that.
 
I realized a problem with what I wrote before in terms of clarity and practicality, if you want to pull digits out of a string of digits, then "cut" is a much better tool than sed:
Code:
now=$(date +%s)
digit=$(echo $now | cut -c 10)
 
I have almost no shell scripting experience, but here is a different version of your code. It is functionally identical to yours, but a little tighter:

Code:
#!/bin/bash
#simple PRNG in bash

a=0 b=0 c=0 d=0 e=0 f=0 g=0 h=0 i=0 j=0

for ((k=0; k<=30; k++)); do
    now=$(date +%s)
    digit=$(echo $now | cut -c 10)
    case $digit in
        0)
            ((a++))
            ;;
        1)
            ((b++))
            ;;
        2)
            ((c++))
            ;;
        3)
            ((d++))
            ;;          
        4)
            ((e++))
            ;;
        5)
            ((f++))
            ;;
        6)
            ((g++))
            ;;
        7)
            ((h++))
            ;;
        8)
            ((i++))
            ;;
        *)   # For anything else (it had better be "9" !), count it with "j"
            ((j++))
            ;;
    esac
    
    sleep 1
    clear
    echo "Occurences of 0: $a"
    echo "Occurences of 1: $b"
    echo "Occurences of 2: $c"
    echo "Occurences of 3: $d"
    echo "Occurences of 4: $e"
    echo "Occurences of 5: $f"
    echo "Occurences of 6: $g"
    echo "Occurences of 7: $h"
    echo "Occurences of 8: $i"
    echo "Occurences of 9: $j"
    echo "Unix Time is: $now" $'\n\n'  # Added by Sphen
done
exit 0  # Added by Sphen

Run this version and watch the "Unix Time" display as it runs.

Just a reminder: Because of the "sleep 1" / loop thing, it is more of a counter than a PRNG. I get what you're thinking - for a one time call, it gives a single "random" digit.

I hope this helps.
 
I have almost no shell scripting experience, but here is a different version of your code. It is functionally identical to yours, but a little tighter:

Code:
#!/bin/bash
#simple PRNG in bash

a=0 b=0 c=0 d=0 e=0 f=0 g=0 h=0 i=0 j=0

for ((k=0; k<=30; k++)); do
    now=$(date +%s)
    digit=$(echo $now | cut -c 10)
    case $digit in
        0)
            ((a++))
            ;;
        1)
            ((b++))
            ;;
        2)
            ((c++))
            ;;
        3)
            ((d++))
            ;;         
        4)
            ((e++))
            ;;
        5)
            ((f++))
            ;;
        6)
            ((g++))
            ;;
        7)
            ((h++))
            ;;
        8)
            ((i++))
            ;;
        *)   # For anything else (it had better be "9" !), count it with "j"
            ((j++))
            ;;
    esac
   
    sleep 1
    clear
    echo "Occurences of 0: $a"
    echo "Occurences of 1: $b"
    echo "Occurences of 2: $c"
    echo "Occurences of 3: $d"
    echo "Occurences of 4: $e"
    echo "Occurences of 5: $f"
    echo "Occurences of 6: $g"
    echo "Occurences of 7: $h"
    echo "Occurences of 8: $i"
    echo "Occurences of 9: $j"
    echo "Unix Time is: $now" $'\n\n'  # Added by Sphen
done
exit 0  # Added by Sphen

Run this version and watch the "Unix Time" display as it runs.

Just a reminder: Because of the "sleep 1" / loop thing, it is more of a counter than a PRNG. I get what you're thinking - for a one time call, it gives a single "random" digit.

I hope this helps.
Oh i get entirely what you were trying to say last time and i know how it works, and thanks for the variation on the theme just because it's interesting.
 
Here is a crude bash script that generates cryptographically strong random digits:

Code:
#!/bin/bash

# A crude bash script to generate random digits from 0 through 9 using /dev/urandom

ran=65535    # Force at least one pass through the loop
# Eliminate 65530-65535 to achieve an even distribution of final digits
until [ $ran -lt 65530 ]
do
    # Get a random unsigned int (2 bytes)
    ran=$(od -vAn -N2 -tu2 < /dev/urandom)
done

# Take only the final digit, use modulo 10:
digit=$((ran%10))
echo $digit
exit 0

You can replace the non-blocking "/dev/urandom" with /dev/random. I doubt that /dev/random will ever block for this use, but I try to write safe code.
I still like this script overall, it's very interesting, but unfortunately you get a lot of repeats when you run it, so that takes away from the unpredictability aspect.
 
I still like this script overall, it's very interesting, but unfortunately you get a lot of repeats when you run it, so that takes away from the unpredictability aspect.
I noticed the repeats, too. At first I thought it was normal, but now I wonder about it. It is getting late here, but I'll look it over in the next couple days.
 
It all boils down to Newtonian cause and effect
Not at the quantum level.

Wizard is coming for us all!

Wiz.png
 
IX0LgkU.gif
You kill me, Gabe.
 
Okay, I looked at the code and the repeats.

I did a check of /dev/urandom by creating a 1 Gbyte file and giving it some quick tests. To no surprise, it is reasonably random. I have much better tests but didn't bother. Having looked at and tested many RNGs, I have learned to trust /dev/random and /dev/urandom in typical usage like this. I won't get into the finer details.

I cannot explain the repeats, but it may have something to do with the original "od" command, which I had not used before. I guess it is related to how the unsigned ints are converted from pairs of random bytes. I did not bother to drill down and figure out why it has too many repeats. It was easier to change the code and then test the new code results.

I rewrote the code to use unsigned bytes instead of unsigned ints (2 bytes). I ran tests with loops of 100, 1000, and 10000 (takes longer!) with a counter that counted repeat digits. I also counted "rejects" - bytes with values from 250-255. Both counts varied from run to run as expected, but the totals danced around the expected ranges: approximately ~10% for repeat digits, and ~2% for "rejects". Here are sample runs of 1000 passes:
Code:
$ ./randtest.sh
repeat digit count = 93
rejects = 23
$ ./randtest.sh
repeat digit count = 108
rejects = 16
$ ./randtest.sh
repeat digit count = 105
rejects = 26
$ ./randtest.sh
repeat digit count = 96
rejects = 21
$

Here is a better version of the code that was based on /dev/urandom. The only real change is that it uses one byte instead of an int, plus the fact that I tested the output to ensure that results match expectations:
Code:
#!/bin/bash

# A crude bash script to generate random digits from 0 through 9 using /dev/urandom

ran=255    # Force at least one pass through the loop
# Eliminate 250-255 to achieve an even distribution of final digits
until [ $ran -lt 250 ]
do
    # Get a random unsigned byte
    ran=$(od -An -td -N1 /dev/urandom)
done

# Take only the final digit, use modulo 10:
digit=$((ran%10))
echo $digit
exit 0

-> In addition to this post and the code in it, please take a look at the shortened version of your script in post #29 above. I would like you to notice:

(a) How I used one case statement instead of the many "else"s.
and
(b) How I pulled the many echo statements and other repeating code out each else block in the loop to make them a single block.

I hope this helps. It has been fun, but I am ready to move on. What about you?
 
I cannot explain the repeats, but it may have something to do with the original "od" command, which I had not used before.
I researched the random and urandom devices as a result of your posts, i think it might just have to do with the fact that like intel, linux is is set produce random digits based on background noise. It's still called "pseudo-random", and i think we have figured out why. Maybe the background noise doesn't change very rapidly when you execute the code? Idk, but the repeats to have a predictable quality to them, but it is random enough for random string generation (most likely for encryption keys).
I hope this helps. It has been fun, but I am ready to move on. What about you?
I'm pretty much done here as far as the experiments on my end are concerned, and of course you have no obligation to participate. Yesterday i took my code from the "uselessweb.com" post and experimented with it...my original intention with combining both the 0-9 digits and and even/odd was to make it so the value doesn't keep going up if you run it multiple times. In the first ordering, the numbers just go from 0,1,2,3,4,5,6,7,8,9, in the second ordering (when the last digit happens to be odd, with a modulus of 1) they are now 5,6,7,8,9,0,1,2,3,4...i just ran it multiple times this morning, the numbers just went like:
Code:
1
5
8
1
3
6
8
2
4
8
And since the nature of these numbers are different from when I tested it last night, I'm satisfied that it's a algorithmically very predictable but fairly "random" from a perspective that isn't paying attention. The experiment is over for me, but I'm definetly down to learn from others about how to create RNGs/PRNGs in bash.
Not at the quantum level.

Wizard is coming for us all!
Yeah that's another wealth of information that i just don't know about...it would make sense that at the quantum level things might be "random" because how do you create matter in a vaccum? But the fact that life on earth and matter in the universe has a repetitive "order" to it would seem to contradict that...No, i'm definitely not a believer in "the divine", yet it appears to me that things tend to work more like a chain reaction rather than random chaos.
 
Last edited by a moderator:

Members online


Latest posts

Top