68

When configuring cron to run a command every other day using the "Day of Month" field, like so:

1 22 */2 * * COMMAND

it runs every time the day of month is odd: 1,3,5,7,9 and so on.

How can I configure cron to run on days of month that are even like 2,6,8,10 and so on (without specifying it literally, which is problematic as every month has a different number of days in the month)?

1
  • 3
    The wording of this question is misleading. Apparently the OP wanted to run a command on even-numbered dates, for example, January 30 and February 2, and not ‘‘every other day’’. Commented Jan 25, 2020 at 21:48

5 Answers 5

104

The syntax you tried is actually ambiguous. Depending on how many days are in the month, some months it will run on odd days and some on even. This is because the way it is calculated takes the total number of possibilities and divides them up. You can override this strage-ish behavior by manually specifying the day range and using either an odd or even number of days. Since even day scripts would never run on the 31st day of longer months, you don't lose anything using 30 days as the base for even-days, and by specifying specifically to divide it up as if there were 31 days you can force odd-day execution.

The syntax would look like this:

# Will only run on odd days:
0 0 1-31/2 * * command

# Will only run on even days:
0 0 2-30/2 * * command

Your concern about months not having the same number of days is not important here because no months have MORE days than this, and for poor February, the date range just won't ever match the last day or two, but it will do no harm having it listed.

The only 'gotcha' for this approach is that if you are on an odd day cycle, following months with 31 days your command will also run on the first of the month. Likewise if you are forcing an even cycle, each leap year will cause one three-day cycle at the end of February. You cannot really get around the fact that any regular pattern of "every other day" is not always going to fall on even or odd days in every month and any way you force this you will either have an extra run or be missing a run between months with mismatched day counts.

5
  • 3
    Thanks, but what will happen on months like February where you have only 28 days? The star actually takes care of that - but is indeed ambiguous.
    – freddie
    Commented Jul 5, 2011 at 12:58
  • 4
    @freddie: See my edited answer ... but it's a non-issue because out of range values will just be ignored, nothing will happen on the 30th or 31st of February. Ever. You could manually specify with a list like 0,2,4...,30,32,34 and it wouldn't matter, the out of range values would just never get matched.
    – Caleb
    Commented Jul 5, 2011 at 13:01
  • 1
    Thanks! I understand, thank for you the informative answer!
    – freddie
    Commented Jul 5, 2011 at 13:03
  • 3
    On Ubuntu server 8.04, it seems that the syntax using day-of-month zero is invalid (bad day-of-month). The following syntax, however, is accepted: 0 0 2-30/2 * * command
    – user31053
    Commented Jan 24, 2013 at 7:37
  • 1
    Fedora and RHEL 5,6,7 also do not like 0 as a day of month. As user31053 pointed out: 0 0 2-30/2 * * command works as expected.
    – NoelProf
    Commented Jun 27, 2014 at 15:55
8

I think a possibility is using the day of the year, such like this:

# for odd days
test $(((`date +%j` % 2))) != 0 && command

# for even days
test $(((`date +%j` % 2))) == 0 && command

It is tested for Unix and Linux systems.

5
  • 1
    I prefer this answer because it skips the problem with the odd number of days with some months. Anyway I suggest the dollar notation instead backticks: test $(($(date +%j) % 2)) == 0 && command
    – caligari
    Commented Apr 24, 2018 at 10:31
  • I have reviewed that %j is not the Julian date, so the best code avoiding the New Year transition must be calculated with seconds: test $(($(date +%s) / 86400 % 2)) == 0 && command
    – caligari
    Commented Apr 24, 2018 at 10:54
  • Thanks for the comments! Our proposal worked for us like a charm. We used that scheme to run every day crons on different server nodes, all they sharing the same crontab, but the script only allowing to run the script on one of them. However, if we need to make it more specific, we will consider your proposal. Thanks!!!
    – Jordi
    Commented Apr 25, 2018 at 15:46
  • 1
    Strip leading zeros with date +%-j to avoid errors like "008: value too great for base."
    – elbowich
    Commented Jan 8, 2020 at 13:09
  • Oh! Thanks for the point! It improves my proposal! Cheers
    – Jordi
    Commented Jan 9, 2020 at 16:13
3

Let's check every day if it's an "other" :-) (bc program required)

0 0 * * * test $(echo `date +%s` / 86400 % 2 == 0 |bc) -eq 0 && command

(I'm not sure the code appears correctly. The date +%s part is between back-apostrophes.)

3
  • This will run every other day but does not answer the question! It will still run sometimes on odd days sometimes on even days depending on the month. This has the same result as the code in the question, you are just arriving at the result by doing your own math on the seconds since epoch. This runs on even days since epoch but not on even days of our calendar.
    – Caleb
    Commented Apr 23, 2014 at 11:38
  • I'd recommend using nested $(...) instead of backticks here because it has no escaping issues and reader will notice it better than backticks. That said, this is the best answer because it doesn't have discontinuity in "every other day" pattern when month or year changes. Commented Apr 6, 2023 at 8:38
  • To select odd vs even date, use -eq 1 instead of -eq 0. Commented Apr 6, 2023 at 8:41
2

In general, I would run it every day and have the script use logic to determine it if it should be run today.

Making a simple status file telling when last run and then compairing would work very easily.

If it needs to be run through different sources, make it argument-dependant.

0

Personally I avoid using the ((jultest=$juledate/2)) approach as it has issues with numbers larger than base 10.

As another user has suggested - bc can provide a more robust solution.

juldate=$(date +%j)
jultest=$(echo "obase=16;$juldate*2" | bc)
if [ $juldate -ne $jultest ]
then
  echo "This is an odd numbered week"
fi

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .