77

I am using Bash on RedHat. I need to schedule a cron job to run at at 9:00 AM on first Sunday of every month. How can I do this?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
ring bearer
  • 20,383
  • 7
  • 59
  • 72

11 Answers11

180

You can put something like this in the crontab file:

00 09 * * 7 [ $(date +\%d) -le 07 ] && /run/your/script

The date +%d gives you the number of the current day, and then you can check if the day is less than or equal to 7. If it is, run your command.

If you run this script only on Sundays, it should mean that it runs only on the first Sunday of the month.

Remember that in the crontab file, the formatting options for the date command should be escaped.

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
Lukasz Stelmach
  • 5,281
  • 4
  • 25
  • 29
  • this is brilliant where is this `-le` stuff documented? I need to do the same for 1st sunday, 2nd sunday 3rd sunday 4th sunday and (maybe) 5th sunday... – Mr Heelis Nov 26 '15 at 12:35
  • Same as with `test` command (these is shortcut for these command): http://linux.die.net/man/1/test – Lukasz Stelmach Nov 27 '15 at 13:16
31

It's worth noting that what looks like the most obvious approach to this problem does not work.

You might think that you could just write a crontab entry that specifies the day-of-week as 0 (for Sunday) and the day-of-month as 1-7, like this...

# This does NOT work.
0 9 1-7 * 0 /path/to/your/script

... but, due to an eccentricity of how Cron handles crontab lines with both a day-of-week and day-of-month specified, this won't work, and will in fact run on the 1st, 2nd, 3rd, 4th, 5th, 6th, and 7th of the month (regardless of what day of the week they are) and on every Sunday of the month.

This is why you see the recommendation of using a [ ... ] check with date to set up a rule like this - either specifying the day-of-week in the crontab and using [ and date to check that the day-of-month is <=7 before running the script, as shown in the accepted answer, or specifying the day-of-month range in the crontab and using [ and date to check the day-of-week before running, like this:

# This DOES work.
0 9 1-7 * * [ $(date +\%u) = 7 ] && /path/to/your/script

Some best practices to keep in mind if you'd like to ensure that your crontab line will work regardless of what OS you're using it on:

  • Use =, not ==, for the comparison. It's more portable, since not all shells use an implementation of [ that supports the == operator.
  • Use the %u specifier to date to get the day-of-week as a number, not the %a operator, because %a gives different results depending upon the locale date is being run in.
  • Just use date, not /bin/date or /usr/bin/date, since the date utility has different locations on different systems.
Mark Amery
  • 143,130
  • 81
  • 406
  • 459
  • 2
    This approach triggers the check 7 times each month, 6 times doing nothing. The approach to check on each Sunday whether the day is at most 7 triggers the check 4 to 5 times each month, 3 to 4 times doing nothing. So the later approach is a littler more efficient. Plus, it only triggers on Sundays when you can afford the overhead more easily. Admitted, this probably does not make any difference in practice... – Christopher K. Feb 15 '19 at 10:06
  • Yeah, Lucasz's answer is better overall, but the caveat given in this answer is definitely worth knowing. – mwfearnley Oct 07 '22 at 12:11
15

You need to combine two approaches:

a) Use cron to run a job every Sunday at 9:00am.

 00 09 * * 7     /usr/local/bin/once_a_week

b) At the beginning of once_a_week, compute the date and extract the day of the month via shell, Python, C/C++, ... and test that is within 1 to 7, inclusive. If so, execute the real script; if not, exit silently.

Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
10

There is a hacky way to do this with a classic (Vixie, Debian) cron:

0 9 1-7 * */7

The day-of-week field starts with a star (*), and so cron considers it "unrestricted" and uses the AND logic between the day-of-month and the day-of-week fields.

*/7 means "every 7 days starting from weekday 0 (Sunday)". Effectively, this means "every Sunday".

Here's my article with more details: Schedule Cronjob for the First Monday of Every Month, the Funky Way

Note – it's a hack. If you use this expression, make sure to document it to avoid confusion later.

Pēteris Caune
  • 43,578
  • 6
  • 59
  • 81
  • 2
    This should be the accepted answer. Just uses cron, no additional scripting needed. As an added bonus this is the only solution that also works for scheduling monthly jobs in Jenkins. – Earl Ruby Jun 24 '23 at 00:20
  • It's not a hack if the native cron allows it. – Thanh Trung Jul 30 '23 at 16:02
6

A hacky solution: have your cron job run every Sunday, but have your script check the date as it starts, and exit immediately if the day of the month is > 7...

thesunneversets
  • 2,560
  • 3
  • 28
  • 46
5

This also works with names of the weekdays:

0 0 1-7 * * [ "$(date '+\%a')" == "Sun" ] && /usr/local/bin/urscript.sh

But,

[ "$(date '+\%a')" == "Sun" ] && echo SUNDAY

