Cron jobs and random times, within given hours
Asked Answered
P

14

144

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.

Psychodiagnostics answered 28/1, 2012 at 23:15 Comment(1)
The question is not very well posed. Ultimately you want to distribute 20 points on the time axis between 9am and 11am. But are there constraints on the minimum time difference? Is doing nothing between 9am and 10:30am acceptable? The only way to do this acceptably seems to Klaus' idea: select your 20 times at 09:00, which allows you to fulfill any constraints you might have, then schedule things with "at".Knives
H
44

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.

Hypogenous answered 29/1, 2012 at 4:47 Comment(4)
You can make the arithmetic assignments more readable by dropping the dollar sign and moving the double parens to the left (e.g. ((maxdelay = 14 * 60)) or ((delay = $RANDOM % maxdelay))). The sleep argument still needs to be the way you have it (although you could add spaces, if desired).Negligee
This worked for me too. My custom bash script looks like below sleep $[ ( $RANDOM % 60 ) + 1 ]s && some_script.shZulmazulu
Am I missing something or the maximum delay should be set to maxdelay=$((14*60/20))Altamirano
@jenishSakhiya The random delays for each of the 20 runs is absolute (well, starting at 9am), not relative to another of the runs. That is, if one of the random delays comes up as 13 hours, that means it'll run at 10pm (13 hours after 9am), not 13 hours after any of the other runs.Hypogenous
C
191

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.

Coonhound answered 29/4, 2013 at 23:7 Comment(8)
I like the late answer. But if you're trying to generate a random integer evenly distributed in the range of 10 to 99, and the output of RANDOM is 0 to 32,767, why wouldn't you just do $[(RANDOM/368)+10]?Gingerly
@jsdalton: Wouldn't the modulo operator be better? $((RANDOM % 90 + 10)) Test: for i in {0..9999}; do echo $((RANDOM % 90 + 10)); done | sort | uniq -cRattigan
On many systems cron does not use bash by default so it could be better to avoid the bashism $RANDOM: sleep $(( $(od -N1 -tuC -An /dev/urandom) \% 90 ))m.Osteoblast
Make sure that crontab is using bash before you use $RANDOM. If you have vixie-cron (seems to be my case on Ubuntu), then you can add SHELL=/bin/bash to the top. There are more alternatives for other cron versions here: superuser.com/a/264541/260350Moccasin
When I use the first suggestion above, I get crontab: errors in crontab file, can't install. Do you want to retry the same edit? please helpCrispas
${RANDOM:0:2} will never be a single digit number so ${RANDOM:1:2} would give better randomness while having a very small possibility of returning an empty string in case ${RANDOM} was a single-digit number.Evvie
@Seano Precisely because of what DaAwesomeP points out in the comment before yours.Tentage
any advise on randomly once every 36 hours?Jumbled
P
99

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

