How to generate sequential dates/times in a shell script? - linux

I would like to download a bunch of data sets from 1981 to 2000 (20 years). Those are in every 10 minutes. I was trying to write a script which will call all times and download the data. But I am unable to complete it. I can't check the leap years and days in each month. My script is:
#!/bin/sh
for yr in {1981..2000};do
for mm in 01 02 03 04 05 06 07 08 09 10 11 12;do
for dd in {1..31};do
if [[ $dd -le 9 ]];then nn=0$dd;else nn=$dd;fi
for tt in 00 10 20 30 40 50; do
echo wget www.xyz.com/$yy/$mm/$nn/$tt.txt
done;
done;
done;
done
How can I fix the problems of leap years, and days in the month generally?

You seem to have left out the hours.
Assuming you have GNU date, you can deal with it by using the date calculations. Do you have to worry about switches between winter and summer (standard and daylight saving) time? If so, there'll be some entertainment to be had with gaps of an hour in the spring and a period in the fall when the raw date/time values repeat.
$ /opt/gnu/bin/date -d '1981-01-01 00:00:00' +'%s %Y-%m-%d %H:%M:%S'
347184000 1981-01-01 00:00:00
$ /opt/gnu/bin/date -d '2000-12-31 23:50:00' +'%s %Y-%m-%d %H:%M:%S'
978335400 2000-12-31 23:50:00
$
That gives you start and end times in Unix timestamp notation (and in the US/Pacific time zone — adjust to suit your needs). You could then use a loop such as:
now=347184000
end=978335400
while [ "$now" -le "$end" ]
do
url=$(date -d "#$now" +'www.example.com/%y/%m/%d/%H/%M.txt')
echo wget "$url"
now=$(($now + 600))
done
There are multiple ways of writing that. I've assumed that there's a directory of hourly files, and within that the 10-minute files, but you can tweak the format to suit your requirements. The use of # in the -d is crucial.
You might prefer to use a scripting language such as Perl or Python instead of repeatedly invoking date as shown.
Note that you have a vast number of files to collect. With about 31 million seconds per year, and 600 seconds per 10 minute interval, you're looking at over 50,000 files per year for 20 years, or 1 million files in total. The target (victim) web site might not be happy with you running that flat out. You'd probably need to pace the retrieval operations — check their terms and conditions.

This is how it can be (please note that this leap year calculation is good until 2100 only):
#!/bin/sh
for yr in {1981..2000};do
for mm in 1 2 3 4 5 6 7 8 9 10 11 12;do
for dd in {1..31};do
if [[ $dd -eq 31 ]] && ( [[ $mm -eq 4 ]] || [[ $mm -eq 6 ]] || [[ $mm -eq 9 ]] || [[ $mm -eq 11 ]] )
then
continue
elif ( [[ $dd -gt 28 ]] && [[ $mm -eq 2 ]] && [[ $(( $yr % 4 )) -ne 0 ]] ) || ([[ $dd -gt 29 ]] && [[ $mm -eq 2 ]] )
then
continue
fi
if [[ $mm -le 9 ]];then mon=0$mm;else mon=$mm;fi
if [[ $dd -le 9 ]];then nn=0$dd;else nn=$dd;fi
for tt in 00 10 20 30 40 50; do
echo wget www.xyz.com/$yy/$mon/$nn/$tt.txt
done;
done;
done;
done

I would use something to figure out the leap years etc for me ie date. The following might give a hint on how to do this.
They way you're using wget means it's going to create a bunch of files with
"10.txt.1"
"10.txt.2"
"10.txt.3"
"10.txt.4"
"10.txt.5"
This might be fine but if you want to put these in a directory on their own or to name the file as something else
#!/bin/bash
#Jan 01 1980
COUNTER=347155200
while [ $COUNTER -lt 978263999 ]; do
year=`date -r $COUNTER +"%y"`;
month=`date -r $COUNTER +"%m"`;
day=`date -r $COUNTER +"%d"`;
hour=`date -r $COUNTER +"%H"`;
min=`date -r $COUNTER +"%M"`;
let COUNTER=COUNTER+600
url="www.xyz.com/$year/$month/$day/$hour/$min.txt";
dir="$year/$month/$day/$hour";
file="$year/$month/$day/$hour/$min.txt"
mkdir -p $dir;
wget "$url" $file;
#Post process files here...
done

