Schedule the last day of every month
Solution 1
Abstract
The correct code should be:
#!/bin/sh
[ "$#" -eq 0 ] && echo "Usage: $0 command [args]" && exit 1
[ "$(date -d tomorrow +'%d')" = 01 ] || exit 0
exec "$@"
Call this script end_of_month.sh
and the call in cron is simply:
00 12 28-31 * * /path/to/script/end_of_month.sh command
That would run the script end_of_month
(which internally will check that the day is the last day of the month) only on the days 28, 29, 30 and 31. There is no need to check for end of month on any other day.
Old post.
That is a quote from the book "Linux Command Line and Shell Scripting Bible" by Richard Blum, Christine Bresnahan pp 442, Third Edition, John Wiley & Sons ©2015.
Yes, that is what it says, but that is wrong/incomplete:
- Missing a closing
fi
. - Needs space between
[
and the following`
. - It is strongly recommended to use $(…) instead of
`…`
. - It is important that you use quotes around expansions like
"$(…)"
- There is an additional
;
afterthen
How do I know? (well, by experience ☺ ) but you can try Shellcheck. Paste the code from the book (after the asterisks) and it will show you the errors listed above plus a "missing shebang". An script without any errors in Shellcheck is this:
#!/bin/sh
if [ "$(date +%d -d tomorrow)" = 01 ] ; then script.sh; fi
That site works because what was written is "shell code". That is a syntax that works in many shells.
Some issues that shellcheck doesn't mention are:
It is assuming that the date command is the GNU date version. The one with a
-d
option that acceptstomorrow
as a value (busybox has a -d option but doesn't understand tomorrow and BSD has a-d
option but is not related to "display" of time).It is better to set the format after all the options
date -d tomorrow +'%d'
.The cron start time is always in local time, that may make one job start 1 hour earlier of later than an exact day count if the DST (daylight saving time) got set or unset.
What we got done is a shell script which could be called with cron. We can further modify the script to accept arguments of the program or command to execute, like this (finally, the correct code):
#!/bin/sh
[ "$#" -eq 0 ] && echo "Usage: $0 command [args]" && exit 1
[ "$(date -d tomorrow +'%d')" = 01 ] || exit 0
exec "$@"
Call this script end_of_month.sh
and the call in cron is simply:
00 12 28-31 * * /path/to/script/end_of_month.sh command
That would run the script end_of_month
(which internally will check that the day is the last day of the month) only on the days 28, 29, 30 and 31. There is no need to check for end of month on any other day.
Make sure the correct path is included. The PATH inside cron will not (not likely) be the same as the user PATH.
Note that there is one end of month script tested (as indicated below) that could call many other utilities or scripts.
This will also avoid the additional problem that cron generates with the full command line:
- Cron splits the command line on any
%
even if quoted either with'
or"
(only a\
works here). That is a common way in which cron jobs fail.
You can test if end_of_month.sh
script works correctly on some date (without waiting to the end of the month to discover it doesn't work) by testing it with faketime:
$ faketime 2018/10/31 ./end_of_month echo "Command will be executed...."
Command will be executed....
Solution 2
Assuming that the syntax errors are fixed, and the command reformulated slightly to be less verbose:
00 12 28-31 * * [ "$( date -d tomorrow +\%d )" != "01" ] || command1
This runs date +%d -d tomorrow
(assuming that it's GNU date
that is used) to get tomorrow's date as a two-digit number. If the number isn't 01
, then today is not the last day of the month. In that case, the tests succeeds and command1
is not executed. The job is run at noon on the days that could possibly be the last day of the month.
The original command:
00 12 * * * if [`date +%d -d tomorrow` = 01 ] ; then ; command1
This has a few issues:
- No space after
[
. - A
;
directly afterthen
. -
%
is special in cron job specifications and must be escaped as\%
(seeman 5 crontab
). - There is no final
fi
at the end that matches up with theif
.
Related videos on Youtube
![AbstProcDo](https://i.stack.imgur.com/K6k1M.png?s=256&g=1)
AbstProcDo
Updated on September 18, 2022Comments
-
AbstProcDo almost 2 years
I read from an instruction to schedule a script on the last day of the month:
Note:
The astute reader might be wondering just how you would be able to set a command to execute on the last day of every month because you can’t set the dayofmonth value to cover every month. This problem has plagued Linux and Unix programmers, and has spawned quite a few different solutions. A common method is to add an if-then statement that uses the date command to check if tomorrow’s date is 01:00 12 * * * if [`date +%d -d tomorrow` = 01 ] ; then ; command1
This checks every day at 12 noon to see if it's the last day of the month, and if so, cron runs the command.
How does
[`date +%d -d tomorrow` = 01 ]
work?
Is it correct to statethen; command1
?-
Michael Homer over 5 yearsAre you sure that's verbatim what it says? As written here it in fact doesn't work.
-
AbstProcDo over 5 yearsI posted the snapshot.@MichaelHomer
-
Michael Homer over 5 yearsThanks! I've fiddled with the formatting to make it match - it's still not quite right, but it is what the picture says.
-
danblack over 5 yearsMissing
; endif
? -
Kusalananda over 5 yearsIt doesn't work. It contains syntax errors: No space after
[
and nofi
at the end. Also,%
is special in crontabs.
-
-
Stéphane Chazelas over 5 yearsThe problem with using
[ ... ] && command1
instead ofif...
is that on days that are not the last day of the month, the cron job will end with a non-zero exit status and that failure may have to be reported. Using[ "$(...)" != 01 ] || command1
is another way to avoid the problem. -
Stéphane Chazelas over 5 yearsAnother problem with the original code is the
;
betweenthen
andcommand1
. -
Stéphane Chazelas over 5 yearsast-open
date
(or thedate
builtin of ksh93 if ksh93 was built as part of ast-open) does supportdate -d tomorrow +%s
ordate +%s tomorrow
). -
Kusalananda over 5 years@StéphaneChazelas Another reason why I dislike one-liners, they are difficult to read.
-
done over 5 yearsExperience has proven (many times) that it is a lot better to test the script correctly running with faketime than it is to wait till the end of the month to find that the scheduled job didn't work. Like discovering that
* * * * * echo "$(date -u +'date %c')" >>~/testfile
wont work because one forgot to quote the\%
(which becomes difficult to debug if only one try every month is posible). @Kusalananda -
done over 5 yearsThe time difference between consecutive runs of the command might not be an integer amount of (24 hour) days as a DST change will shift the start time of cron.
-
cat over 5 yearsWhy not quote the percent sign?
"+%d"
, orcron
really looks for%
as a top-level token? -
done over 5 years@cat It doesn't matter how you quote, either
"
or'
(except\
), the percent sign%
will make cron break the line in two parts. That is one usual way to get cron to fail. -
Daya almost 5 years
00 20 28-31 * * if [ "$( date +\%d -d tomorrow )" = "01" ]; then sh /home/monitor/test.sh; fi
This Resolved my Month end Cronjob problem.