LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Bash script for printing days in a specific month (taking into account a leap year) (https://www.linuxquestions.org/questions/programming-9/bash-script-for-printing-days-in-a-specific-month-taking-into-account-a-leap-year-941666/)

shayno90 04-25-2012 08:36 AM

Bash script for printing days in a specific month (taking into account a leap year)
 
I have created a rather long winded bash script to print the number of days in the month, and give information about leap years if the current month is February. However I am having difficulty running it and need a better approach to scripting it if you can help? I am testing with if/elsif/else before trying a case function.

Code:

#!/bin/bash

# Leap year check

currentmonth=$(date +"%B")
month=$(date +%m)
leap=$(date +%Y)
M1=$01
M2=$02
M3=$03
M4=$04
M5=$05
M6=$06
M7=$07
M8=$08
M9=$09
M10=$10
M11=$11
M12=$12
M13=$13
echo "The current month is $currentmonth"
if [ $month -eq $M1 ]; then
    echo "$month has 31 days"
        elif [ $month -eq $M2 ]; then
            echo "$month has 28 days"
        elif [ $month -eq $M3 ]; then
            echo "$month has 31 days"
        elif [ $month -eq $M4 ]; then
            echo "$month has 30 days"
        elif [ $month -eq $M5 ]; then
            echo "$month has 31 days"
        elif [ $month -eq $M6 ]; then
            echo "$month has 30 days"
        elif [ $month -eq $M7 ]; then
            echo "$month has 31 days"
        elif [ $month -eq $M8 ]; then
            echo "$month has 31 days"
        elif [ $month -eq $M9 ]; then
            echo "$month has 30 days"
        elif [ $month -eq $M10 ]; then
            echo "$month has 31 days"
        elif [ $month -eq $M11 ]; then
            echo "$month has 30 days"
        elif [ $month -eq $M12 ]; then
            echo "$month has 31 days"
else [ $[$leap % 400] -eq "0" ]; then
    echo "This is a leap year. February has 29 days"
fi


MensaWater 04-25-2012 08:46 AM

You can use "cal" to get the calendar for any month. In your script you're apparently only interested in the current month so the following would get number of days for current month:

Code:

cal |egrep -v [a-z] |wc -w
The cal command by default shows calendar for current month. The egrep -v tells it to exclude the output for any line that contains letters which will be the Month and Day headers so you're left only with the numeric days. wc -w counts the words which will be the number of days in this case.

You can also specify a given month/year with cal. So if you wanted to see if February 2012 was a leap year you could do:
Code:

cal 2 2012 |egrep -v [a-z] |wc -w
You can even get the number of days in a year by specifying only the year (not month) with cal:
Code:

cal 2012 |egrep -v "[a-z]|[0-9][0-9][0-9]" |wc -w
Note the extra characters in the egrep are to exclude any 3 digit number so would exclude the year header (which is 4 digits) but leave all the days (which are 2 digits).

If interested only in the current year you can specify it with the date command:
Code:

cal $(date +%Y) |egrep -v "[a-z]|[0-9][0-9][0-9]" |wc -w

shayno90 04-25-2012 09:00 AM

Thanks for that MensaWater. I will use that in the future as much simpler. However I am practising some bash scripting with "if/else" statements and was looking for a simpler way to condense the script and achieve the same result as using "cal".

catkin 04-25-2012 09:03 AM

M1=$01 sets the value of M1 to the value of variable 0 with literal string 1 added to it. Try M1=01 etc.

catkin 04-25-2012 09:23 AM

  • You could make the script neater by doing away with the M* variables and dealing with the leap year in the February elif.
  • The variable leap actually holds the year so easier to understand if it has that name.
  • Although the months look like numbers they are actually strings so = is more appropriate than -eq.
  • [[ <test expression> ]] is preferred over [ <test expression> ] for reasons explained here.
  • (( <arithmetic expression> )) is arguably more appropriate for arithmetic testing than the traditional test forms.
  • I would line up the if-elif-else-fi but that is just a personal preference.
Putting that all together:
Code:

#!/bin/bash

# Leap year check

currentmonth=$(date +"%B")
month=$(date +%m)
year=$(date +%Y)
echo "The current month is $currentmonth"
if [[ $month = 01 ]]; then
    echo "$month has 31 days"
elif [[ $month = 02 ]]; then
    if (( year % 400 != 0 )); then
        echo "$month has 28 days"
    else
        echo "$month has 29 days"
    fi
elif [[ $month -eq 03 ]]; then
    echo "$month has 31 days"
...
elif [[ $month = 12 ]]; then
    echo "$month has 31 days"
else
    echo "Programming error: \$month not in expected 01-012 range: $month" >&2
fi


shayno90 04-25-2012 09:54 AM

Thanks for that info catkin as I needed to some assistance in trying to formulate an approach to scripting this issue.
Code:

#!/bin/bash

# Leap year check

currentmonth=$(date +%B)
month=$(date +%m)
year=$(date +%Y)
echo "The current month is $currentmonth"

if [[ $month = 01 ]]; then
    echo "$month has 31 days"
elif [[ $month = 02 ]]; then
    if (( $year % 400 != 0 )); then
        echo "$month has 28 days"
    else
        echo "$month has 29 days"
    fi           
elif [[ $month = 03 ]]; then
    echo "$month has 31 days"
elif [[ $month = 04 ]]; then
    echo "$month has 30 days"
elif [[ $month = 05 ]]; then
    echo "$month has 31 days"
elif [[ $month = 06 ]]; then
    echo "$month has 30 days"
elif [[ $month = 07 ]]; then
    echo "$month has 31 days"
elif [[ $month = 08 ]]; then
    echo "$month has 31 days"
elif [[ $month = 09 ]]; then
    echo "$month has 30 days"
elif [[ $month = 10 ]]; then
    echo "$month has 31 days"
elif [[ $month = 11 ]]; then
    echo "$month has 30 days"
elif [[ $month = 12 ]]; then
    echo "$month has 31 days"
else
    echo "Programming error: \$month not in expected 01-12 range: $month" >&2
fi

Not correct yet as:
The current month is April
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
Programming error: $month not in expected 01-12 range: 04

It has a problem with the final "fi" in the script.

grail 04-25-2012 09:55 AM

Personally I would change the ifs into a case to make it cleaner looking:
Code:

#!/bin/bash

currentmonth=$(date +%B)
month=$(date +%m)
year=$(date +%Y)

case $month in
    0[13578]|10|12) days=31;;
    0[469]|11)            days=30;;
    *)        (( year % 400 )) && days=29 || days=28
