Shell script that calculates the previous Monday, Tuesday,... relative to today - shell

I'm trying desperatly to find a bash or ksh routine that allows me to find for example the previous Monday,Tuesday,Wednesday,... preceding today's date. Additonal it has to work on plain vanilla Solaris X and I don't have the GNU date available.
eg: Today = Thursday 2013/01/17 ; Let's say I want to find the last Monday. It has to return:
2013/01/14
I've managed to find a script on the net that does the job perfectly for all days except in this specific case:
eg: Today = Thursday 2013/01/17 ; I want to find the last Thursday which should give as result: 2013/01/10 ; but instead I get todays date again.
The script used was this:
#!/bin/ksh
#Get the nbr of the current weekday (1-7)
DATEWEEK=`date +"%u"`
#Which previous weekday will we need (1-7)
WEEKDAY=$1
# Main part
#Get current date
DAY=`date +"%d"`
MONTH=`date +"%m"`
YEAR=`date +"%Y"`
#Loop trough the dates in the past
COUNTER=0
if [[ $DATEWEEK -eq $WEEKDAY ]] ; then
# I need to do something special for the cases when I want to find the date of the same day last week
DAYS_BACK=168
DAY=`TZ=CST+$DAYS_BACK date +%d`
echo "DAY (eq) = $DAY"
else
while [[ $DATEWEEK -ne $WEEKDAY ]] ; do
COUNTER=`expr $COUNTER + 1`
echo "Counter is: $COUNTER"
DAYS_BACK=`expr $COUNTER \* 24`
echo "DAYS BACK is: $DAYS_BACK"
DAY=`TZ=CST+$DAYS_BACK date +%d`
echo "DAY is: $DAY"
if [[ "$DAY" -eq 0 ]] ; then
MONTH=`expr "$MONTH" - 1`
if [[ "$MONTH" -eq 0 ]] ; then
MONTH=12
YEAR=`expr "$YEAR" - 1`
fi
fi
DATEWEEK=`expr $DATEWEEK - 1`
if [[ $DATEWEEK -eq 0 ]]; then
DATEWEEK=7
fi
done
fi
echo $DAY/$MONTH/$YEAR

