Link to home
Start Free TrialLog in
Avatar of vadicherla
vadicherla

asked on

Shell Script

Hi

i have cronjob shell script  which we want to run all week days in a year except  first 3 business days of month and last two business days  of month     We dont want script not on this particular days.    This applies all months in a year.

Can we do some modificaton to this script or modify the cron enty

current crontab entry

04 00 * * 1-5 /sdfdf/sdfd.sh

Thanks
Avatar of simon3270
simon3270
Flag of United Kingdom of Great Britain and Northern Ireland image

I *had* seen your previous question in the "community" zone, so wasa bit surprised when it was closed!

Just have this at the start of your shell script.
#!/bin/bash

# exit if in first 3 business days of the month or last 2

sday=0  # number of business days we have counted
cday=0  # day number we are looking at now
tday=$(date '+%d')  # today's day of the month
#tday=$1  # temp for testing - day is parameter
maxstart=3
maxend=2
ymdate=$(date "+%Y/%m/")
#ymdate=$2  # temp for testing
mdate=$(date '+%e')
#mdate=$3  # for testing
ydate=$(date '+%Y')
#ydate=$4  # for testing

# First check whether today is in the first $maxstart days of the month
while [ $tday -gt $cday -a $sday -le $maxstart ]; do
  cday=$(expr $cday + 1)
  c0day=$(printf "%02d" $cday)
  if [ $(date '+%u' --date=${ymdate}${c0day}) -lt 6 ]; then
    sday=$(expr $sday + 1)
  fi
done

if [ $sday -le $maxstart ]; then
  echo In first $maxstart business days - date $tday, bus days $sday, checked $cday days
  exit 0
fi

# Now check the end of the month
# Get the last day of the month
dom=(0 31 28 31 30 31 30 31 31 30 31 30 31)
if [ ! \( $(expr $ydate % 4) -ne 0 -o \( $(expr $ydate % 400) -ne 0 -a $(expr $ydate % 100) -eq 0 \) \) ]; then
  dom[2]=29
fi

cday=$(expr ${dom[${mdate}]} + 1)
sday=0
while [ $tday -lt $cday -a $sday -le $maxend ]; do
  cday=$(expr $cday - 1)
  c0day=$(printf "%02d" $cday)
  if [ $(date '+%u' --date=${ymdate}${c0day}) -lt 6 ]; then
    sday=$(expr $sday + 1)
  fi
done

if [ $sday -le $maxend ]; then
  echo In last $maxend business days - date $tday, bus days $sday, checked $cday days
  exit 0
fi

Open in new window

By the way, most of the code will work in most modern shells, but the array initialisation is a bit bash-specific (though it may work in other shells too).  If it complains about the "dom=(0 3" line, replace it with
    dom[0]=0
    dom[1]=31
    dom[2]=28
and so on up to
    dom[12]=31

The "expr" statements could all also be replaced by neater shell versions, but I didn't want to be too bash-specific if I could help it!
Excluding Leap Years, would this work for you?

04 00 4-29 1,3,5,7,8,10,12 1-5 /sdfdf/sdfd.sh
04 00 4-28 4,6,9,11 1-5 /sdfdf/sdfd.sh
04 00 4-26 2 1-5 /sdfdf/sdfd.sh
I don't think you can do it sensibly with crontab entries alone.  The trouble is that a line like:

    04 00 4-26 2 1-5 /sdfdf/sdfd.sh

will run if day of month is 4 to 26 OR day of week is 1 to 5 (not AND).

So if the 1st is a Monday, it will run.

My code assumed that you stayed with the original "04 00 * * 1-5" entry, so the script was only called Monday to Friday (but it would be simple to add that check to the script if you preferred).
Avatar of vadicherla
vadicherla

ASKER

Hi Expert

Do  you think it will easaier to implement if we change the script to run all 7 days a week except first 3 business days of month and last 2 business day of month??

Thanks
In that case, you could modify serialband's solution to

04 00 4-29 1,3,5,7,8,10,12 * /sdfdf/sdfd.sh
04 00 4-28 4,6,9,11 * /sdfdf/sdfd.sh
04 00 4-26 2 * /sdfdf/sdfd.sh

with the limitation that it won't work exactly in leap years.
Then at the top of the script have:
if [ $(date '+%u') -ge 6 ]; then
    exit 0
fi

Open in new window

to avoid running the rest of the script on Saturday or Sunday.
Ah, take that back.  The crontab solution stops the script running on the first three days of the month - not the first three *business* days.  That's why my shell script does checking of the day of the week (the "if [ $(date '+%u' --date=${ymdate}${c0day}) -lt 6 ]" bit).

Note that my script doesn't cope with business holidays (e.g. January 1 is not a business day in many countries), but that would get quite complex!
I've never done both day of the week and day of month together, so I forgot that it was an 'or', not an 'and' for that combination.
 
Maybe you could incorporate the test in the cron to simplify your subsequent tests within the script for the first 3 and last 2 business days.

04 00 4-29 1,3,5,7,8,10,12 * test $(date +$u) -ge 6 &&/sdfdf/sdfd.sh
04 00 4-28 4,6,9,11 * test $(date +$u) -ge 6 && /sdfdf/sdfd.sh
04 00 4-26 2 * test $(date +$u) -ge 6 && /sdfdf/sdfd.sh
I'd be wary of making cron entries any more complex than they need to be (there's also quite a short line length limit in some of the crons I've used).  And just to complicate matters, you'd have to escape the % in the "date +%u" bit because cron uses % as an end-of-line marker.

It would be just as easy to add that check in the script.  If it does need to count business days rather than just days, you'll need my complicated code, and adding the "date +%u" check I gave a couple of replies ago would be a trivial addition.
So, vadicherla, does the "business" days count mean that if, for example, the 1st and 2nd of the month were Saturday and Sunday, then the first three business days would be the 3rd, 4th and 5th, so you wouldn't run the script until Thursday the 6th?

Also, would you rather not insert this extra code into your existing script (the "/sdfdf/sdfd.sh" in your example)?  In that case, you could have a script which just contains the date checking code, and if all of the tests pass, it could then run your original script - that would be very easy to set up (just let me know and I'll show you how).
So, vadicherla, does the "business" days count mean that if, for example, the 1st and 2nd of the month were Saturday and Sunday, then the first three business days would be the 3rd, 4th and 5th, so you wouldn't run the script until Thursday the 6th?

This is correct.  

Also, would you rather not insert this extra code into your existing script (the "/sdfdf/sdfd.sh" in your example)?  In that case, you could have a script which just contains the date checking code, and if all of the tests pass, it could then run your original script - that would be very easy to set up (just let me know and I'll show you how).

Yes this will be a  good solution
OK, I've added some extra code to the end of the script.  I've also fixed up a couple of bugs (the old code would have failed in September, oddly!), and added the "only run on business days" check that I mentioned earlier.  Note that I have commented out the "echo" commands I had to say why it was executing.  This is because cron commonly emails the user if a cron job produces any output (on the assumption that successful UNIX/Linux code usually produces no output - output implies a problem).
#!/bin/bash

# Exit if in first 3 business days of the month or last 2, or not a business day.
# If parameters are provided, they are run if the above tests do not exit.

sday=0  # number of business days we have counted
cday=0  # day number we are looking at now
tday=$(date '+%d')  # today's day of the month
#tday=11  # temp for testing
maxstart=3
maxend=2
ymdate=$(date "+%Y/%m/")  # start of date as YYYY/MM/
#ymdate=2014/04/  # temp for testing
mdate=$(date '+%m')       # Month, Jan=01
#mdate=04  # for testing
mdate=$(echo $mdate | sed 's/^0//')  # strip leading 0
ydate=$(date '+%Y')       # Year, eg 2014
#ydate=2014  # for testing

# First check that this is a business day
if [ $(date '+%u') -ge 6 ]; then
    # echo This is not a business day
    exit 0
fi

# Now check whether today is in the first $maxstart days of the month
while [ $tday -gt $cday -a $sday -le $maxstart ]; do
  cday=$(expr $cday + 1)
  c0day=$(printf "%02d" $cday)
  if [ $(date '+%u' --date=${ymdate}${c0day}) -lt 6 ]; then
    sday=$(expr $sday + 1)
  fi
done

if [ $sday -le $maxstart ]; then
  # echo In first $maxstart business days - date $tday, bus days $sday, checked $cday days
  exit 0
fi

# Now check the end of the month
# Get the last day of the month
dom=(0 31 28 31 30 31 30 31 31 30 31 30 31)
if [ ! \( $(expr $ydate % 4) -ne 0 -o \( $(expr $ydate % 400) -ne 0 -a $(expr $ydate % 100) -eq 0 \) \) ]; then
  dom[2]=29
fi

cday=$(expr ${dom[${mdate}]} + 1)
sday=0
while [ $tday -lt $cday -a $sday -le $maxend ]; do
  cday=$(expr $cday - 1)
  c0day=$(printf "%02d" $cday)
  if [ $(date '+%u' --date=${ymdate}${c0day}) -lt 6 ]; then
    sday=$(expr $sday + 1)
  fi
done

if [ $sday -le $maxend ]; then
  # echo In last $maxend business days - date $tday, bus days $sday, checked $cday days
  exit 0
fi

#Now, if a parameter has been provided, run it, and any parameters passed to it
if [ $# -ge 1 ]; then
  cmd=$1
  shift
  exec $cmd "$@"
fi

Open in new window


save this script as, say, busdaysonly.sh, make it executable, then your crontab entry would be

    4 0 * * * /path/to/busdaysonly.sh /sdfdf/sdfd.sh
Hi

How we are going to handle the fedral holidays with this script.
As I said above, adding holiday checking is a whole lot more complicated! It really depends on how automated you need it to be.

The best bet, and similar to a method I've used in a spreadsheet, is to have a file with all of the federal holiday dates over as many years as you are going to use this program, with one date per line in YYYY/MM/DD format. Then in the test that does the "date +%u... -lt 6" bit, also test that the date you are checking is not in that file. I'm not at a computer at the moment, but I'll test it out as soon as I can.
thank you
ASKER CERTIFIED SOLUTION
Avatar of simon3270
simon3270
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial