Will this code work over a new year? - shell

Basically, I have a series of commands I want to run every other sunday. I set a cron task to run the script every sunday, then this script only allows the script to run on even weeks, thus it only runs every other sunday. My question is, will this script still work going from year to year.
if [ $(($(date +'%U') % 2)) -eq 0 ]
then
some command
fi

You have what's known as the XY problem here.
You have a problem with this part of your shell script, and you want to solve the problem by fixing the script. In reality, fixing the root cause of the problem is easier.
Simply alter your cron job to run every other Sunday:
#----+-----+-----+-----+-----+-------------------------------------------------
#min |hour |day |month|day |command
# | |of mn| |of wk|
#----+-----+-----+-----+-----+-------------------------------------------------
03 04 * * 7 expr `date +%W` % 2 >/dev/null || fortnightly.sh
See How to instruct cron to execute a job every second week? for more info.

If you don't want to specify this with cron syntax, you can use the %s format instead of %U. This will give you the number of seconds since 1st Jan 1970 UTC. You can divide this to get a week number:
$(($(date +'%s') / 604800))
Then you can do your modulo test on that.
Note the number 604800 = 7 * 86400 = 7 * 24 * 60 * 60 ie the number of seconds in one week.
If you're running this every day, you'll want to know that it's actually a Sunday. So in this case, you would divide by 86400 to get a day number. Then, armed with the knowledge that day zero was a Thursday, you can check that the result (modulo 14) is either 3 or 10, depending on which Sunday you started at.

Related

bash script - how to get the time difference (in hour:minute:seconds) between two unix-timestamps

