30 Keeping Track of Events


#30 Keeping Track of Events

This script is actually two scripts that implement a simple calendar program. The first script, addagenda , enables you to specify either the day of the week or the day and month for recurring events, or the day, month, and year for one-time events. All the dates are validated and saved, along with a one-line event description, in an .agenda file in your home directory. The second script, agenda , checks all known events, showing which are scheduled for the current date.

I find this kind of tool particularly useful for remembering birthdays and anniversaries. It saves me a lot of grief !

The Code

 #!/bin/sh # addagenda - Prompts the user to add a new event for the agenda script. agendafile="$HOME/.agenda" isDayName() {   # return = 0 if all is well, 1 on error   case $(echo   tr '[[:upper:]]' '[[:lower:]]') in    sun*mon*tue*wed*thu*fri*sat*) retval=0 ;;    *) retval=1 ;;   esac   return $retval } isMonthName() {     case $(echo   tr '[[:upper:]]' '[[:lower:]]') in       jan*feb*mar*apr*may*jun*)    return 0        ;;       jul*aug*sep*oct*nov*dec*)    return 0        ;;       *) return 1       ;;     esac } normalize() {   # Return string with first char uppercase, next two lowercase   echo -n   cut -c1   tr '[[:lower:]]' '[[:upper:]]'   echo    cut -c2-3 tr '[[:upper:]]' '[[:lower:]]' } if [ ! -w $HOME ] ; then   echo " 
 #!/bin/sh # addagenda - Prompts the user to add a new event for the agenda script. agendafile="$HOME/.agenda" isDayName() { # return = 0 if all is well, 1 on error case $(echo $1  tr '[[:upper:]]' '[[:lower:]]') in sun*mon*tue*wed*thu*fri*sat*) retval=0 ;; *) retval=1 ;; esac return $retval } isMonthName() { case $(echo $1  tr '[[:upper:]]' '[[:lower:]]') in jan*feb*mar*apr*may*jun*) return 0 ;; jul*aug*sep*oct*nov*dec*) return 0 ;; *) return 1 ;; esac } normalize() { # Return string with first char uppercase, next two lowercase echo -n $1  cut -c1  tr '[[:lower:]]' '[[:upper:]]' echo $1  cut -c2-3 tr '[[:upper:]]' '[[:lower:]]' } if [ ! -w $HOME ] ; then echo "$0: cannot write in your home directory ($HOME)" >&2 exit 1 fi echo "Agenda: The Unix Reminder Service" echo -n "Date of event (day mon, day month year, or dayname): " read word1 word2 word3 junk if isDayName $word1 ; then if [ ! -z "$word2" ] ; then echo "Bad dayname format: just specify the day name by itself." >&2 exit 1 fi date="$(normalize $word1)" else if [ -z "$word2" ] ; then echo "Bad dayname format: unknown day name specified" >&2 exit 1 fi if [ ! -z "$(echo $word1sed 's/[[:digit:]]//g')" ] ; then echo "Bad date format: please specify day first, by day number" >&2 exit 1 fi if [ "$word1" -lt 1 -o "$word1" -gt 31 ] ; then echo "Bad date format: day number can only be in range 1-31" >&2 exit 1 fi if ! isMonthName $word2 ; then echo "Bad date format: unknown month name specified." >&2 exit 1 fi word2="$(normalize $word2)" if [ -z "$word3" ] ; then date="$word1$word2" else if [ ! -z "$(echo $word3sed 's/[[:digit:]]//g')" ] ; then echo "Bad date format: third field should be year." >&2 exit 1 elif [ $word3 -lt 2000 -o $word3 -gt 2500 ] ; then echo "Bad date format: year value should be 2000-2500" >&2 exit 1 fi date="$word1$word2$word3" fi fi echo -n "One-line description: " read description # Ready to write to data file echo "$(echo $datesed 's/ //g')$description" >> $agendafile exit 0 