(My previous suggestion didn't work as I thought. It was late last night ...)
The key thing is to ignore today, but still loop. Try it like this:
#!/bin/ksh
#Get the nbr of the current weekday (1-7)
DATEWEEK=`date +"%u"`
#Which previous weekday will we need (1-7)
WEEKDAY=$1
# Main part
#Get current date
DAY=`date +"%d"`
MONTH=`date +"%m"`
YEAR=`date +"%Y"`
#Loop trough the dates in the past
COUNTER=0
while [ $COUNTER -eq 0 ] || [[ $DATEWEEK -ne $WEEKDAY ]] ; do
COUNTER=`expr $COUNTER + 1`
echo "Counter is: $COUNTER"
DAYS_BACK=`expr $COUNTER \* 24`
echo "DAYS BACK is: $DAYS_BACK"
DAY=`TZ=CST+$DAYS_BACK date +%d`
echo "DAY is: $DAY"
if [[ "$DAY" -eq 0 ]] ; then
MONTH=`expr "$MONTH" - 1`
if [[ "$MONTH" -eq 0 ]] ; then
MONTH=12
YEAR=`expr "$YEAR" - 1`
fi
fi
DATEWEEK=`expr $DATEWEEK - 1`
if [[ $DATEWEEK -eq 0 ]]; then
DATEWEEK=7
fi
done
echo $DAY/$MONTH/$YEAR
Your DAY=`TZ=CST+$DAYS_BACK date +%d` trick doesn't work for me though. Linux date seems to cap it at one day.

Does this work?
today=$(date +"%u")
weekday=$1
curdate=$(date +"%s")
gobackdays=$(($today - $weekday))
if [ $gobackdays -le 0 ]; then
let gobackdays+=7
fi
SECSDAY=86400
backtime=$(($curdate - $gobackdays * $SECSDAY))
echo $(date -d "#$backtime")

Related

Printing date range in AIX

I'm trying to print date range in AIX. I'm not able to use 'date' in AIX properly. I've tried some solutions suggested in Stack overflow. Nothing worked. Please find the error and code below.
I can't download 'GNU'.
#!/bin/ksh
startdate=20141030
enddate=20141120
loop_date=$startdate
let j=0
while [ "$loop_date" -ne "$enddate" ]; do
loop_date=`date -j -v+${j}d -f "%Y%m%d" "$startdate" +"%Y%m%d"`
echo $loop_date
let j=j+1
done
Error:
date: illegal option -- j
Usage: date [-u] [+Field Descriptors]
Try this;
#!/bin/ksh
startdate=$1
enddate=$2
loop_date=$startdate
currentdate=`date +%Y%m%d`
let j=0;
if [[ $startdate -lt $currentdate && $enddate -lt $currentdate ]];then
loop_date=$currentdate
while [[ "$loop_date" -gt "$startdate" ]]; do
loop_date=$(TZ=CST+$j date +%Y%m%d)
if [[ $loop_date -le $enddate ]];then
echo $loop_date
fi
let j=j+24
done
fi
if [[ $startdate -gt $currentdate && $enddate -gt $currentdate ]];then
while [[ "$loop_date" -lt "$enddate" ]]; do
loop_date=$(TZ=CST-$j date +%Y%m%d)
if [[ $startdate -le $loop_date ]];then
echo $loop_date
fi
let j=j+24
done
fi
user#host:/tmp:>ksh test.sh 20150630 20150705
20150705
20150704
20150703
20150702
20150701
20150630
user#host:/tmp:>ksh test.sh 20170630 20170705
20170630
20170701
20170702
20170703
20170704
20170705

Why is my If Statement in Bash not working?

Hi I'm creating an archiving system in bash with a long and messy if statement which doesn't currently work with the current errors being on the last few lines with the else if statement. Although I expect there are further errors with this statement anyway. The variable choice is from a menu which displays in the terminal. So if they select option 1 the user then has to enter data to be written to the crontab file.
if [ $choice -eq "1" ] then
echo "Enter the MINUTE"
read minute
if [ $minute -eq 0 ] then
GLOBIGNORE=*
minute="*"
echo minute
fi
echo "Enter the Hour"
read hour
if [ $hour -eq 0 ] then
GLOBIGNORE=*
hour="*"
echo hour
fi
echo "Enter the Day"
read day
if [ $day -eq 0 ] then
GLOBIGNORE=*
day="*"
echo day
fi
echo "Enter the Month"
read month
if [ $month -eq 0 ] then
GLOBIGNORE=*
month="*"
echo month
fi
echo "Enter the weekday"
read weekday
if [ $weekday -eq 0 ] then
GLOBIGNORE=*
weekday="*"
echo weekday
fi
echo $minute $hour $day $month $weekday " date > ~/testcron.log" > testcron.txt fi
elif [ $choice -eq "2" ]
then
echo "Enter the Source and Destination Locations"
fi
There are several issues with your code:
if [ $hour -eq 0 ] then
GLOBIGNORE=*
hour="*"
echo hour
fi
In general (all test []) are missing a ; after it:
if [ $hour -eq 0 ]; then
The echo hour will not print the value of var hour but the word hour. Change to echo "$hour" (yes, quoted). Also, there is no need to set the variable GLOBIGNORE to * if vars are properly quoted.
The vars here are not quoted, that is a reason for it to fail (or to need GLOBIGNORE):
echo $minute $hour $day $month $weekday
Change to:
echo "$minute $hour $day $month $weekday"
The redirection on that same line is a plain >, that will empty the file.
If you want to append to the files, do as this:
echo "$minute $hour $day $month $weekday" "$(date >> ~/testcron.log)" >>testcron.txt
In that line there is an unneeded additional fi
This script may be of help:
get(){
read -p "$1" "$2"
if [ "$((${!2}))" -eq 0 ]; then
eval "$2"="*"
echo "${!2}"
fi
}
read -p "Choice?:" choice
if [ "$choice" -eq "1" ]; then
get "Enter the MINUTE" minute
get "Enter the Hour" hour
get "Enter the Day" day
get "Enter the Month" month
get "Enter the weekday" weekday
date >> ~/testcron.log
echo "$minute $hour $day $month $weekday" >> testcron.txt
elif [ "$choice" -eq "2" ]; then
echo "Enter the Source and Destination Locations"
fi
You are missing a semi-colon:
if [ $choice -eq "1" ]; then
or
if [ $choice -eq "1" ]
then
A semi-colon or a newline is required because the command is actually [ and has to be terminated after the ], which is just the last argument given to [.
That is the old test (or [) syntax, you could also use:
if (( choice == 1 ))
then

Bash Script for deleting old backup files

every night my server creates backups for every mysql database. All of these files are saved in a folder like /backup/mysql/2014-11-28. Over the last few months a lot of folders and files have been stored in that directory and I would like to reduce this.
Therefore I would need a bash script which deletes every folder in the given directory, except every folder created this month (not in the last 30 days, but the actual month) and except one backup from every week (for example the backup from sunday). Since I have no clue how to do the sunday party I decided it would be simpler to keep the backups from the 07th, the 14th, the 21st and the the 28th.
#!/bin/bash
in_array() {
local haystack=${1}[#]
local needle=${2}
for i in ${!haystack}; do
if [[ ${i} == ${needle} ]]; then
return 0
fi
done
return 1
}
YEAR=`date +%Y`
LASTYEAR=`date +%Y -d "1 year ago"`
MONTH=`date +%m`
DAYS="07 14 21 28"
for i in $( ls ); do
BACKUP_YEAR=$(echo "${i}" | cut -d'-' -f1)
BACKUP_MONTH=$(echo "${i}" | cut -d'-' -f2)
BACKUP_DAY=$(echo "${i}" | cut -d'-' -f3)
DELETE=false
if [[ "$BACKUP_YEAR" == "$YEAR" ]]; then
if [[ "$BACKUP_MONTH" != "$MONTH" ]]; then
if [ $(in_array $DAYS "$BACKUP_DAY") ]; then
DELETE=true
fi
fi
fi
if [[ "$BACKUP_YEAR" == "$LASTYEAR" ]]; then
if [[ "$BACKUP_DAY" != ${DAYS[0]} ]]; then
DELETE=true
fi
fi
if [ "$DELETE" = true ] ; then
#OUTPUT=`rm -v -R $i`
echo $i
fi
done
The second part (backups from the year before) works perfectly fine, but the first part (for backups from this year) doesn't work and I don't know why. I found the function on this site, but I guess somehow my call is wrong.
EDIT: The code I am now using:
#!/bin/bash
read YEAR MONTH <<<$(date "+%Y %m")
LASTYEAR=$(( YEAR-1 ))
DAYS=" 07 14 21 28 "
for fn in $( ls )
do
if ([ "${fn:0:4}" = "$YEAR" ] &&
[ "${fn:5:2}" != "$MONTH" ] &&
[ "${DAYS/ ${fn:8:2} /}" = "$DAYS" ]) || ([ "${fn:0:4}" = "$LASTYEAR" ] &&
[ "${fn:8:2}" != ${DAYS:1:2} ])
then
#OUTPUT=`rm -v -R $fn`
echo "$fn"
fi
done
#!/bin/bash
read YEAR MONTH <<<$(date "+%Y %m")
LASTYEAR=$(( YEAR-1 ))
DAYS=" 07 14 21 28 "
for fn in $( ls )
do
DELETE=false
if [ "${fn:0:7}" = "$YEAR-$MONTH" ] &&
[ "${DAYS/ ${fn:8:2} /}" != "$DAYS" ]
then
DELETE=true
elif [ "${fn:0:4}" = "$LASTYEAR" ] &&
[ "${fn:8:2}" != ${DAYS:1:2} ]
then
DELETE=true
fi
if [ "$DELETE" = true ]
then
#OUTPUT=`rm -v -R $fn`
echo "$fn"
fi
done
Assuming that a file name matches the following date format YYYY-MM-DD, for instance, 2014-03-27, the expression ${fn:0:7} would be 2014-03; ${fn:8:2} would be 27; ${fn:0:4} would be 2014.
Read the bash manual pages about the parameter expansion.
The expression ${DAYS/ ${fn:8:2} /} checks the day from the filename (wrapped by white spaces) is found in the DAYS list. It would be equivalent to echo "${DAYS}" | grep -q "${BACKUP_DAY}".
note: it is possible to reduce the commands inside the for loop (join if and elif by means of a || and remove the DELETE variable checking and the DELETE variable itself) but I decided to keep the look of the original script.

Unix convert Month name to number

In BASH shell scripting or using gdate, given a date like "Oct 2011" how do I convert to a year-month number format? Output should be "2011-10", for example.
mydate="Oct 2011"
date --date="$(printf "01 %s" $mydate)" +"%Y-%m"
The parse_datetime interface for GNU date (which is what the example uses) has lots of rules. the Oct 2011 form of the date isn't one of them, so you prepend a "01 " to the front of it and date likes it.
read mon year <<< "Oct 2012"
date -d "$mon 1 $year" "+%Y-%m"
Result:
2012-10
You can convert the month to a number by finding the position of the name string:
#!/bin/bash
month=Oct
months="JanFebMarAprMayJunJulAugSepOctNovDec"
tmp=${months%%$month*}
month=${#tmp}
monthnumber $((month/3+1))
printf "%02d\n" $monthnumber
The output of the script above is:
10
Your specific string you could code:
#!/bin/bash
mydate="Oct 2011"
monthnumber() {
month=$1
months="JanFebMarAprMayJunJulAugSepOctNovDec"
tmp=${months%%$month*}
month=${#tmp}
monthnumber=$((month/3+1))
printf "%02d\n" $monthnumber
}
arr=(`echo ${mydate}`);
month=$(monthnumber ${arr[0]})
year=$(echo ${arr[1]})
echo "$year-$month"
The output would be:
2011-10
case "`date | awk '{print $2 }'`" in
Jan) MON="01" ;;
Feb) MON="02" ;;
Mar) MON="03" ;;
Apr) MON="04" ;;
May) MON="05" ;;
Jun) MON="06" ;;
Jul) MON="07" ;;
Aug) MON="08" ;;
Sep) MON="09" ;;
Oct) MON="10" ;;
Nov) MON="11" ;;
Dec) MON="12" ;;
esac
echo $MON
I'm not sure if there is a shorter way of doing this, but here is one way. This is by no means fool proof. You can improve this by adding other checks to input and make the comparison case insensitive.
#!/bin/ksh
### Validate input
if [ $# -eq 0 ]
then
echo "Usage: $0 InputMonYYYY"
echo "Example: $0 \"Oct 2011\""
exit 1
fi
### Read input
INPUTSTR=$1
MON_STR=`echo $INPUTSTR |cut -d' ' -f1`
YYYY_STR=`echo $INPUTSTR |cut -d' ' -f2`
if [[ "$MON_STR" = "Jan" ]] then
MON_NUM=01
elif [[ "$MON_STR" = "Feb" ]] then
MON_NUM=02
elif [[ "$MON_STR" = "Mar" ]] then
MON_NUM=03
elif [[ "$MON_STR" = "Apr" ]] then
MON_NUM=04
elif [[ "$MON_STR" = "May" ]] then
MON_NUM=05
elif [[ "$MON_STR" = "Jun" ]] then
MON_NUM=06
elif [[ "$MON_STR" = "Jul" ]] then
MON_NUM=07
elif [[ "$MON_STR" = "Aug" ]] then
MON_NUM=08
elif [[ "$MON_STR" = "Sep" ]] then
MON_NUM=09
elif [[ "$MON_STR" = "Oct" ]] then
MON_NUM=10
elif [[ "$MON_STR" = "Nov" ]] then
MON_NUM=11
elif [[ "$MON_STR" = "Dec" ]] then
MON_NUM=12
fi
echo ${YYYY_STR}-${MON_NUM}
Bash4 supports hash-tables (answer by Jim is the correct one though).
Example
#!/bin/bash
declare -A months=( ["Jan"]="01" ["Feb"]="02" )
mydate="Jan 2011"
echo ${mydate:4:8}-"${months["${mydate:0:3}"]}"
Output:
2011-01
Let's kick this dead horse.
If you don't care about invalid month names you can use this function I've written which is quite short (and only does 1 exec) but expects a month to be valid english 3-chars lower or upper case and only requires GNU sed and bash:
m2n() { echo $((-10+$(sed 's/./\U&/g;y/ABCEGLNOPRTUVY/60AC765A77ABB9/;s/./+0x&/g'<<<${1#?}) ));}
For your example I'd do:
read m y <<<"$#"; echo "$y-`m2n $m`"

Bash condition with AND OR operators

I've got a bash shell that gets options as either month (mm) and year (yyyy) or time (mmyyyy).
I can parse options and assign them to variables. Now, I need some conditions to check for those variables. What I need is: if (month AND year) OR time then proceed else display usage. This is my code and it doesn't seem to be working.
if [[ -z $MONTH && -z $YEAR ]] || [[ -z $TIME ]]; then
usage
exit 1
else
if [[ -z $TIME ]]; then
echo $MONTH$YEAR
else
echo $TIME
fi
fi
Your logic is a bit backward. This:
if [[ -z $MONTH && -z $YEAR ]] || [[ -z $TIME ]]; then
usage
("if I'm missing both month and year, it's an error; or, if I have those, but am missing time, it's an error") should be this:
if [[ -z $MONTH || -z $YEAR ]] && [[ -z $TIME ]]; then
usage
("if I'm missing month or year, and I'm missing time, it's an error").

Resources