Monday, December 8, 2008

Simple loops and counters in bash

Welcome to this weeks issue of Practical Shell Scripting. Last week I gave an example and explanation of a shell script that did some reading from a text file in a line by line fashion. This week I'm going to give a discussion of basic looping and simple math comparison. First lets look at the basics of doing simple math and comparison testing. Then I'll give a real world example of when this stuff might be used.

One of the confusing things for me is the syntax that bash uses for incrementing. The basic construct is just like that of basic, and I think maybe pascal and cobol, but it's been a while
since I coded in those languages. Specifically bash uses the word let to carry out arithmetic operations. However, here's where it gets a little funky. Instead of a simple statement like

let A=A+1

Which is how this is done in most formal languages. However bash uses an expression like this:

let $((COUNTER=$COUNTER+1))

In order to get this right and make it work the $(( on the left side must be just like that and the variable COUNTER must be next to them. While the second half must be $COUNTER, referencing the variable as one normally would. If the syntax is not exactly like this you can pretty much count on having some problems.

How and why this works is something that I have no idea. I only know that it does, perhaps someday in my travels this will be revealed to me, but for now I'm willing to take it for granted
that this is simply how we do things in bash. There is another syntax for doing math that I have come across in some bash scripts, however I have found that it doesn't always work with every version of bash and the example that I have given seems to be very portable across older and newer version.

I have one more topic that I need to bring up here before we get into the whole code example, and that is comparison operators. Bash uses a collection of acronym like operators to do comparison, you see the same syntax in perl. However, for us C/C++ guys it looks pretty strange. The basic operators are:

-eq ##equal to
-ne ## not equal to
-le ## less than or equal to
-lt ## less than
-gt ## greater than
-ge ## greater than or equal to

Now here's an example of a very simple while loop that uses both a math variable that gets incremented and a comparison operator to break the loop.

#!/bin/bash
##---------------------------------------------------------------------##
##
## This is a simple looping example with a counter and
## a math comparison operator.
##
##
##---------------------------------------------------------------------##

COUNTER=0
MAX_SIZE=10

while [ $COUNTER -lt $MAX_SIZE ]
do
let $((COUNTER=$COUNTER+1))
echo $COUNTER
sleep 1
done


Now I'd like to share a real world example that I had to write a few years back to do some house cleaning in a directory to make sure that we didn't overflow the drive. The company I work does automated nightly builds using a cron job to make sure that the coders didn't break anything during the days work. Also, there are additional day builds in the same directory to test quick fixes and branch merges. Each build takes up around 100 to 150 megabytes of space. So the problem turns up when after a while the builds end up taking up the whole drive and then at some point the next nightly build fails and some one has to go about the time consuming and tedious job of purging off the old builds. So we came up with a rule that we would keep the most current 10 builds and all the previous ones would be deleted. If anyone wanted a specific build to be saved off that was his responsibility to do so. The house-cleaning script would be run before the regular builds were run to make sure that there was enough room.

#!/bin/bash
# this is the house-cleaning.sh
# it makes sure that there are not a lot
# of silly old bulds laying around taking up
# space
#
#
#-----------------------------------------------------
#
# $Id: $
# $Change: $
# $Author: tglaser $
# $Date: 2008/11/06 $
# $NoKeyword: $
#
#
#-------------------------------------------------------

HOME=/home/tglaser
DERIVEDDIR=$HOME/BuildDir

cd $DERIVEDDIR

pwd

counter=0
too_many_files=5


for file in `ls -t`
do
echo $file
let $((counter=$counter+1))
if [ $counter -gt $too_many_files ]; then
echo "there are $counter files or directories"
echo "So we are going to delete $file";
rm -rf $file;
else
echo "you've still got room to work here"
echo "No need to delete anything yet"
fi

echo " "
echo " "
sleep 3
done

cd $HOME

exit

##----------------------------------------------------##



The only important difference between this script and the first one is the for loop which I will go into in one of the next issues.
So that's it for this week. As always if you have any questions or comments please feel free to contact me. Happy coding.

No comments: