15

I want a way to run a command randomly, say 1 out of 10 times. Is there a builtin or GNU coreutil to do this, ideally something like:

chance 10 && do_stuff

where do_stuff is only executed 1 in 10 times? I know I could write a script, but it seems like a fairly simple thing and I was wondering if there is a defined way.

  • 1
    This is a fairly good indicator that your script is probably getting too-out-of-hand for bash to continue being a reasonable choice. You should consider a more full fledged programming language, perhaps a scripting language like Python or Ruby. – Alexander - Reinstate Monica Sep 1 at 14:43
  • @Alexander this is not even really a script, just one line. I'm using it in a cron-job to notify me randomly every now and then as a reminder to do something – retnikt Sep 3 at 13:55
40

In ksh, Bash, Zsh, Yash or BusyBox sh:

[ "$RANDOM" -lt 3277 ] && do_stuff

The RANDOM special variable of the Korn, Bash, Yash, Z and BusyBox shells produces a pseudo-random decimal integer value between 0 and 32767 every time it’s evaluated, so the above gives (close to) a one-in-ten chance.

You can use this to produce a function which behaves as described in your question, at least in Bash:

function chance {
  [[ -z $1 || $1 -le 0 ]] && return 1
  [[ $RANDOM -lt $((32767 / $1 + 1)) ]]
}

Forgetting to provide an argument, or providing an invalid argument, will produce a result of 1, so chance && do_stuff will never do_stuff.

This uses the general formula for “1 in n” using $RANDOM, which is [[ $RANDOM -lt $((32767 / n + 1)) ]], giving a (⎣32767 / n⎦ + 1) in 32768 chance. Values of n which aren’t factors of 32768 introduce a bias because of the uneven split in the range of possible values.

  • (1/2) On re-visiting this issue: It is intuitive that there should be an upper limit on the number of parts, it is not possible to have 40.000 parts, for example. Actually the real limit is quite smaller. The issue is that the integer division is just an approximation to how big each part could be. It is required that two consecutive parts result in a difference of the limit $((32767/parts+1)) be bigger than 1 or we run into the risk of having the number of parts increase in 1 while the result of the division (and therefore the limit) be the same. (cont..) – Isaac Sep 12 at 22:13
  • (2/2) (cont..) That will account for more numbers than are actually available. The formula for that is (32767/n-32767/(n+1))>=1, solving for n that gives a limit at ~181.5. In fact the number of parts could run up to 194 without issue. But at 195 parts, the resulting limit is 151, the same result as with 194 parts. That is incongruent and should be avoided. In short, the upper limit for the number of parts (n), should be 194. You could make the limit tests: [[ -z $1 || $1 -le 1 || $1 -ge 194 ]] && return 1 – Isaac Sep 12 at 22:13
25

Non-standard solution:

[ $(date +%1N) == 1 ] && do_stuff

Check if the last digit of the current time in nanoseconds is 1!

  • That's awesome. – Eric Duminil Aug 31 at 20:15
  • 2
    You have to make sure that the call [ $(date +%1N) == 1 ] && do_stuff does not occur at regular intervals, otherwise the randomness is spoiled. Think of while true; do [ $(date +1%N) == 1 ] && sleep 1; done as an abstract counterexample. Nonetheless, the idea of playing with the nanoseconds is really good, I think I will use it, hence +1 – XavierStuvw Sep 1 at 0:15
  • 3
    @XavierStuvw I think you'd have a point if the code was checking seconds. But nanoseconds? It should appear really random. – Eric Duminil Sep 1 at 11:04
  • 9
    @EricDuminil: Unfortunately: 1) The system clock is not contractually obligated to provide actual nanosecond resolution (it may round to the nearest clock_getres()), 2) The scheduler is not prohibited from, for example, always starting a timeslice on a 100-nanosecond boundary (which would introduce bias even if the clock is right), 3) The supported way of obtaining random values is (usually) reading from /dev/urandom or inspecting the value of $RANDOM. – Kevin Sep 1 at 21:32
  • 1
    @Kevin: Thanks a lot for the comment. – Eric Duminil Sep 2 at 11:20
21

An alternative to using $RANDOM is the shuf command:

[[ $(shuf -i 1-10 -n 1) == 1 ]] && do_stuff

will do the job. Also useful for randomly selecting lines from a file, eg. for a music playlist.

  • 1
    Didn't know that command. Very nice! – Eisenknurr Sep 2 at 5:02
12

Improving upon the first answer and making it much more obvious what you are trying to achieve:

[ $(( $RANDOM % 10 )) == 0 ] && echo "You win" || echo "You lose"
  • 1
    $RANDOM % 10 will have a bias, unless the $RANDOM produces exactly a multiple of 10 different values (which generally doesn't happen in a binary computer) – phuclv Sep 2 at 0:38
  • 1
    @phuclv Yes, it will have the same bias as "$RANDOM" -lt $((32767 / n + 1)). – Eisenknurr Sep 2 at 4:59
  • Yes, the bias is identical, 0.100006104 instead of 0.1 for 1-in-10 (when checking against 0). – Stephen Kitt Sep 3 at 8:21
1

I'm not sure if you want randomness or periodicity ... For periodicity:

for i in `seq 1 10 100`; do echo $i;done
1
11
21
31
41
51
61
71
81
91

You can mix it with the "$RANDOM" trick above to produce something more chaotic, for instance:

for i in seq 1 1000 $RANDOM; do echo $i;done

HTH :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.