Parodic answered 26/3, 2015 at 19:55 Comment(4)
::MindBLOWN:: another obscure place to use a little bit of perl.Defection
This is definitely the cleanest answerDanyelldanyelle
This is kind of inefficient though, as you are creating a lot of processes.Quillan
Ruby (Perl's baby) is also a short and nice solution if you don't have perl installed ruby -e 'sleep rand 900'Whalebone
H
44

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.

Hypogenous answered 29/1, 2012 at 4:47 Comment(4)
You can make the arithmetic assignments more readable by dropping the dollar sign and moving the double parens to the left (e.g. ((maxdelay = 14 * 60)) or ((delay = $RANDOM % maxdelay))). The sleep argument still needs to be the way you have it (although you could add spaces, if desired).Negligee
This worked for me too. My custom bash script looks like below sleep $[ ( $RANDOM % 60 ) + 1 ]s && some_script.shZulmazulu
Am I missing something or the maximum delay should be set to maxdelay=$((14*60/20))Altamirano
@jenishSakhiya The random delays for each of the 20 runs is absolute (well, starting at 9am), not relative to another of the runs. That is, if one of the random delays comes up as 13 hours, that means it'll run at 10pm (13 hours after 9am), not 13 hours after any of the other runs.Hypogenous
P
32

Some cron implementations offer a RANDOM_DELAY variable.

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

See crontab(5) for details.

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.

Pedro answered 15/1, 2016 at 16:50 Comment(7)
I would love to use the RANDOM_DELAY variable, but can't find any hint in the manpage of crontab(5) on Ubuntu 14.04.4 LTS.Placido
That's unfortunate. I wonder if it's not supported there. I see it documented in that manpage on Centos 7 and Arch Linux.Pedro
this seems like the correct answer but can you put an example?Tremaine
Please note that RANDOM_DELAY is established once and remains constant for the whole runtime of the daemon.Hopeh
That variable does not seem to exist even in Ubuntu 19.04 man page. cron package is 3.0pl1-128.1ubuntu1Troublemaker
The RANDOM_DELAY flag is feature of cronie-crond while Ubuntu seems to be running vixie-cron which lacks this flag.Gibby
I had to run through a good handful of man pages to find it, but RANDOM_DELAY is described in info crontab as well as man 5 crontab for cronie on CentOS 8. I was starting to suspect that it wasn't there, but I was just looking at section 1 instead of section 5.Cosmic
Y
11

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 
Yale answered 14/1, 2016 at 9:12 Comment(2)
If someone wonders, like me: %N provides current nanos, but some man pages are lacking information for it. This is a very clever solution for people which just need "some randomness" easily per command.Soothsay
Apart from the caveats already covered elsewhere, this will only work if you have GNU date (which you probably do on most Linuxes, but not on Busybox, standard MacOS, or various other BSD-derived platforms).Tentage
E
7

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.

Envelop answered 29/1, 2012 at 20:53 Comment(1)
An example of using at is here: https://mcmap.net/q/161139/-random-cron-jobSemibreve
F
5

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
Fimbriation answered 11/4, 2019 at 14:43 Comment(2)
I tried everything and it still didn't work for me. You post explained why, thanks!Vedda
$[ ... ] is deprecated syntax since waaaay back; for anything from this millennium, you would prefer $((RANDOM\%90))m which is POSIX-compatible syntax (but of course RANDOM is still Bash only).Tentage
L
4

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"
Liter answered 29/3, 2020 at 20:48 Comment(0)
D
3

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.

Disinfection answered 18/12, 2017 at 4:21 Comment(0)
C
3

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.

Catastrophism answered 18/7, 2020 at 17:36 Comment(2)
Welcome to SO. Well, old thread or not, this sent me off to ~$info dd and I can understand what's going on on the left side of |, but can't make out the right side. So, for me and others interested in overcoming some restrictions in random value generation, why not take a moment to explain the RHS, and make a stronger pitch for using your approach. The depth of explanation both makes people comfortable with the process you suggest and its benefits Thanks.Angelenaangeleno
Ah ok. od aka "octal dump" takes a file or stdin and dumps the binary data in human-readable form. We want our number shown as an unsigned decimal (-t u4), and don't want the address index (-A none). The -N4 is redundant as we take only 4 bytes, but doesn't hurt either. I hope this explains it...Catastrophism
A
2

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.
Amphimacer answered 17/6, 2020 at 16:30 Comment(4)
Don't circumvent the reputation system by posting comments as answers. Your comment does however look good enough to actually be an answer. I recommend removing "I don't have rep to add a comment, but".Skinnydip
I just reviewed this answer and missed the part where you're asking a question in return at the end (sloppy work on my part). Don't ask questions in answers. Post your own question for questions you have. I made your semi-question into something that could pass for an answer.Skinnydip
Got it! Thx. I'll be more careful next time, no ill intent was meant.Amphimacer
I'm sure you had no ill intent and you don't need to be overly careful. You outlined a possible solution - and stumbled a bit at the end. No worries!Skinnydip
C
1

I was looking for the same problem, for me the best solution was to create a schedule on the server side (it's very simple by means of a programming language), populate a db table with these tasks with two columns: run_time and status (new | processed). So my cron runs every minute and finds jobs that need to be done (status = new and running time <= now()). Very simple and there is one huge plus: in this case I can make an interface for it and see what happens...

Crazyweed answered 27/10, 2022 at 19:23 Comment(0)
T
0

As it's not mentioned by now, I will show another clean way: Using 'jot'

jot comes on every BSD unix (available as athena-jot in many linuxes, too) and is a sequential and random(!) number generator like seq but easier and more powerful. This simple command:

sleep $(jot -nr 1 1 3600); scriptname

in your crontab executes 'scriptname' after a delay of 1 to 3600 seconds of sleep.

jot -nr 1 1 360

means: give one (first 1) random (-r) number without newline (-n) in the range of one (second number - here 1) and 3600 (third number - here 3600) Clean syntax.

Tragopan answered 9/3 at 16:25 Comment(0)
P
-1

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.

Peterson answered 20/2, 2018 at 6:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.