As we all know, the shell builtin variable $RANDOM is a pseudo random number generator.
Today, we will be looking at some creative ways to generate psuedo random numbers, trying to use shell builtins as much as possible.
The Best Solution is shuf (not exactly builtin though)
# Shuffle and return n'th line of range
shuf --head-count="1" --input-range="1-10000"
shuf -n1 -i1-10000
9523
Ways to generate various strings of numbers in BASH
# Generate a number between 0 - 32767
echo $RANDOM
7469
$RANDOM Caveats
From the 2 above scenarios, we analyse the distribution of the digits and find out the frequency of each value in order to see how random they really are:
# Count the frequency of numbers in a 10000 samples
# cut -c1 chops the first digit from our variable
for i in $(seq 1 1000); do
RNUMS+=($(cut -c1 <<< $RANDOM))
done
### OR
while [[ $COUNTER -le 1000 ]]; do
RNUMS+=($(cut -c1 <<< $RANDOM))
COUNTER=$COUNTER+1
done
# show RNUMS array length, should be 1000
echo ${#RNUMS[@]}
Now, we want to test the frequency of each digit:
for n in ${RNUMS[@]}; do
case $n in
1 ) ((ones++))
;;
2 ) ((twos++))
;;
3 ) ((three++))
;;
4 ) ((fours++))
;;
5 ) ((fives++))
;;
6 ) ((sixes++))
;;
7 ) ((sevens++))
;;
8 ) ((eights++))
;;
9 ) ((nines++))
;;
0 ) ((zeros++))
;;
esac
done
echo $ones $twos $threes $fours $fives $sixes $sevens $eights $nines $zeros
Strangely, the output expected should be ~100, ~100, ~100…
However, the output is totally different:
330 367 108 22 41 28 30 42 33
Creative Ways To Generate Bad Random Numbers In Bash
The second digit from $RANDOM$RANDOM
cut -c2 <<< $RANDOM$RANDOM
# cut the 2nd digit from RANDOMRANDOM
# $RANDOM's minimum value is 1 therefore the second digit should always be there
# Do not use this value on its own though, as it clearly is not random
while [[ $COUNTER -le 1000 ]]; do
RNUMS+=($(cut -c2 <<< $RANDOM$RANDOM))
COUNTER=$COUNTER+1
done
# frequency test
111 114 92 97 86 87 89 95 105 125
# again
120 100 108 93 100 97 78 83 97 125
# CLEARLY NOT RANDOM
A digit from $(date +%N)
cut -c9 <<< $(date +%N)
# Print the time in nanoseconds of the current moment
# Do not use this value on its own though, as it clearly is not random
date +%N
277936336
while [[ $COUNTER -le 1000 ]]; do
RNUMS+=($(cut -c9 <<< $(date +%N)))
COUNTER=$COUNTER+1
done
# frequency test
103 89 105 92 102 104 100 106 91 109
# DECENTLY RANDOM
A digit from cksum <<< $RANDOM$RANDOM
cut -c2 <<< $(cksum <<< $RANDOM$RANDOM)
# Checksum $RANDOM$RANDOM
echo $(cksum <<< $RANDOM$RANDOM)
277936336
while [[ $COUNTER -le 1000 ]]; do
RNUMS+=($(cut -c2 <<< $(cksum <<< $RANDOM$RANDOM)))
COUNTER=$COUNTER+1
done
# frequency test
128 123 91 78 101 95 85 86 92 122
# again
124 111 110 79 82 91 100 103 84 117
# and again
126 118 104 92 101 85 88 87 86 114
# CLEARLY NOT RANDOM!
A digit from cksum <<< $(($RANDOM*$RANDOM))
cut -c2 <<< $(cksum <<< $(($RANDOM*$RANDOM)))
# Checksum $RANDOM x $RANDOM
while [[ $COUNTER -le 1000 ]]; do
RNUMS+=($(cut -c2 <<< $(cksum <<< $(($RANDOM*$RANDOM)))))
COUNTER=$COUNTER+1
done
# and analysis
112 127 107 103 88 84 106 85 81 108
126 106 97 99 85 99 88 83 105 113
# CLEARY NOT RANDOM!
Shuf 1-10
shuf -n1 -i1-10
# Shuffle 1-10
while [[ $COUNTER -le 1000 ]]; do
RNUMS+=($(shuf -n1 -i1-10))
COUNTER=$COUNTER+1
done
# and analysis
95 107 76 93 113 88 116 109 110
101 95 116 100 107 109 83 91 103
# GREAT RANDOM NUMBER GENERATOR