Cron jobs and random times, within given hours

BashCronSh

Bash Problem Overview


I need the ability to run a PHP script 20 times a day at completely random times. I also want it to run only between 9am - 11pm.

I'm familiar with creating cron jobs in linux.

Bash Solutions


Solution 1 - Bash

Yeah, yeah, the question is over a year old, but maybe I can add something useful:

How to cron something at a random offset 20 times a day between 9am and 11pm? That's kinda tricky within cron, because you are dividing 14 hours by 20 execution times. I don't like the other answers very much because they require writing a bash wrapper script for your php script.

However, if you'll allow me the liberty to ease the timing and frequency restriction to 13 times between 8:30am and 11:09pm, this might do the trick, and all within the confines of your crontab:

30 8-21/* * * * sleep ${RANDOM:0:2}m ; /path/to/script.php

${RANDOM:3:2} uses bash's $RANDOM that other people have mentioned above, but adds bash array slicing. Since bash variables are untyped, the pseudo-random signed 16-bit number gets truncated to the first 2 of its 5 decimal digits, giving you a succinct one-liner for delaying your cronjob between 10 and 99 minutes (though the distribution is biased towards 10 to 32).

The following might also work for you, but I found it do be "less random" for some reason (perhaps Benford's Law is triggered by modulating pseudo-random numbers. Hey, I don't know, I flunked math... Blame it on bash!):

30 8-21/* * * * sleep $[RANDOM\%90]m ; /path/to/script.php

You need to render modulus as '%' above because cron (well, at least Linux 'vixie-cron') terminates the line when it encounters an unescaped '%'.

Maybe you could get the remaining 7 script executions in there by adding another line with another 7-hour range. Or relax your restriction to run between 3am and 11pm.

Solution 2 - Bash

So I'm using the following to run a command between 1AM and 330AM

0 1 * * * perl -le 'sleep rand 9000' && *command goes here*

That has been taking care of my random needs for me. That's 9000 seconds == 150 minutes == 2.5 hours

Solution 3 - Bash

If I understand what you're looking for, you'll need to do something a bit messy, like having a cron job that runs a bash script that randomizes the run times... Something like this:

crontab:

0 9 * * * /path/to/bashscript

and in /path/to/bashscript:

#!/bin/bash

maxdelay=$((14*60))  # 14 hours from 9am to 11pm, converted to minutes
for ((i=1; i<=20; i++)); do
    delay=$(($RANDOM%maxdelay)) # pick an independent random delay for each of the 20 runs
    (sleep $((delay*60)); /path/to/phpscript.php) & # background a subshell to wait, then run the php script
done

A few notes: this approach it a little wasteful of resources, as it fires off 20 background processes at 9am, each of which waits around for a random number of minutes (up to 14 hours, i.e. 11pm), then launches the php script and exits. Also, since it uses a random number of minutes (not seconds), the start times aren't quite as random as they could be. But $RANDOM only goes up to 32,767, and there are 50,400 seconds between 9am and 11pm, it'd be a little more complicated to randomize the seconds as well. Finally, since the start times are random and independent of each other, it's possible (but not very likely) that two or more instances of the script will be started simultaneously.

Solution 4 - Bash

Cron offers a RANDOM_DELAY variable. See crontab(5) for details.

> The RANDOM_DELAY variable allows delaying job startups by random amount of > minutes with upper limit specified by the variable.

This is seen commonly in anacron jobs, but also can be useful in a crontab.

You might need to be careful with this if you have some jobs that run at fine (minute) granularity and others that are coarse.

Solution 5 - Bash

I ended up using sleep $(( 1$(date +%N) % 60 )) ; dostuffs (compatible with bash & sh)

The 1 prefix is to force NON base 8 interpretation of date +%N (e.g. 00551454)

Do not forget to escape % using % in a crontab file

* * * * *  nobody  sleep $(( 1$(date +\%N) \% 60 )) ; dostuffs 

Solution 6 - Bash

My first thought would be to create one cron job launching 20 randomly scheduled at jobs. The at utility (http://unixhelp.ed.ac.uk/CGI/man-cgi?at) is used for executing commands at specified time.

Solution 7 - Bash

al-x 's Solution does not work for me since crontab commands are not executed in bash but in sh I guess. What does work is:

30 8 * * * bash -c "sleep $[RANDOM\%90]m" ; /path/to/script.py

Solution 8 - Bash

I realize it's an older thread, but I want to add one random-value related thing that I use a lot. Instead of using the $RANDOM variable with a fixed and limited range, I often make arbitrary-range random values in the shell with

dd if=/dev/urandom bs=4 count=1 2>/dev/null | od -N4 -t u4 -A none

so you can do, for example,

FULLRANDOM=$(dd if=/dev/urandom bs=4 count=1 2>/dev/null | od -N4 -t u4 -A none)

and overcome some the restrictions that were discussed in this thread.

Solution 9 - Bash

at -f [file] [timespec]

or

echo [command] | at [timespec]

or

at [timespec] ... and interactive specification like script's recording.

Command

At runs the text provide on stdin or in the file specified by -f [file].

Timespec

Here's the [timespec] grammar. It can be something like:

  • 24-hour time as 4-digit int, e.g. 0100, 2359, 1620
  • now + 10 minutes
  • 2071-05-31 - 5 hours 12 minutes UTC

If you're explicitly specifying the timezone, some versions of the timespec might only allow UTC for the optional timezone argument.

Example

cat script.sh | at now + $(($RANDOM % 10)) hours $(($RANDOM % 60)) minutes

at -f script.sh now + $(($RANDOM % 10)) hours $(($RANDOM % 60)) minutes

Try it out...

You can test the bash parsing by pre-pending echo and escaping the | (pipe).

echo cat script.sh \| at now + $(($RANDOM % 10)) hours $(($RANDOM % 60)) minutes

echo at -f script.sh now + $(($RANDOM % 10)) hours $(($RANDOM % 60)) minutes

To see jobs scheduled, use atq and job contents (environment vars, setup, and command/script) with at -c [jobid].

Note

The system is part of cron, and the interactive prompt actually captures the whole current state of your shell, so you can run commands without specifying absolute paths.

Solution 10 - Bash

You can try with this example to use random times before execute command:

#!/bin/bash
# start time
date +"%H:%M:%S"
    
# sleep for 5 seconds
sleep $(shuf -i 1-25 -n 1)
# end time
date +"%H:%M:%S"

Solution 11 - Bash

For those who googled the way here:

If you are using anacron(Ubuntu desktop and laptop) then you can edit

/etc/anacrontab

and add

RANDOM_DELAY=XX 

Where XX is the amount of minutes you want to delay the base job.

Anacron is like cron but it does not expect your computer to be on 24x7 (like our laptops) and will run the scripts that it missed because the system was down.

Solution 12 - Bash

What about creating a script that rewrites the crontab every day?

  1. Read current crons (A)
  2. Pick random times (B)
  3. Rewrite prior crons (A), add new random crons (B)
  4. Make sure to add to the cron to run this script in the first place.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionfloatleftView Question on Stackoverflow
Solution 1 - Bashal-xView Answer on Stackoverflow
Solution 2 - BashDaveView Answer on Stackoverflow
Solution 3 - BashGordon DavissonView Answer on Stackoverflow
Solution 4 - BashMicah ElliottView Answer on Stackoverflow
Solution 5 - Bash131View Answer on Stackoverflow
Solution 6 - BashklausView Answer on Stackoverflow
Solution 7 - BashWilbertView Answer on Stackoverflow
Solution 8 - BashMLPView Answer on Stackoverflow
Solution 9 - BashmcintView Answer on Stackoverflow
Solution 10 - BashHoussin BoullaView Answer on Stackoverflow
Solution 11 - BashGanesh KrishnanView Answer on Stackoverflow
Solution 12 - BashmellofellowView Answer on Stackoverflow