I need to get the time difference between two unix-timestamps in hour, minute, and seconds. I can get the different in minutes and seconds correctly but the hour is always wrong.
I have two unix-timestamps and I subtract to get the difference.
1595455100 - 1595452147 = 2953
(That's the time difference between 2:09:07pm and 2:58:20pm on the same day)
Then I do date -d #2953 +'%I:%M:%S'
I get 04:49:13 but I expect to get 0:49:13
I'd also tried date -d #2953 +'%H:%M:%S' and date -d #2953 +'%T' and get 16:49:13. I've also checked differences greater than an hour, the difference in minute and seconds is still correct but the hour is still wrong.
I don't understand the Hour format and appreciate any help.
You don't necessarily need date for this conversion. You can use integer math and printf for fun and profit.
start=1595452147
end=1595455100
s=$((end - start))
time=$(printf %02d:%02d:%02d $((s / 3600)) $((s / 60 % 60)) $((s % 60)))
echo $time
There may be a way to do it with date too, but the above should work reliably.

How can I get the timestamp in days in awk?

I have a file "file_XYZ_18548".
Here the name's ending 18548 is a timestamp in days, and it is changing day by day, like "file_XYZ_18550".
I would like to get this date via variable but I couldn't find a date command to get the timestamp in days.
I can get the date result but I can't get the timestamp in reverse.
timeinday=18550
timestp=timeinday*86400
datetm=$(echo $timestp | gawk '{print(strftime("%Y-%m-%d %H:%M:%S", $0))}')
echo $datetm
2020-10-14 10:09:10
How can I get this with date command in bash scripting? Is there any way of this via awk/gawk etc..?
Given that 2020-10-14 - 18548 days corresponds to 1970-01-03, it is reasonable to guess that the 'epoch' for the day count is 1970-01-01. It is likely that day 1 was 1970-01-01, so day zero was 1969-12-31.
Converting day count to date
You can use the GNU date command like this:
daycount=18548
date -u -d "#$(( ($daycount-1) * 86400 ))" +"%Y-%m-%d %H:%M:%S"
That yields the result 2020-10-12 00:00:00. You can drop the time component of the format if you wish (you probably do; midnight isn't very exciting when it is always midnight). You can calibrate the -1 to resolve exactly what day should correspond to 18548. Just in case it isn't obvious, there are 86,400 seconds in a day (24 hours • 60 minutes per hour • 60 seconds per minute).
The $(( … )) notation is Bash's Arithmetic Expansion notation.
Converting current time to day count
If you want to convert the current time to the day offset, then you can use the %s specifier to get the seconds since 1970-01-01 00:00:00Z and divide by 86400 to get the number of days:
echo $(( $(date +'%s') / 86400 ))
which (at 2020-10-14 23:30 -06:00, aka 1602739800 seconds since the Unix Epoch) yields the result:
18550
Again, if need be, you can adjust the value of the division to account for when day 1 was in this scheme. Shell arithmetic in Bash is integer arithmetic, which is exactly what is wanted. You might need to use -u to get UTC as the time zone (as I did earlier), and so on.

Deriving URLs from date

I would like to automate a download of an image from a third party server with a CRON job and then upload the image to my website.
I have 2 issues:
First, the third party site changes the image name every day using the following logic:
http://thirdpartysite.com/ImageFinder.aspx?ReportID=FILENAME
where FILENAME is 26601 +14 for each day after 6 Oct 2014 (so 7 Oct would be 26615, 8 Oct would be 26629 etc).
How do I build this into a simple Linux bash script for use with wget?
Second, how do I upload this to my site via FTP (or similar) with the same script.
NOTE: I have permission to host the file on my site and have linked the original site / placed credit for the image.
Following the suggestions of #Abhay, first get the timestamp of Oct 6, let's store it in the variable $d0:
d0=$(date +%s -d 20141006)
Then store the timestamp of a target date, say Oct 8 and store it in $d1:
d1=$(date +%s -d 20141008)
Then you can calculate the difference and apply the required arithmetic operations in $((...)), like this:
echo $((26601 + 14 * (d1 - d0) / 60 / 60 / 24))
# outputs: 26629
The date command has one very good format: %s, which prints number of seconds since "epoch", which is the fixed date 1 January 1970, 00:00 UTC. I'll call it "timestamp". In combination with this, you can use the -d date-string, so that it prints the given date as number of seconds. Now you can take today's timestamp, subtract the timestamp of "6 Oct 2014" from it, and you get number of seconds between the two times. Now you can divide it by (60 * 60 * 24) to get it in number of days, and do further arithmetic to get the desired number, and make a file name out of it.
The date string formats that -d option takes are flexible, but as of now I am not sure whether it takes "6 Oct 2014" as is. Try a few permutations, or better, check the "info" page.

Time calculation in bash

We have a little issue with a time calculation in Bash.
Lets give you a little explaination about the situation up here.
We download every 5 minutes one file from an FTP server. This file contains time information about the data in this file. But the timeformat of the file is in UTC, and our local time is UTC+2. The files contains information about the past 5 minutes from local time. Now we have the following code:
TIMESTAMP=$(echo "$(TZ=UTC date "+%Y%m%d%H%M") - ($(date +%M)%5)-5" | bc)
That works well for several hours but after 55 minutes it becomes a problem. So we dont able to get the files with the 55 minutes, 60 minutes.
So if local time is: 19:47
The file with time 17:40 (utc) is available on the server at 19:45 local time
The time the files are available on the server are not constant too bad...
19:00, 19:05, 19:10 etc... but sometimes the file is one minute later....
This is my crontab file:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/kbroeren/cronscripts
*/1 * * * * /home/kbroeren/cronscripts/radar >> /home/kbroeren/radar_log.txt 2>&1
*/5 * * * * sudo /usr/bin/python /home/kbroeren/cronscripts/radar_plot.py >>/home/kbroeren/out.txt 2>&1
Is there a better and correct way to do this ?
You seem to be looking for %s:
date +%s
This returns:
%s seconds since 1970-01-01 00:00:00 UTC
You'd need to change the arithmetic a bit, though.
The date command can convert to and from a number of date and time formats. If you want to do arithmetic with dates and times, it's probably easiest to convert to “seconds since the unix epoch”, do the arithmetic, and convert back to the time format of your choice.
You might consider something like:
date --date="#$(echo $(TZ=UTC date +%s) - $(date +%s)'%(5*60)-(5*60)' | bc)"

How to schedule to run first Sunday of every month

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

Resources