: cannot write in your home directory ($HOME)" >&2 exit 1 fi echo "Agenda: The Unix Reminder Service" echo -n "Date of event (day mon, day month year, or dayname): " read word1 word2 word3 junk if isDayName $word1 ; then if [ ! -z "$word2" ] ; then echo "Bad dayname format: just specify the day name by itself." >&2 exit 1 fi date="$(normalize $word1)" else if [ -z "$word2" ] ; then echo "Bad dayname format: unknown day name specified" >&2 exit 1 fi if [ ! -z "$(echo $word1sed 's/[[:digit:]]//g')" ] ; then echo "Bad date format: please specify day first, by day number" >&2 exit 1 fi if [ "$word1" -lt 1 -o "$word1" -gt 31 ] ; then echo "Bad date format: day number can only be in range 1-31" >&2 exit 1 fi if ! isMonthName $word2 ; then echo "Bad date format: unknown month name specified." >&2 exit 1 fi word2="$(normalize $word2)" if [ -z "$word3" ] ; then date="$word1$word2" else if [ ! -z "$(echo $word3sed 's/[[:digit:]]//g')" ] ; then echo "Bad date format: third field should be year." >&2 exit 1 elif [ $word3 -lt 2000 -o $word3 -gt 2500 ] ; then echo "Bad date format: year value should be 2000-2500" >&2 exit 1 fi date="$word1$word2$word3" fi fi echo -n "One-line description: " read description # Ready to write to data file echo "$(echo $datesed 's/ //g')$description" >> $agendafile exit 0

The second script is shorter but is used more often:

 #!/bin/sh # agenda - Scans through the user's .agenda file to see if there #   are any matches for the current or next day. agendafile="$HOME/.agenda" checkDate() {   # Create the possible default values that'll match today   weekday=   day=   month=   year=   format1="$weekday"   format2="$day$month"   format3="$day$month$year"   # and step through the file comparing dates...   IFS=""       # the reads will naturally split at the IFS   echo "On the Agenda for today:"   while read date description ; do     if [ "$date" = "$format1" -o "$date" = "$format2" -o "$date" = "$format3" ]     then       echo "  $description"     fi   done < $agendafile } if [ ! -e $agendafile ] ; then   echo " 
 #!/bin/sh # agenda - Scans through the user's .agenda file to see if there # are any matches for the current or next day. agendafile="$HOME/.agenda" checkDate() { # Create the possible default values that'll match today weekday=$1 day=$2 month=$3 year=$4 format1="$weekday" format2="$day$month" format3="$day$month$year" # and step through the file comparing dates... IFS="" # the reads will naturally split at the IFS echo "On the Agenda for today:" while read date description ; do if [ "$date" = "$format1" -o "$date" = "$format2" -o "$date" = "$format3" ] then echo " $description" fi done < $agendafile } if [ ! -e $agendafile ] ; then echo "$0: You don't seem to have an .agenda file. " >&2 echo "To remedy this, please use 'addagenda' to add events" >&2 exit 1 fi # Now let's get today's date... eval $(date "+weekday=\"%a\" month=\"%b\" day=\"%e\" year=\"%G\"") day="$(echo $daysed 's/ //g')" # remove possible leading space checkDate $weekday $day $month $year exit 0 
: You don't seem to have an .agenda file. " >&2 echo "To remedy this, please use 'addagenda' to add events" >&2 exit 1 fi # Now let's get today's date... eval $(date "+weekday=\"%a\" month=\"%b\" day=\"%e\" year=\"%G\"") day="$(echo $daysed 's/ //g')" # remove possible leading space checkDate $weekday $day $month $year exit 0

How It Works