esac

echo "$month has $days days"


grail 04-25-2012 09:56 AM

No you need to check your typing:
Code:

if (( $year % 400] != 0 )); then

catkin 04-25-2012 09:59 AM

@grail: the OP is practising with if-elif-else-fi before moving on to case. And well spotted on that typo :)

shayno90 04-25-2012 10:01 AM

Quote:

Originally Posted by grail (Post 4662771)
No you need to check your typing:
Code:

if (( $year % 400] != 0 )); then

Yes, I noticed this a few minutes ago and changed it but got the same result.
I wanted to try "if/else" statements before moving on to "case" statements.

michaelk 04-25-2012 10:16 AM

FYI leap year check If I remember correctly is:
if(year%400 ==0 || (year%100 != 0 && year%4 == 0))

shayno90 04-25-2012 10:27 AM

Quote:

Originally Posted by michaelk (Post 4662789)
FYI leap year check If I remember correctly is:
if(year%400 ==0 || (year%100 != 0 && year%4 == 0))

Changing to
Code:

if ( $year % 400 == 0 ) || ($year % 100 != 0 && $year % 4 == 0); then
or
Code:

if ( ($year % 400 == 0 ) || ($year % 100 != 0 && $year % 4 == 0) ); then

gives the same output error:
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
Programming error: 04 not in expected 01-12 range: 04

Line 40 is :
Code:

38 else
39    echo "Programming error: $month not in expected 01-12 range: $month" >&2
40 fi


michaelk 04-25-2012 10:52 AM

Sorry, I did not put the formula in proper bash syntax.
Code:

if (( ($year % 400) == 0 )) || (( ($year%4) == 0 ) && ($year%100) !=0) )); then
  echo "This is a leap year"
else
  echo "this is not a leap year"
fi


shayno90 04-25-2012 11:21 AM

Quote:

Originally Posted by michaelk (Post 4662809)
Sorry, I did not put the formula in proper bash syntax.
Code:

if (( ($year % 400) == 0 )) || (( ($year%4) == 0 ) && ($year%100) !=0) )); then
  echo "This is a leap year"
else
  echo "this is not a leap year"
fi


Syntax is not correct for
Code:

if (( ($year % 400) == 0 )) || (( ($year%4) == 0 ) && ($year%100) !=0) )); then
Syntax error: word unexpected (expecting ")")
The test expression brackets need to be changed.

The nested if/else statement for leap year is the issue as the syntax doesn't allow the script to run and results in previous error messages.

grail 04-25-2012 11:26 AM

Quote:

@grail: the OP is practising with if-elif-else-fi before moving on to case. And well spotted on that typo
hmmm ... can see a typo but not read the questions and answers :doh:

It would appear that the code in post #6 works just fine for me:
Code:

$ ./leap_year.sh
The current month is April
04 has 30 days

Is that the same way you are running the code?

michaelk 04-25-2012 12:22 PM

Does this work?
Code:

if (( ($year % 400) == 0 )) || (( $year % 4 == 0 && $year % 100 !=0 )); then
  echo "This is a leap year"
else
  echo "this is not a leap year"
fi


shayno90 04-25-2012 02:09 PM

Quote:

Originally Posted by grail (Post 4662843)
hmmm ... can see a typo but not read the questions and answers :doh:

It would appear that the code in post #6 works just fine for me:
Code:

$ ./leap_year.sh
The current month is April
04 has 30 days

Is that the same way you are running the code?

Yes running the script from the script directory works as you run it but not the other way!

Code:

root@localhost:~/script# ./leaptest.sh
The current month is April
04 has 30 days

but not this way
Code:

