The case study for shell programming

A Second Case Study for Shell Programming

Summary

A solution to a bash script problem from the lab exam 2006 is discussed.


The Problem: Step 1

The CS department at ANU runs a cake club. Members meet every second Tuesday, at 10.30 am in the commons room and drink coffee or tea with a cake provided by one of the members. The club has a manager whose responsibility is to notify every club member about every meeting (via email) and establish whose turn it is to bring the cake.

The member list is maintained in the file members.txt which has the following CSV format:

email@address,full name[,status]

This is an example of such file:

u1234456@anu.edu.au,Alexei B Khorev
rms@fsf.org,Richard M Stallman,HonMem
u2345678@anu.edu.au,Ian Barnes
torvalds@transmeta.com,Linus B. Torvalds,HonMem
chris.johnson@anu.edu.au,Chris Johnson
  	...	...	....

As you can see, there are two categories: honorary members (marked by HonMem) and regular members (marked by the empty field). Only the regular members have to provide a cake for every meeting. The cake duty is regulated by the roster.txt file, which consists of all regular members. The format of the roster.txt file is the same as members.txt but without the third field (the status). The initial roster.txt can be easily generated from the members.txt file by running the command:

shell-prompt> grep -v HonMem members.txt > roster.txt
shell-prompt> cat roster.txt
u1234456@anu.edu.au,Alexei B Khorev
u2345678@anu.edu.au,Ian Barnes
chris.johnson@anu.edu.au,Chris Johnson
  	...	...	....

We rely on the constraint (which will be assumed throughout the whole exercise) that neither email address, nor any part of the full name contain the pattern "HonMem". The first member on the list is the person who has to bring cake for the next club meeting.

The task is to write a bash script called cake which will:

  1. read the roster.txt file and print out the name of the person whose turn it is to bring cake
  2. read the members.txt file, and send an "email" to every member about the forthcoming meeting. For the person whose turn it is to bring the cake, the message will also contain a reminder about the roster duty. Because we do not want to send real emails to fictitious addresses from the members.txt list, you will simulate the sending of email by echoing the line which should contain the email address of the person to whom the message is intended (consider this as the debugging phase; when operational, the cake script will build the message body and send it to the appropriate email address)
  3. update the roster.txt file by moving the first line (which corresponded to the member who is bringing the cake) to the bottom of the roster.txt list (roster rotation)
  4. display the new roster

The command cake will be run as follows:

shell-prompt> ./cake < members.txt

The output will look something like this:

	Hello, Alexei!
	Remind you that the cake club is meeting today at 10.30 am.
	This time it is your turn to bring the cake!
	See you there.
	The Cake club manager
	(sending to u1234456@anu.edu.au)

	Hello, Ian!
	Remind you that the cake club is meeting today at 10.30 am.
	See you there.
	The Cake club manager
	(sending to u2345678@anu.edu.au)

	   .... .... ....
	   .... .... ....
	   .... .... ....
	
	The new roster is
	u2345678@anu.edu.au,Ian Barnes
	chris.johnson@anu.edu.au,Chris Johnson
	u1234456@anu.edu.au,Alexei B Khorev

Solution to Step 1 (discussed orally)

Result

	#!/bin/bash
	#who is the caker man today
	caker=$(head -1 roster.txt | cut -d',' -f2)
	caker_email=$(head -1 roster.txt | cut -d',' -f1)
	echo The roster man today is $caker
	echo His email address is $caker_email
	echo
	#looping through members.txt list and sending emails
	while read line
	do
	email=$(echo $line | cut -d',' -f1)
	fn=$(echo $line | cut -d',' -f2 | cut -f1)
	echo Hello, ${fn}!
	echo Remind you that the cake club is meeting today at 10.30 am.
	if [ $caker_email == $email ]
	then
	echo This time it\'s your turn to bring the cake!
	fi
	echo See you there.
	echo The Cake club manager, `whoami`
	#to simulate sending the email
	echo "(sending to $email)"
	echo
	done
	#updating the roster
	#using the necessary option to only pick all lines but the last one in the file
	tail +2 roster.txt > tmp.txt
	head -1 roster.txt >> tmp.txt
	mv tmp.txt roster.txt
	echo The new roster is
	cat roster.txt

Step 2: Making the script more versatile

To enter the cake club is much easier than to enter a masonic lodge. You have to simply ask the club manager to add your email address and name to the members.txt file. Being a computer professional, the club manager will do it with another bash script. Your task in this part is to write such script. Call it addmember. The script will take two command line arguments — email address and the full name. Since the full name can contain more than one word, the second argument can be provided with quotation marks. The addmember script will be run as follows:

