7

How I can generate random number between 0-60 in sh (/bin/sh, not bash)? This is a satellite box, there is no $RANDOM variable, and other goods [cksum, od (od -vAn -N4 -tu4 < /dev/urandom)].

I want to randomize a crontab job's time.

jww
  • 97,681
  • 90
  • 411
  • 885
Adrian
  • 115
  • 1
  • 2
  • 5

7 Answers7

11

If you have tr, head and /dev/urandom, you can write this:

tr -cd 0-9 </dev/urandom | head -c 3

Then you have to use the remainder operator to put in 0-60 range.

marco
  • 4,455
  • 1
  • 23
  • 20
  • Ah. I had read the question in a such a way to make be believe that /dev/urandom wasn't present. This is a great solution. – Thedward Jan 13 '11 at 16:54
  • 1
    I get `tr: illegal byte sequence` – Chris Jefferson Nov 15 '15 at 12:55
  • 1
    try with LANG=C prior to the command (source: http://stackoverflow.com/questions/11287564/getting-sed-error-illegal-byte-sequence-in-bash which is for sed but hopefully should work for you too) – marco Nov 18 '15 at 17:10
9

How about using the nanoseconds of system time?

date +%N

It isn't like you need cryptographically useful numbers here.

Depending on which version of /bin/sh it is, you may be able to do:

$(( date +%N % 60 ))

If it doesn't support the $(()) syntax, but you have dc, you could try:

dc -e `date +%N`' 60 % p'

Without knowing which operating system, version of /bin/sh or what tools are available it is hard to come up with a solution guaranteed to work.

Thedward
  • 1,432
  • 9
  • 8
  • UNfortunatelly not worked with date, there is no dc.. /bin/sh --help /bin/sh --help BusyBox v1.15.3 (2010-07-21 18:00:33 CEST) multi-call binary – Adrian Jan 12 '11 at 23:40
3

I know this post is old, but the suggested answers are not generating uniform unbiased random numbers. The accepted answer is essentially this:

% echo $(( $(tr -cd 0-9 </dev/urandom | head -c 3) % 60))

The problem with this suggestion is that by choosing a 3-digit number from /dev/urandom, the range is from 0-999, a total of 1,000 numbers. However, 1,000 does not divide into 60 evenly. As such, you'll be biased towards generating 0-959 just slightly more than 960-999.

The second answer, while creative in using nanoseconds from your clock, suffers from the same biased approach:

% echo $(( $(date +%N) % 60 ))

The range for nanoseconds is 0-999,999,999, which is 1 billion numbers. So, if you're dividing that result by 60, you'll again be biased towards generating 0-999,999,959 slightly more than 999,999,960-999,999,999.

All the rest of the answers are the same- biased non-uniform generation.

To generate unbiased uniform random numbers in the range of 0-59 (is what I assume he means rather than 0-60, if he's attempting to randomize a crontab(1) entry), we need to force the output to be a multiple of 60.

First, we'll generate a random 32-bit number between 0 and 4294967295:

% RNUM=$(od -An -N4 -tu2 /dev/urandom | awk '{print $1}')

Now we'll force our range to be between $MIN and 4294967295 that is a multiple of 60:

% MIN=$((2**32 % 60)) # 16

This means:

4294967296 - 16 = 4294967280
4294967280 / 60 = 71582788.0

In other words, my range of [16, 4294967295] is exactly a multiple of 60. So, every number I generate in that range, then divide by 60, will be equally likely as any other number. Thus, I have an unbiased generator of numbers 0-59 (or 1-60 if you add 1).

The only thing left to do is make sure that my number is between 16 and 4294967295. If my number is less than 16, then I'll need to generate a new number:

% while [ $RNUM -lt $MIN ]; do RNUM=$(od -An -N1 -tu2 /dev/urandom); done
% MINUTE=$(($RNUM % 60))

Everything put together for copy/paste goodnees:

#!/bin/bash
RNUM=$(od -An -N4 -tu2 /dev/urandom | awk '{print $1}')
MIN=$((2**32 % 60))
while [ $RNUM -lt $MIN ]; do RNUM=$(od -An -N1 -tu2 /dev/urandom); done
MINUTE=$(($RNUM % 60))
Aaron Toponce
  • 547
  • 5
  • 9
2

Do you have awk? You can call awk's rand() function. For instance:

awk 'BEGIN { printf("%d\n",rand()*60)  }' < /dev/null
wuputah
  • 11,285
  • 1
  • 43
  • 60
frankc
  • 11,290
  • 4
  • 32
  • 49
  • 1
    @Adrian: You have to seed the random number generator: `awk 'BEGIN { srand(); printf("%d\n",rand()*60) }'`. No redirect is necessary if there is only a `BEGIN` clause. – Dennis Williamson Jan 13 '11 at 04:35
  • @Dennis, this is true if you plan on call rand() more than once a second. Otherwise, srand() should be automatically seeded by the time – frankc Jan 13 '11 at 14:35
  • 2
    `awk 'BEGIN {print rand(), rand()}'; sleep 3; awk 'BEGIN {print rand(), rand()}'` gives me the same pair of values twice for both GNU awk and BusyBox awk (different values for the different versions). If I include `srand()` and even if I remove the `sleep`, the values vary. It's not unusual to have to explicitly seed a random number generator. Part of the reason for this is so you can get a predictable sequence during testing. – Dennis Williamson Jan 13 '11 at 14:50
0

Beware of errors when generated number starts by 0 and has other digits greater than 7, as it is interpreted as octal, I would propose:

tr -cd 0-9 </dev/urandom | head -c 4 | sed -e 's/^00*//

specially in case you want to process it any further, for example to establish a range:

RANDOM=`tr -cd 0-9 </dev/urandom | head -c 4 | sed -e 's/^00*//'`
RND50=$((($RANDOM%50)+1))   // random number between 1 and 50
0
value=`od -An -N2 -tu2 /dev/urandom`
minutes=`expr $value % 60`

The seed will be between 0 and 65535, which is not an even multiple of 60, so minutes 0-15 have a slightly greater chance ob being chosen, but the discrepancy is probably not important.

If you want to achieve perfection, use "od -An -N1 -tu1" and loop until value is less than 240.

Tested with busybox od.

0

After a year of using the top-voted solution, from this thread, for generating a random number between a range in Shell, I have confirmed, as @Aaron Toponce suggests, that this method is biased and not uniformly random. Of the range 1-65535, the following numbers were randomly generated no more than one time (within a year): 3, 4, 6, 7, 9, 13, 17, 19, 20, 21, 58080, 60020, 60443, 61532, 61900, 62078, 63331, 64623, 64680, 65000, 65129, 65389. Notice that these numbers are statistical outliers on a bell curve of the range. Conversely, this same method has a tendency of "randomly" generating common numbers within the bell curve with some frequency proving unreliable for production implementation. These empirical results have brought me back to this thread to share my experience; while, still searching for a pure Shell solution that generates random numbers between a range, which does not require alternative applications.