root@localhost:~/script# sh leaptest.sh
The current month is April
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
leaptest.sh: 40: [[: not found
Programming error: 04 not in expected 01-12 range: 04

Any idea why?

shayno90 04-25-2012 02:16 PM

Quote:

Originally Posted by michaelk (Post 4662873)
Does this work?
Code:

if (( ($year % 400) == 0 )) || (( $year % 4 == 0 && $year % 100 !=0 )); then
  echo "This is a leap year"
else
  echo "this is not a leap year"
fi


Yes, the code works when run as:
Code:

root@localhost:~/script# ./lyear.sh
The current month is April
04 has 30 days

but not when run as:
Code:

root@slocalhost:~/script# sh lyear.sh
The current month is April
lyear.sh: 13: Syntax error: word unexpected (expecting ")")

I am confused now as I thought you had to specify the script was a bash script before running?!

catkin 04-25-2012 08:43 PM

When invoked as sh (which may or may not be a link to bash on your system), bash runs in POSIX mode and the full bash feature set is not available. Try bash 1year.sh

shayno90 04-26-2012 05:11 AM

Quote:

Originally Posted by catkin (Post 4663101)
When invoked as sh (which may or may not be a link to bash on your system), bash runs in POSIX mode and the full bash feature set is not available. Try bash 1year.sh

Thanks I invoked the wrong bash shell when running the script!

I will add this to the script also to see if I can get user input in the form of one argument (enter a year in the terminal to check if it is a leap year.)

Code:

#!/bin/bash
# This script will test the user input to see if we're in a leap year or not.

year=`date +%Y`
getyear=("$1"(year))


if [ ! $getyear == `date +%Y` ]; then
  echo "Year must be in the format YYYY!"
  exit
fi

if [ $[$getyear % 400] -eq "0" ]; then
  echo "This is a leap year.  February has 29 days."
elif [ $[$getyear % 4] -eq 0 ]; then
        if [ $[$getyear % 100] -ne 0 ]; then
          echo "This is a leap year, February has 29 days."
        else
          echo "This is not a leap year.  February has 28 days."
        fi
else
  echo "This is not a leap year.  February has 28 days."
fi

How can I get the user input to read in the format of "date +%Y"?

shayno90 04-26-2012 06:57 AM

Did it using the read function but would like to know is it possible to do so from adding an argument to the script itself? (i.e. ./lyear.sh 2000)

Code:

#!/bin/bash
# This script will test the user to see if we're in a leap year or not.

echo "Type the year that you want to check (4 digits), followed by [ENTER]:"

read year

if (( ("$year" % 400 == "0") )) || (( ("$year" % 4 == "0") && ("$year" % 100 != "0") )); then

    echo "$year is a leap year"
else
    echo "This is not a leap year"
fi


catkin 04-26-2012 07:07 AM

Something like
Code:

if [[ $1 = '' ]]; then
    read -p 'Enter year > ' year_in
else
    year_in=$1
fi

For robustness your script could check that year_in is an integer before doing arithmetic on it -- and an unsigned integer for sanity.

grail 04-26-2012 07:19 AM

I am a bit lost at what you want?
Code:

year=`date +%Y`
getyear=("$1"(year))

How were these ever to be the same? Also the second causes an error when run at the command line, even if you turn $1 into just 1.

You have solved with the read option which takes the entered value and assigns it to the variable supplied. How about you take out the middle man and apply it to the variable yourself,
which would then be a parameter from the command line :)

shayno90 04-26-2012 08:10 AM

Quote:

Originally Posted by grail (Post 4663483)
I am a bit lost at what you want?
Code:

year=`date +%Y`
getyear=("$1"(year))

How were these ever to be the same? Also the second causes an error when run at the command line, even if you turn $1 into just 1.

You have solved with the read option which takes the entered value and assigns it to the variable supplied. How about you take out the middle man and apply it to the variable yourself,
which would then be a parameter from the command line :)

I wasn't sure how to assign the argument from the command line (i.e., ./lyear 2000) and then ensure the argument (2000) is passed/formatted according to `date +%Y` (YYYY) (of course I don't want the result from date, just how to format it like date).

catkin 04-26-2012 08:26 AM

Quote:

Originally Posted by shayno90 (Post 4663522)
I wasn't sure how to assign the argument from the command line (i.e., ./lyear 2000) and then ensure the argument (2000) is passed/formatted according to `date +%Y` (YYYY) (of course I don't want the result from date, just how to format it like date).

Both the argument passed from the command line and the date output are strings so no special formatting is required.

grail 04-26-2012 10:51 AM

Here is a possible way to check you are getting up to 4 and only 4 digits:
Code:

regex='^[1-9][0-9]{,3}$'

user_date=$1

while [[ ! $user_date =~ $regex ]]; do read -p "Invalid value, please try again :> " user_date; done


kieranpilot 08-22-2018 07:43 PM

Clean way to do it using cal
 
As seen here :

YEAR=2020
MONTH=2

for DAY in $(DAYS=`cal $MONTH $YEAR | awk 'NF {DAYS = $NF}; END {print DAYS}'` && seq -f '%02G' $DAYS) ;do
DATE="$YEAR-$MONTH-$DAY"
echo $DATE
done

astrogeek 08-23-2018 02:35 AM

@kieranpilot - You have posted to a thread which has had no activity in six years. Although your information may be relevant it is best to start your own thread if you are experiencing a similar problem to attract attention of currently active members.

Additionally, advertising is not permitted in the forums, so your promotional post has been moderated. If you wish to advertise please visit the LQ Jobs Marketplace or contact us via the link on any page.

MadeInGermany 08-23-2018 05:50 PM

Let GNU date do the calculations:
Code:

currmonth=`date +%Y-%m`
days=`date -d "$currmonth-01 + 1 month - 1 day" +%d`
echo "The current month is $currmonth and has $days days"



All times are GMT -5. The time now is 09:35 AM.