Related

It does not return the expected output [duplicate]

This question already has answers here:
Compare Numbers not working properly (Bash Script in Hacker Rank)
(2 answers)
Closed last year.
The intention of my script is to print the users who have inactive days of more than 30. This is my script:
#!/bin/bash
max=30
grep -E ^[^:]+:[^\!*] /etc/shadow | awk -F: '{print $1" "$7}' | while read user days
do
if [ $days -gt $max ]
then
echo $user
echo $days
fi
done
I created 2 users, test and test2 each with 30 days and 50 days inactivity respectively. This script returns me both when I only need to print test2 with 50 days of inactivity.
I think you are making this more complicated than needed.
Since you already have awk in your pipe, that can replace grep and compare to max.
Simple example.
Given:
cat file
dawg 2 3 4 5 6 45
boosh 2 3 4 5 6 25
You can do:
max=30
awk -v max="$max" '$7>max{print $1, $7}' shadow
dawg 2 3 4 5 6 45
I left out the grep portion but you can either keep that as a pipe (if it is working) or integrate into the awk.
If you want to compare two integers in Bash, you either need to use the correct test, which is (( arithmetic context ))
$ (( 1>2 )); echo $?
1
$ (( 10>2 )); echo $?
0
# 0 means TRUE
or the correct operator which is [ -gt ]:
$ [ 10 -gt 2 ]; echo $?
0
$ [ 1 -gt 2 ]; echo $?
1
But if you use > inside of [[ .. ]] you are actually comparing strings and may be the wrong answer:
$ [[ 10 < 2 ]]; echo $?
0 # '0' means 'true' 10 < 2 only as strings
And you must use double [[ .. ]] to use > otherwise it is the redirection operator. (Thanks M. Nejat Aydin
...)

How to schedule the job in cron which will run every month of 8th business day

I want to schedule a job which will run 8th business day of every month. How can i achieve it.
Format :
30 1 * 1-12 1 [Command]
You can try running this as a daily cron this:
#!/bin/bash
if [ -f ./ranThisMonth ]; then
if [ "$(date +%d -gt 10)" ]; then
rm ./ranThisMonth.tmp
fi
else
echo "Running now!"
if [[ "$(date +%u)" -lt 5 && "$(date +%d)" -ge 8 ]]; then
# Your commands here
touch ./ranThisMonth.tmp
fi
fi
if [ -f ./ranThisMonth ]; then checks whether it ran the current month
if [ "$(date +%d -gt 10)" ]; then in case its past the 10th resets it
if [[ "$(date +%u)" -lt 5 && "$(date +%d)" -ge 8 ]]; then if weekday (businessday) and past the 8th

Schedule Cron to skip first Saturday of every month

