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?
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.
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.
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.
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...
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)
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
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.
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 :-).
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).
try the following
0 15 10 ? * 1#1
http://www.quartz-scheduler.org/documentation/quartz-1.x/tutorials/crontrigger
00 09 1-7 * 0 /usr/local/bin/once_a_week
every sunday of first 7 days of the month
Related
I am writing a script that shows the amount of days left until we should change a password. The problem is that right now, we are about 11 days until the password needs to change. I'm struggling with the logic to script this out. The beginning of the 85 day count down was May 16, 2021. The next 85 days starts August 9, 2021. I would like the days to reset after 85. I'm honestly not even sure how to start this. Maybe a for-loop that returns 85 - $currentDate and then resets after 85 but I'm not sure how to get it to start today in the middle of the 85 days.
In your script, you can create a starting date of May 16, 2021 and increment that date by 85 days as needed:
# Original Start Date at 12:00 AM
$85Date = [datetime]'May 16, 2021'
# Today's date
$Current = Get-Date
# checks if today is past the 85 day date
# if you didn't change the password with 0 days remaining, this logic fails
while ($Current.Date -gt $85Date) {
# increments $85Date by 85 days
$85Date = $85Date.AddDays(85)
}
# check when there are 0 days remaining
if ($Current.Date -eq $85Date) {
# Send alert here for 0 days remaining
}
# output how many days remain
'{0} days remaining until password expiration' -f ($85Date - $current.Date).Days
The code as it is won't know if the password was updated unless you add that logic. It will just assume you waited until day 85 to reset it each time. To add more intelligence, you would need to then do one of the following:
Add logic that tests the password was reset
Have the password reset update an attribute on the account that can be read from PowerShell. That date can be used instead of today.
Update some other artifact like a database table or file with the password change date. Then have the script read that each time it runs.
There are going to be many alternatives to making this work. As of now, we do not know what service provides the account, what mechanism of alerting will be used, or what the limitations are for a PowerShell solution in your environment.
I wanted to calculate the seconds since the Unix Epoch (1970-01-01 00:00:00). Usually I would use
date +"%s"
Now, on my system the +"%s" option is not available but I easily got around using some other 'date' options and parsed it using bc:
date -u +"scale=0;(((((%Y-1970)*365.2425+%j)*24+%H)*60+%M)*60+%S)/1" | bc
This is short for
years = year_now - 1970
days = years * 365.2425 + day_of_year_now
hours = days * 24 + hour_now
minutes = hours * 60 + minute_now
seconds = minutes * 60 + second_now
So far so good. Then I discovered that the result of this calculation does not match with the result of the +"%s" option. I needed to add a magic number:
date -u +"scale=0;(((((%Y-1970)*365.2425+%j)*24+%H)*60+%M)*60+%S-36936)/1" | bc
Why?
Additionally, several months later, this magic number has changed from -36936 to -99792.
Why?
I'm sure something is wrong with my maths. I do not need better solutions in other script languages but I'd appreciate if somebody could correct my maths, please. Maybe someone has the source code for date and could show me its internal algorithm for +"%s" ... ?
Here is a POSIX way that should then work on all Unix and Unix like systems:
awk 'BEGIN {srand();print srand()}'
If you use ksh93, this should also work:
printf "%(%s)T\n"
Most system will have perl installed:
perl -le 'print time'
I need to create a list of days between a date interval.
Say for example from 2001-01-01 to 2009-12-31:
2001-01-01
2001-01-02
2001-01-03
..
2009-12-29
2009-12-30
2009-12-31
I know how to do it but maybe someone has a script already made?
If not, I will make such a script and upload it so others won't waste time on this when they need it.
I do not know awk from GnuWin32, but if the functions "mktime" and "strftime" are available, you can try the following code:
BEGIN {
START_DATE="2001-02-01"
END_DATE="2001-03-05"
S2=START_DATE
gsub("-"," ",S2)
T=mktime(S2 " 01 00 00")
if (T<0)
printf("%s is invalid.\n",START_DATE) >> "/dev/stderr"
else
{
for(S=START_DATE; END_DATE>S ;T+=86440) print S=strftime("%F",T)
}
}
The key is to convert the start date to a number meaning the seconds since the Epoch, add 86400 seconds (one day or 24 x 60 x 60) and convert back to the ISO date format.
After some trials I realized the mktime() function admits wrong dates as good (for instance, 2000-14-03).
Best regards
I'm trying to add a date and time for an event in org-mode that will repeat on the first Wednesday of each month.
I know I can use the diary-sexp format to identify the first Wednesday of each month, like so:
* My Special Event
<%%(diary-float t 3 1)>
This works, but I also need to specify the time when that event occurs (ideally both the start and end times). I've tried both of the following:
<%%(diary-float t 3 1) 19:30>
<%%(diary-float t 3 1) 19:30-20:30>
...but neither works.
Is this even possible, and if so, what am I doing wrong?
You can use the following to get the desired effect
* My Special Event 19:30-20:30
<%%(diary-float t 3 1)>
It will output in your agenda as (Agenda trimmed):
18:00...... ----------------
filename: 19:30-20:30 My Special Event
20:00...... ----------------
I've a script scheduled every 4th and 14th day of month.
when script starts on 4th, I need to get the last day of the previous month (easy, $a.AddDays(-5))
when script starts on 14th, I need to get the last day of 2 month before.
for example:
14 april.
I want to get:28 february 2013
how is it possible?
also I want to get the date in format yyyymmdd
UDPDATE:
Is it possible that your solution doesn't work well with the change of the year?
if I'm in january, $(get-date).month -1 results 0. so I did this:
$datenow = Get-date
if $datenow.day -eq 14
$datenow.AddDays(-14)
then I calculate
$LastDayInMonth = [System.DateTime]::DaysInMonth($(Get-date).Year, $(Get-date.Month))
$datenow.AddDays(-$LastDayInMonth)
$datestring = $datenow.ToString("yyyyMMdd")
To get the date in a string:
$(get-date).Date.ToString("yyyyMMdd")
For getting the last day of two months prior:
if($(get-date).Day -eq 14){
$LastDayInMonth = [System.DateTime]::DaysInMonth($(get-date).Year, $($(get-date).Month - 2))
}
Update
The line
$LastDayInMonth = [System.DateTime]::DaysInMonth($(get-date).Year, $($(get-date).Month - 2))
Uses the static method DaysInMonth in the System.DateTime class to get the days in the month of the month that is passed to it. It's documentation is here. It takes as input two parameters, the month as an integer, and the year as an integer as well.
In our case, we want 2 months before this month, so for the month parameter we pass in
$(get-date).month - 2
And that is surrounded in a parenthesis to make sure powershell does the calculation and then pass the result of the calculation. get-date is a powershell cmdlet that gives the current date and time as a .NET dateTime object, so we have all the properties and methods at our disposal.
The whole
[System.DateTime]::DaysInMonth()
is just the way of calling static methods in powershell.
Update 2
Once you get the last day in the month, you can concatenate the string by:
$LastDayInMonthString = "$($(get-date).AddMonths(-2).ToString("yyyyMM"))$LastDayInMonth"
$(Get-Date).addDays(-$(Get-Date).Day).addMonths(-1).ToString("yyyyMMdd")