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
;
after then
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 accepts tomorrow
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....
Best Answer
[
is neither a metacharacter nor a control operator (not even a reserved word; same for]
) thus it needs whitespace around it. Otherwise the shell "sees" the command[01=01]
instead of the command[
with the separate parameters01
,=
,01
, and]
. Each operator and operand needs to be a separate argument to the[
command, so whitespace is necessary around the operators as well.[$month="01"]
is a wildcard pattern matching any of the characters in$month
or"01
. If it doesn't match anything, it's left alone.If there is a semicolon after the closing bracket, you don't need a space before it, because the semicolon is always part of a separate token.
The same goes for bash's (and ksh's and zsh's) double bracket syntax.
More than one condition
There are two ways to combine conditions:
within
[
with separate
[
commands combined with&&
or||
Grouping with brackets is probably easier within
[
.The first one should be avoided as it's unreliable (try for instance with
month='!'
). Problems with strange variable content can be avoided by using the safe string (if there is one) first; or by using[[
/]]
instead of[
/]
: