I need to run a script every 64 hours. I couldn't find the answer with cron
. Is it possible with it, or should I use a loop in a shell script?
-
4Does your OS use systemd? If it does use systemd timer units: linuxconfig.org/…– Artem S. TashkinovSep 19, 2021 at 9:59
-
5Does this answer your question? How to schedule cronjob for every 45 days?– ilkkachuSep 19, 2021 at 10:50
-
1Cron doesn't bend to that too well directly. See How to schedule cronjob for every 45 days? and How to set cron job for every 5 hours for some ways of dealing with it. Adjust for the desired time interval. (Ignore my answer to the first one, it won't help you here. The other ones would.)– ilkkachuSep 19, 2021 at 10:52
-
4May I ask why 64 hours specifically?– MechMK1Sep 19, 2021 at 22:48
-
2Looking at this question a second time, it’s sounding to me like an XY problem. If we were to know why it must be every 64 hours, we may be able to provide a solution that actually avoids that requirement altogether and better fits the required use case.– Austin HemmelgarnSep 21, 2021 at 11:31
5 Answers
Just run every hour and check that the number of hours since some arbitrary instant (for instance, instant 0 of the Unix epoch time) is a multiple of 64:
0 * * * * t=$(date +\%s); [ "$(( (t / 3600) \% 64 ))" -eq 0 ] && your-command
-
16
-
8There is also the issue that
date %s
gives seconds in UTC andcron
runs on local time. At DST changes a whole hour is erased or added to the local time. That should not be a problem here as the script is called "every hour", but worth noting for longer intervals.– user232326Sep 20, 2021 at 3:50 -
7@somebody, I did consider it doing it every 8 hours, but that wouldn't work in locales that implement DST. For instance in mainland UK and currently, that would have to be
0 0,8,16 * * *
in winter and0 1,9,17 * * *
in summer. Sep 20, 2021 at 6:16 -
2@Griffin, by
crontab(5)
man page has in theLIMITATIONS
section: Even if a user specifies the TZ environment variable in his crontab this will affect only the commands executed in the crontab, not the execution of the crontab tasks themselves..date +%s
itself is independent on timezone. Sep 20, 2021 at 14:16 -
1@Griffin A
4 3 * * *
will be executed at03:04:00
local time, even with a TZ set for the crontab. Only the actual commands, likedate
here:4 3 * * * date
, will be affected by such TZ environment variable.– user232326Sep 20, 2021 at 14:32
I suggest perhaps using a crontab "front end" like crontab.guru for figuring out crontab if you're a beginner.
However, as in your case, the hour setting only allows for values of 0 to 23, so you can't use crontab here.
Instead, I'd suggest using at
. In your case, I'd probably use something like:
at now + 64 hours
and then enter your command or
echo "<your command>" | at now + 64 hours
at the beginning of your script, etc.
Basically, you'll be scheduling running the command right when the command has been invoked the last time. Also, if you don't want a time delta, rather the exact time, I suggest doing a bit of time arithmetic, and then use an exact time with at
to have the command run.
I highly suggest reading the man page of at
, as it is fairly comprehensive.
-
17I'd suggest to add the new
at
-scheduling at the beginning of the script, otherwise a failure during script execution will also stop scheduling the next run.– FelixJNSep 19, 2021 at 10:21 -
1crontab.guru is nice and pretty, but isn't worth anything for any of the special cases that cron can't directly do, but that crop up from time to time. Like intervals that don't divide cleanly into days or months, or running something on the first/last $weekday of a month.– ilkkachuSep 19, 2021 at 10:56
-
@FelixJN That's actually a good idea, I've edited my answer using your suggestion.– polemonSep 19, 2021 at 18:45
-
@FelixJN: That also reduces the amount of timing error this will accumulate, but still not to zero. A fraction of a second every 64 hours is trivial for most use-cases, but if it matters you could maybe round down to the start of the hour somehow, maybe parsing
data
output. (More timing error can happen if the system is sometimes swap thrashing or mid reboot, or worse hibernated, when theat
time comes. Big delay in commands running.) Sep 20, 2021 at 14:45 -
On my *nix systems. "AT" only runs if the user is logged in to the machine at the time. Sep 20, 2021 at 17:20
cron
is made to run things on date or clock events, i.e. every 1st of a month or week or every third hour of a day, respectively.
For running things in time intervals that cannot be simply expressed by arithmetics on clock time or dates, I'd suggest using a systemd
timer - which of course works only if you have systemd
running on your respective device.
You will need two files, the service file to run the script and the timer file for managing the events.
$cat /etc/systemd/system/myscript.service
[Unit]
Description=this service runs my script
[Service]
ExecStart=/full/path/to/myscript.sh
#[Install]
#WantedBy=multi-user.target
This service simply executes the script without checking if it succeeded or anything. Optionally enable the service via systemctl enable myscript.service
which means it will be executed once during boot time - I commented this out, because the timer will take care of boot events. You may manually run your script by systemctl start myscript.service
.
$cat /etc/systemd/system/myscript.timer
[Unit]
Description=this timer runs myscript.service every 64h
[Timer]
OnBootSec=120
OnUnitActiveSec=64h
[Install]
WantedBy=timers.target
This is the timer to run the myscript.service
every 64h since the last activation of the service as well as two minutes after boot time (to ensure that the system is in the right state for the service to run).
Note that the timer will not remember the 64h if you reboot/power off. Enable and start the timer accordingly:
systemctl enable myscript.timer
systemctl start myscript.timer
Since the timer only starts after a boot or since the last unit activation, you will have to run the service once to ensure the timer runs:
systemctl start myscript.service
Check the status of the timers by
systemctl list-timers
-
1Also, consider
OnActiveSec=
instead ofOnUnitActiveSec=
if you don't want the timer to be dependent on when the unit was last run, e.g. if you want the timer to run every 64h even if you manually run the unit at other times.– BobSep 21, 2021 at 9:31 -
2Also, using a fixed 120s timer to try to "ensure that the system is in the right state" is a bad idea. Use
After=
withWants=
orRequires=
to more explicitly specify dependencies so that you know when they are available, rather than guessing 120s is enough in all situations.– BobSep 21, 2021 at 9:33 -
1@Bob Agreed regarding
After=
etc.. Be careful:Persistent=true
only applies to events scheduled withOnCalendar
, a missed 64h-interval viaOn(Unit)ActiveSec
will not be accounted for. See official documentation– FelixJNSep 21, 2021 at 10:18 -
Ah, I had missed that line. Good point! I'll delete my other comment to avoid confusion. That does restrict options, then, since
OnCalendar=
runs into the same problems cron does -- leaving your post-boot catchup the best option if it must run on boot.– BobSep 21, 2021 at 14:08
64 hours is 2 and 2/3 days, so 3 * 64 hours = 192 hours = 8 days. So you could almost do
0 0 1-25/8 * * foo
0 16 3-27/8 * * foo
0 8 6-30/8 * * foo
except that you get a short pattern at the end of the month (if the month has 30 days, then there will be only 16 hours between the run at 08:00 on the 30th and the one at 00:00 on the 1st; if the month has 31 days, it will be 40 hours; if the month has 28 days, it will be 32 hours between the run at 16:00 on the 27th and the one at 00:00 on the 1st; if the month has 29 days, it will be 56), and also things will be off by 1 hour whenever your locality goes on or comes off of summer time (assuming it does).
Doesn't quite meet your specs (and I endorse any of the other answers that do), but it is an interesting exercise in making do.
Surprised no one has mentioned either of:-
Anacron
It's supplied as standard on most distro's.