I need a cron to skip the first Saturday of every month. I know we can run on specific days using the below command, but is there a way to skip on a particular day for every month?
//To run on first Monday of every month
0 2 * * 1 [ date '+\%m' == date '+\%m' -d "1 week ago" ] || /path/to/command
Is your command a binary file or just script?
How about the idea to add something like that in the beginning:
[ $(date | awk '{print $1}') == "Sat" ] && exit 1 || { \
# your code here
...
}
Since there seem to be no option in Cron.
I'm going with script for the above need.
**if [ $(date +%d) -le 7 ] && [ $(date +%u) -eq 1 ] ;**
[ $(date +%d) -le 7 ] - Checks if the days fall in first 7 days of month
[ $(date +%u) -eq 6 ] - Checks if day is equal to Saturday
Thanks.
The following works in cron (don't forget to escape the %):
0 2 * * * [[ `date '+\%u'` -ne '6' && `date '+\%d'` -lt '8' ]] || [[ `date '+\%d'` -gt '7' ]] && /path/to/command
The logic is :
If the day is not a Saturday and the day of the month is 1-7, execute the command
If the above is not correct, check if the day of the month is bigger than 7, if so execute
Another way would be to invert the logic :
0 2 * * * [[ ! ( `date '+\%u'` -eq '6' && `date '+\%d'` -lt '8' ) ]] && /path/to/command
The logic is :
If the day is not the first Saturday, execute the command
man 5 crontab
The "sixth" field (the rest of the line) specifies the command to be run. The entire command portion of the line, up to a newline or
a "%" character, will be executed by /bin/sh or by the shell specified in the SHELL variable of the cronfile. A "%" character in the command, unless escaped with a backslash (\), will be changed into newline characters, and all data after the first % will be sent to the command as standard input.

I have two dates and need to find the difference in hours

Wed Jan 21 20:44:20 EST 2015
Wed Jan 21 19:04:20 EST 2015
I have two dates about, need to get the difference in minutes. Please help
"c=date -d #$(( $(date -d "$b" +%s) - $(date -d "$a" +%s) )) -u +'%H:%M'" -> This command is giving in HH:MM but i want in MM
Thank you
this gives you the result: 100 minutes:
echo $((($(date -d "$a" +%s) - $(date -d "$b" +%s))/60 ))
Note that, it will always give an int value, if you need the precision less than 1 min, like 100.25 you may want to use bc or awk to do the calculation instead of $(( .. ))

Technique to compute next day's date from a date supplied via an arg?

I'm having some trouble figuring this out.
I have a script that checks a log file that is generated the day after the date specified by the script call. This is because the files fetched for the specified day's data won't be fetched until the day after. And when that cron runs in the morning, the log file will have a timestamp in it's file name for the next day, not the day specified by the arg. I need to keep the arg the same since is standard convention for our group to only specify the day that the data refers to, not the day that it is fetched by the cron job. That date (stored in $NOW in this script) is used quite a bit for other operations.
So right now I'm doing this crazy nested conditional thing (which actually works but I think it is hideous):
#Setup the date, and variables
TENANT=$1
DATE_ARRAY=(`echo $2 | sed -e 's/\// /g'`)
MONTH=(`echo ${DATE_ARRAY[0]}`)
DAY=(`echo ${DATE_ARRAY[1]}`)
YEAR=(`echo ${DATE_ARRAY[2]}`)
NOW=$YEAR$MONTH$DAY
...
# The next 15 lines or so will iterate the cron log date to the correct
# month or year if the load date is at the end of the month/year.
if [ "$DAY" -eq 28 ] && [ "$MONTH" -eq 02 ]; then
CRON_NOW=$(($NOW + 173))
elif [ "$DAY" -le 29 ]; then
CRON_NOW=$(($NOW + 1))
elif [ "$DAY" -eq 30 ]; then
if [ "$MONTH" -eq 04 ] || [ "$MONTH" -eq 06 ] || [ "$MONTH" -eq 09 ] || [ "$MONTH" -eq 11 ]; then
CRON_NOW=$(($NOW + 171))
else
CRON_NOW=$(($NOW + 1))
fi
elif [ "$DAY" -eq 31 ]; then
if [ "$MONTH" -eq 01 ] || [ "$MONTH" -eq 03 ] || [ "$MONTH" -eq 05 ] || [ "$MONTH" -eq 07 ] || [ "$MONTH" -eq 08 ] || [ "$MONTH" -eq 10 ]; then
CRON_NOW=$(($NOW + 170))
fi
if [ "$MONTH" -eq 12 ]; then
CRON_NOW=$(($NOW + 8870))
fi
fi
So you can see I did this crazy work around because I couldn't figure out how to use "date" to show the next days date from the one specified. I have a feeling there is some abstract awesome way to do this with "date" but I just don't know about it and can't decipher it from the man page.
I know about "date --date=xx/xx/xxxx" or "date --date=23 days ago" or whatever. I want something more like "date --date=xx/xx/xxxx minus one day" since I think trying to compute out how many days ago it was is just as hard as what I did to iterate it forward a day.
As always, much thanks to any answers. This site rocks the house yo!
-Ryan
This should work:
date --date="20 Feb 2010 1 day"
It returns:
Sun Feb 21 00:00:00 EST 2010
...which is one day after the given date.
$ date -d 'tomorrow'
Thu Feb 11 18:52:56 CST 2010
$ date -d '3/1/2011 - 1 day'
Mon Feb 28 00:00:00 CST 2011
You can take a timestamp of a date, add one day worth of seconds to it and dan transform it to date again.
date --date=#$(($(date --date=2008-09-04 +%s) + 86400)) +%F

Resources