will FAIL on comandline due to special treatment of "%" in crontab (also valid for https://stackoverflow.com/a/3242169/2919695)

Community
  • 1
  • 1
maniac_on_moon
  • 129
  • 2
  • 4
  • 1
    This is similar to the one above but I like it better for readability. – Adam Plumb May 18 '15 at 23:28
  • this should be the correct answer, what I would say is that the second part of maniac_'s answer states: "but.. will FAIL" all he means is it will fail in CMD on BASH but NOT fail if it is a crontab – Mr Heelis Nov 26 '15 at 12:39
  • This will fail if you're in a non-English locale (since `%a` outputs a locale-specific string). It's also not portable to other Unixes where `/bin/sh` is a shell with `[` implementation that doesn't support the `==` operator (e.g. Ubuntu, where `/bin/sh` is Dash). – Mark Amery Jul 14 '18 at 22:03
5

Run a cron task 1st monday, 3rd tuesday, last sunday, anything..

http://xr09.github.io/cron-last-sunday/

Just put the run-if-today script in the path and use it with cron.

30 6 * * 6 root run-if-today 1 Sat && /root/myfirstsaturdaybackup.sh

The run-if-today script will only return 0 (bash value for True) if it's the right date.

EDIT:

Now with simpler interface, just one parameter for week number.

# run every first saturday
30 6 * * 6 root run-if-today 1 && /root/myfirstsaturdaybackup.sh

# run every last sunday
30 6 * * 7 root run-if-today L && /root/lastsunday.sh
MGP
  • 2,981
  • 35
  • 34
  • In the 1st format, script is kind of independent. In simpler format, the script depends on the day of the week parameter. Am I getting it right? – Ashfaq Apr 25 '19 at 04:44
  • 1
    Exactly @Ashfaq, since you're already stating the week day on cron there's no need to check for it on the script, it limits itself to check what week is it: 1st, 2nd, 3rd or 4th week. Now if you want to use this without cron then it makes sense to use the 1st format and check for the week day. – MGP Apr 25 '19 at 04:52
0

maybe use cron.hourly to call another script. That script will then check to see if it's the first sunday of the month and 9am, and if so, run your program. Sounds optimal enough to me :-).

gtrak
  • 5,598
  • 4
  • 32
  • 41
  • 4
    Except you might as well be calling it daily: it's a little known fact that the day changes once a day. ;) – Isaac Jul 13 '10 at 20:19
  • Thanks for your answer Though not a big deal, why to run a script every hour? I am rather looking for a weekly script to run every Sunday. But wondering if anybody has already done something similar. – ring bearer Jul 13 '10 at 20:23
  • I'm not experienced at cron, that's why, there's probably a better way. But polling your conditional script every hour would get you pretty close to 9am. Daily doesn't really tell you when it will run during the day, and you specified 9am. – gtrak Jul 13 '10 at 20:26
-1

If you don't want cron to run your job everyday or every Sunday you could write a wrapper that will run your code, determine the next first Sunday, and schedule itself to run on that date.

Then schedule that wrapper for the next first Sunday of the month. After that it will handle everything itself.

The code would be something like (emphasis on something...no error checking done):

#! /bin/bash
#We run your code first
/path/to/your/code
#now we find the next day we want to run
nskip=28 #the number of days we want to check into the future
curr_month=`date +"%m"`
new_month=`date --date='$nskip days' +"%m"`
if [[ curr_month = new_month ]] 
then
((nskip+=7))
fi
date=`date --date='$nskip days' +"09:00AM %D` #you may need to change the format if you use another scheduler
#schedule the job using "at"
at -m $date < /path/to/wrapper/code

The logic is simple to find the next first Sunday. Since we start on the first Sunday of the current month, adding 28 will either put us on the last Sunday of the current month or the first Sunday of the next month. If it is the current month, we increment to the next Sunday (which will be in the first week of the next month).

And I used "at". I don't know if that is cheating. The main idea though is finding the next first Sunday. You can substitute whatever scheduler you want after that, since you know the date and time you want to run the job (a different scheduler may need a different syntax for the date, though).

-2

try the following

0 15 10 ? * 1#1

http://www.quartz-scheduler.org/documentation/quartz-1.x/tutorials/crontrigger

Anup
  • 5
  • 2
  • 1
    -1; this won't work because the `?` syntax isn't part of regular Unix Cron. It is rather an extension of the syntax in Quartz Scheduler. – Mark Amery Jul 14 '18 at 22:04
  • Hilariously, this is exactly what I wanted even though it is absolutely not the answer to the original question. If you're using Rundeck, this is what you want. – medley56 Oct 08 '18 at 16:15
-4

00 09 1-7 * 0 /usr/local/bin/once_a_week

every sunday of first 7 days of the month

  • 7
    Sorry, this one will actually work every Sunday AND the first 7 days of the month. See here: http://stackoverflow.com/a/6203414/327064 – ak112358 Aug 03 '13 at 19:54