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. Tashkinov– Artem S. Tashkinov2021-09-19 09:59:25 +00:00Commented Sep 19, 2021 at 9:59
-
5Does this answer your question? How to schedule cronjob for every 45 days?ilkkachu– ilkkachu2021-09-19 10:50:54 +00:00Commented Sep 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.)ilkkachu– ilkkachu2021-09-19 10:52:53 +00:00Commented Sep 19, 2021 at 10:52
-
4May I ask why 64 hours specifically?MechMK1– MechMK12021-09-19 22:48:44 +00:00Commented Sep 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 Hemmelgarn– Austin Hemmelgarn2021-09-21 11:31:55 +00:00Commented Sep 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
-
16could check every 8 hours too (highest common factor of 24 and 64)somebody– somebody2021-09-19 22:22:24 +00:00Commented Sep 19, 2021 at 22:22
-
8There is also the issue that
date %sgives seconds in UTC andcronruns 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.user232326– user2323262021-09-20 03:50:10 +00:00Commented Sep 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.Stéphane Chazelas– Stéphane Chazelas2021-09-20 06:16:18 +00:00Commented Sep 20, 2021 at 6:16 -
2@Griffin, by
crontab(5)man page has in theLIMITATIONSsection: 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 +%sitself is independent on timezone.Stéphane Chazelas– Stéphane Chazelas2021-09-20 14:16:44 +00:00Commented Sep 20, 2021 at 14:16 -
1@Griffin A
4 3 * * *will be executed at03:04:00local time, even with a TZ set for the crontab. Only the actual commands, likedatehere:4 3 * * * date, will be affected by such TZ environment variable.user232326– user2323262021-09-20 14:32:49 +00:00Commented Sep 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.FelixJN– FelixJN2021-09-19 10:21:01 +00:00Commented Sep 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.ilkkachu– ilkkachu2021-09-19 10:56:13 +00:00Commented Sep 19, 2021 at 10:56
-
@FelixJN That's actually a good idea, I've edited my answer using your suggestion.polemon– polemon2021-09-19 18:45:18 +00:00Commented Sep 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
dataoutput. (More timing error can happen if the system is sometimes swap thrashing or mid reboot, or worse hibernated, when theattime comes. Big delay in commands running.)Peter Cordes– Peter Cordes2021-09-20 14:45:45 +00:00Commented 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.Scottie H– Scottie H2021-09-20 17:20:16 +00:00Commented 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.Bob– Bob2021-09-21 09:31:01 +00:00Commented Sep 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.Bob– Bob2021-09-21 09:33:09 +00:00Commented Sep 21, 2021 at 9:33 -
1@Bob Agreed regarding
After=etc.. Be careful:Persistent=trueonly applies to events scheduled withOnCalendar, a missed 64h-interval viaOn(Unit)ActiveSecwill not be accounted for. See official documentationFelixJN– FelixJN2021-09-21 10:18:13 +00:00Commented Sep 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.Bob– Bob2021-09-21 14:08:46 +00:00Commented Sep 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.