shell-prompt> ./addmember henry.gardner@anu.edu.au "Henry Gardner"

The new member will be added to the members.txt file with a temporary status NewMem. Therefore after the executing the above command, the members.txt file will look like this:

u1234456@anu.edu.au,Alexei B Khorev
rms@fsf.org,Richard M Stallman,HonMem
u2345678@anu.edu.au,Ian Barnes
torvalds@transmeta.com,Linus B. Torvalds,HonMem
chris.johnson@anu.edu.au,Chris Johnson
henry.gardner@anu.edu.au,Henry Gardner,NewMem

If the person who wishes to enter the club is already a member then the request must be rejected. As the ID of membership, the addmember script will use the email address (the first field). The attempt to add a duplicate member will be rejected, and and the members.txt file will remain unchanged.

Solution to Step 2 (discussed orally)

Result

shell-prompt>  cat addmember

#!/bin/bash
#check if there are CLA for adding new member
if [ $# -eq 2 ]
then
if grep $1 members.txt > /dev/null
then
echo "You are already a member"
exit 1
else 
echo $1,$2,NewMem > members.txt
echo The new members list is
cat members.txt
fi
else 
echo "Usage: addmember email@address full_name"
exit 1
fi

The cake script also gets modified:

	#!/bin/bash
	#who is the caker man today
	caker=$(head -1 roster.txt | cut -d',' -f2)
	caker_email=$(head -1 roster.txt | cut -d',' -f1)
	echo The roster man today is $caker
	echo His email address is $caker_email
	echo
	#looping through members.txt list and sending emails
	while read line
	do
	email=$(echo $line | cut -d',' -f1)
	fn=$(echo $line | cut -d',' -f2 | cut -f1)
	echo Hello, ${fn}!
	echo Remind you that the cake club is meeting today at 10.30 am.
	if [ $caker_email == $email ]
	then
	echo This time it\'s your turn to bring the cake!
	fi
	echo See you there.
	echo The Cake club manager, `whoami`
	#to simulate sending the email
	echo "(sending to $email)"
	echo
	done
	#updating the roster
	grep NewMem members.txt | cut -d',' -f1,2 >> roster.txt
	#using the necessary option to only pick all lines but the last one in the file
	tail +2 roster.txt > tmp.txt
	head -1 roster.txt >> tmp.txt
	mv tmp.txt roster.txt
	echo The new roster is
	cat roster.txt

Step 3: A Final Touch

This can be a bit hard.

Modifiy the cake script to eliminate "sending emails" if the script has already been run recently. Before executing all the commands in the cake script, check whether the roster.txt file was modified during last 7 days (the roster.txt file can be only modified by the cake script, but not by the addmember script). If the roster.txt file was last modified less than 7 days ago, the script must abort all the remaining executions and exit. If the roster.txt file was last modified more than 7 days ago, the script must continue and execute all the commands which it contains.

This feature would prevent from unnecessary emailing and — more importantly — distorting the roster.txt file.

Hint. You will need to know how to determined whether a file was modified in the last 7 days. Check the find command and the option -mtime (example of such use will be presented in today's lecture).

Solution to Step 3 (discussed orally)

Result

	#!/bin/bash
	#checking if the script has been run recently
	find .	-mtime +8 | grep roster > /dev/null
	if [ $? -eq 1 ]
	then
	echo The reminder has been already sent.
	exit 1
	fi
	#who is the caker man today
	caker=$(head -1 roster.txt | cut -d',' -f2)
	caker_email=$(head -1 roster.txt | cut -d',' -f1)
	echo The roster man today is $caker
	echo His email address is $caker_email
	echo
	while read line
	do
	email=$(echo $line | cut -d',' -f1)
	fn=$(echo $line | cut -d',' -f2 | cut -f1)
	echo Hello, ${fn}!
	echo Remind you that the cake club is meeting today at 10.30 am.
	if [ $caker_email == $email ]
	then
	echo This time it\'s your turn to bring the cake!
	fi
	echo See you there.
	echo The Cake club manager, `whoami`
	#to simulate sending the email
	echo "(sending to $email)"
	echo
	done
	#updating the roster
	grep NewMem members.txt | cut -d',' -f1,2 >> roster.txt
	tail +2 roster.txt > tmp.txt
	head -1 roster.txt >> tmp.txt
	mv tmp.txt roster.txt
	echo The new roster is
	cat roster.txt
	#Making new members into regular members
	grep -v NewMem members.txt > tmp.txt
	grep NewMem members.txt | cut -d',' -f1,2 >> tmp.txt
	mv tmp.txt members.txt