The agenda script supports three types of recurring events: weekly events (e.g., every Wednesday), annual events (e.g., every August 3), and one-time events (e.g., 1 January, 2010). As entries are added to the agenda file, their specified dates are normalized and compressed so that 3 August becomes 3Aug, and Thursday becomes Thu. This is accomplished with the normalize function:

 normalize() {   # Return string with first char uppercase, next two lowercase   echo -n   cut -c1   tr '[[:lower:]]' '[[:upper:]]'   echo   cut -c2-3 tr '[[:upper:]]' '[[:lower:]]' } 

This chops any value entered down to three characters , ensuring that the first is uppercase and the second and third are lowercase. This format matches the standard abbreviated day and month name values from the date command output, which is critical for the correct functioning of the agenda script.

The agenda script checks for events by taking the current date and transforming it into the three possible date string formats (dayname, day+month, and day+month+year). It then simply compares each of these date strings to each line in the .agenda data file. If there's a match, that event is shown to the user. While long, the addagenda script has nothing particularly complex happening in it.

In my opinion, the coolest hack is how an eval is used to assign variables to each of the four date values needed:

 eval $(date "+weekday=\"%a\" month=\"%b\" day=\"%e\" year=\"%G\"") 

It's also possible to extract the values one by one (for example, weekday="$(date +%a)" ), but in very rare cases this method can fail if the date rolls over to a new day in the middle of the four date invocations, so a succinct single invocation is preferable. In either case, unfortunately , date returns a day number with either a leading zero or a leading space, neither of which is desired. So the line of code immediately subsequent to the line just shown strips the leading space from the value, if present, before proceeding.

Running the Script

The addagenda script prompts the user for the date of a new event. Then, if it accepts the date format, the script prompts for a one-line description of the event.

The companion agenda script has no parameters and, when invoked, produces a list of all events scheduled for the current date.

The Results

To see how this pair of scripts works, let's add a number of new events to the database:

 $  addagenda  Agenda: The Unix Reminder Service Date of event (day mon, day month year, or dayname):  31 October  One line description:  Halloween  $  addagenda  Agenda: The Unix Reminder Service Date of event (day mon, day month year, or dayname):  30 March  One line description:  Penultimate day of March  $  addagenda  Agenda: The Unix Reminder Service Date of event (day mon, day month year, or dayname):  Sunday  One line description:  sleep late (hopefully)  $  addagenda  Agenda: The Unix Reminder Service Date of event (day mon, day month year, or dayname):  marc 30 03  Bad date format: please specify day first, by day number $  addagenda  Agenda: The Unix Reminder Service Date of event (day mon, day month year, or dayname):  30 march 2003  One line description:  IM Marv to see about dinner  

Now the agenda script offers a quick and handy reminder of what's happening today:

 $  agenda  On the Agenda for today:   Penultimate day of March   sleep late (hopefully)   IM Marv to see about dinner 

Notice that it matched entries formatted as day+month, day of week, and day+month+year. For completeness, here's the associated .agenda file, with a few additional entries:

 $  cat ~/.agenda  14FebValentine's Day 25DecChristmas 3AugDave's Birthday 4JulIndependence Day (USA) 31OctHalloween 30MarPenultimate day of March Sunsleep late (hopefully) 30Mar2003IM Marv to see about dinner 

Hacking the Script

This script really just scratches the surface of this complex and interesting topic. It'd be nice to have it look a few days ahead, for example, which can be accomplished in the agenda script by doing some date math. If you have the GNU date command, date math (e.g., today + 2 days) is easy. If you don't, well, it requires quite a complex script to enable date math solely in the shell.

Another, perhaps easier hack would be to have agenda output Nothing scheduled for today when there are no matches for the current date, rather than On the Agenda for today: and no further output.

Note that this script could also be used on a Unix box for sending out systemwide reminders about events like backup schedules, company holidays, and employee birthdays. Simply have the agenda script on each user's machine point to a shared read-only .agenda file, and then add a call to the agenda script in each user's .login or similar file.




Wicked Cool Shell Scripts. 101 Scripts for Linux, Mac OS X, and Unix Systems
Wicked Cool Shell Scripts
ISBN: 1593270127
EAN: 2147483647
Year: 2004
Pages: 150
Authors: Dave Taylor

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net