old-www/LDP/LG/issue57/okopnik.html

647 lines
25 KiB
HTML

<!--startcut ==============================================-->
<!-- *** BEGIN HTML header *** -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML><HEAD>
<title>Introduction to Shell Scripting LG #57</title>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#0000AF"
ALINK="#FF0000">
<!-- *** END HTML header *** -->
<CENTER>
<A HREF="http://www.linuxgazette.com/">
<H1><IMG ALT="LINUX GAZETTE" SRC="../gx/lglogo.jpg"
WIDTH="600" HEIGHT="124" border="0"></H1></A>
<!-- *** BEGIN navbar *** -->
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="nielsen2.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue57/okopnik.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../faq/index.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="sharma.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
<!-- *** END navbar *** -->
<P>
</CENTER>
<!--endcut ============================================================-->
<H4 ALIGN="center">
"Linux Gazette...<I>making Linux just a little more fun!</I>"
</H4>
<P> <HR> <P>
<!--===================================================================-->
<center>
<H1><font color="maroon">Introduction to Shell Scripting</font></H1>
<H4>By <a href="mailto:ben-fuzzybear@yahoo.com">Ben Okopnik</a></H4>
</center>
<P> <HR> <P>
<!-- END header -->
<BLOCKQUOTE>
<STRONG> "When the only hammer you have is C++, the whole world looks
like a thumb."</STRONG><BR>
<CITE> -- Keith Hodges</CITE>
</BLOCKQUOTE>
<H2>The Myth of Design; the Mystery of Color</H2>
<P> At this point in the series, we're getting pretty close to what I consider
the upper limit of basic shell scripting; there are still a few areas I'd
like to cover, but most of the issues involved are getting rather, umm,
involved. A good example is the `tput' command that I'll be covering this
month: in order to really understand what's going on, as opposed to just
using it, you'd need to learn all about "termcap/terminfo" controversy
(A.K.A. one of the main arguments in the "Why UNIX Sucks" debate) - a
deep, involved, ugly issue (for a fairly decent and simple explanation,
see Hans de Goede's <A HREF="http://electron.et.tudelft.nl/~jdegoede/fixkeys.html">fixkeys.tgz</A>, which contains a neat little "HOWTO".
For a more in-depth study, the <A HREF="http://www.ssc.com/mirrors/LDP/HOWTO/Keyboard-and-Console-HOWTO.html">Keyboard-and-Console-HOWTO</A> is an awesome
reference on the subject). I'll try to make sense despite the confusion,
but be warned...
<H2>Affunctionately Yours</H2>
<P> The concept of functions is not a difficult one, but is certainly very
useful: they are simply blocks of code that you can execute under a
single label. Unlike a script, they do not spawn a new subshell but
execute within the current one. They can be used within a script, or
stand-alone.
<P> Let's see how a function works in a shell script:
<A HREF="misc/okopnik/venice_beach.sh.txt">(text version)</A>
<PRE>
#!/bin/bash
#
# "venice_beach" - translates English to beach-bunny
function kewl () # Makes everything, like, totally rad, dude!
{
[ -z $1 ] &amp;&&amp; {
echo "That was bogus, dude."
return
}
echo "Like, I'm thinkin', dude, gimme a minute..."
sleep 3
echo $@', dude!'
# While the function runs, positional parameters ($1, etc.)
# refer to those given the function - not the shell script.
}
clear
kewl $(echo "$@"|tr -d "[:punct:]") # Strip off all punctuation
</PRE>
<P> This, umm, incredibly important script should print the "I'm
thinkin'..." line followed by a thoroughly mangled list of parameters:
<PRE>
Odin:~$ venice_beach Right on
Like, I'm thinkin', dude, gimme a minute...
Right on, dude!
Odin:~$ venice_beach Rad.
Like, I'm thinkin', dude, gimme a minute...
Rad, dude!
Odin:~$ venice_beach Dude!
Like, I'm thinkin', dude, gimme a minute...
Dude, dude!
</PRE>
<P> Functions may also be loaded into the environment, and invoked just
like shell scripts; we'll talk about sourcing functions later on. For
those of you who use Midnight Commander, check out the "mc ()"
function described in their man page - it's a very useful one, and is
loaded from ".bashrc".
<P> Important item: functions are created as "function pour_the_beer ()
{ ... }" or "pour_the_beer () { ... }" (the keyword is optional); they
are invoked as "pour_the_beer" (no parentheses). Also, be very careful
(as in, _do not_ unless you really mean it) about using an "exit"
statement in a function: since you're running the code in the current
shell, this will cause you to exit your current (i.e. the "login")
shell! Exiting a shell script this way can produce some very ugly
results, like a `hung' shell that has to be killed from another VT
(yep, I've experimented). The statement that will terminate a function
without killing the shell is "return".
<H2>Single, Free, and Easy</H2>
<P> Everything we've discussed in this series so far has a common
underlying assumption: that the script you're writing is going to be
saved and re-used. For most scripts, that's what you'd want - but what
if you have a situation where you need the structure of a script, but
you're only going to use it once (i.e., don't need or want to create a
file)? The answer is - Just Do It:
<PRE>
Odin:~$ (
&gt; echo
&gt; [ $blood_caffeine_concentration -lt 5ppm ] &amp;&&amp; {
&gt; echo $LOW_CAFFEINE_ERROR
&gt; while [ $coffee_cup != "full" ]
&gt; do
&gt; brew ttyS2 # Enable coffeepot via /dev/ttyS2
&gt; echo "Drip..."
&gt; sleep 1m
&gt; done
&gt; echo
&gt;
&gt; while [ $coffee_cup != "empty" ]
&gt; do
&gt; sip_slowly # Coffee ingestion binary, from coffee_1.6.12-4.tgz
&gt; done
&gt; }
&gt;
&gt; echo "Aaaahhh!"
&gt; echo
&gt; )
Coffee Not Found: Operator Halted!
Drip...
Drip...
Drip...
Aaaahhh!
Odin:~$
</PRE>
<P> Typing a `(' character tells "bash" that you'd like to spawn a
subshell and execute, within that subshell, the code that follows -
and this is what a shell script does. The ending character, `)',
obviously tells the subshell to 'close and execute'. For an equivalent
of a function (i.e., code executed within the current shell), the
delimiters are `{' and `}'.
<P> Of course, something like a simple loop or a single 'if' statement
doesn't even require that:
<PRE>
Odin:~$ for fname in *.c
&gt; do
&gt; echo $fname
&gt; cc $fname -o $(basename $fname .c)
&gt; done
</PRE>
<P> "bash" is smart enough to recognize a multi-part command of this type
- a handy sort of thing when you have more than a line's worth of
syntax to type (not an uncommon situation in a 'for' or a 'while'
statement). By the way, a cute thing happens when you hit the up-arrow
to repeat the last command: "bash" will reproduce everything as a
single line - with the appropriate semi-colons added. Clever, those
GNU people...
<P> No "hash-bang" ("#!/bin/bash") is necessary for a one-time script, as
it would be at the start of a script file. You know that you're
executing this as a "bash" subshell (at least I _hope_ you're running
"bash" while writing and testing a "bash" script...), whereas with a
script file you can never be sure: the user's choice of shell is a
variable, so the "hash-bang" is necessary to make sure that the script
uses the correct interpreter.
<H2>The Best Laid Plans of Mice and Men</H2>
<P> In order to write good shell scripts, you have to learn good
programming. Simply knowing the ins and outs of the commands that
"bash" will accept is far from all there is - the first step of problem
resolution is problem definition, and defining exactly what needs to
be done can be far more challenging than writing the actual script.
<P> One of the first scripts I ever wrote, "bkgr" (a random background
selector for X), had a problem - I'd call it a "race condition", but
that means something different in Unix terminology - that took a long
time and a large number of rewrites to resolve. "bkgr" is executed as
part of my ".xinitrc":
<PRE>
...
# start some nice programs
bkgr &amp;
rxvt-xterm -geometry 78x28+0+26 -tn xterm -fn 10x20 -iconic &amp;
coolicon &amp;
icewm
</PRE>
<P> OK, by the book - I background all the processes except the last one,
"icewm" (this way, the window manager keeps X "up", and exiting it
kills the server). Here was the problem: "bkgr" runs, and "paints" my
background image on the root window; fine, so far. Then, "icewm" runs
- and paints a greenish-gray background over it (as far as I've been
able to discover, there's no way to disable that other than hacking
the code).
<P> What to do? I can't put "bkgr" after "icewm" - the WM has to be last.
How about a delay after "bkgr", say 3 seconds... oh, that won't work:
it would simply delay the "icewm" start by 3 seconds. OK, how about
this (in "bkgr"):
<PRE>
...
while [ -z "$(ps ax|grep icewm)" ] # Check via 'ps' if "icewm" is up
do
sleep 1 # If not, wait, then loop
done
...
</PRE>
<P> That should work, since it'll delay the actual "root window painting"
until after "icewm" is up!
<P>
<HR NOSHADE WIDTH="50%" ALIGN="center">
<P> It didn't work, for three major reasons.
<P> Reason #1: try the above "ps ax|grep" line, from your command line,
for any process that you have running; e.g., type
<PRE>
ps ax|grep init
</PRE>
<P> Try it several times. What you will get, randomly, is either one or
two lines: just "init", or "init" and the "grep init" as well, where
"ps" manages to catch the line that you're currently executing!
<P> Reason #2: "icewm" starts, takes a second or so to load, and then
paints the root window. Worse yet, that initial delay varies - when
you start X for the first time after booting, it takes significantly
longer than subsequent restarts. "So," you'd say, "make the delay in
the loop a bit longer!" That doesn't work either - I've got two
machines, an old laptop and a desktop, and the laptop is horribly slow
by comparison; you can't "size" a delay to one machine and have it
work on both... and in my not-so-humble opinion, a script should be
universal - you shouldn't have to "adjust" it for a given machine. At
the very least, that kind of tuning should be minimized, and
preferably eliminated completely.
<P> One of the things that also caused trouble at this point is that some
of my pics are pretty large - e.g., my photos from the Kennedy Space
Center - and take several seconds to load. The overall effect was to
allow large pics to work with "bkgr", whereas the smaller ones got
overpainted - and trying to stretch the delay resulted in a
significant built-in slowdown in the X startup process, an untenable
situation.
<P> Reason #3: "bkgr" was supposed to be a random background selector as
well as a startup background selector - meaning that if I didn't like
the original background, I'd just run it again to get another one. A
built-in delay any longer than a second or so, given that a pic takes
time to paint anyway, was not acceptable.
<P> What a mess. What was needed was a conditional delay that would keep
running as long as "icewm" wasn't up, then a fixed delay that would
cover the interval between the "icewm" startup and the "root window
painting". The first thing I tried was creating a reliable `detector'
for "icewm":
<PRE>
...
delay=0
X="$(ps ax)"
while [ $(echo $X|grep -c icewm) -lt 1 ]
do
[ $delay -eq 0 ] &amp;&amp; (delay=1; sleep 3)
[ $delay -eq 1 ] &amp;&amp; sleep 1
X="$(ps ax)"
done
...
</PRE>
<P> '$X' gets set to the value of "$(ps ax)", a long string listing all
the running processes which we check for the presence of "icewm" as
the loop condition. The thing that makes all the difference here is
that "ps ax" and "grep" are not running at the same time: one runs
inside (and just before) the loop, the other is done as part of the
loop test (a nifty little hack, and well worth remembering). This
registers a count of only one "icewm" if it is running, and none if it
is not. Unfortunately, due to the finicky timing - specifically the
difference in the delays between an initial X startup and repeated
ones - this wasn't quite good enough. Lots of experimentation later,
here's a version that works:
<PRE>
...
delay=0
until [ ! $(xv -root -quit /usr/share/Eterm/tiny.gif) ]
do
delay=1
sleep 1
done
[ delay -eq 1 ] &amp;&amp; sleep 3
...
</PRE>
<P> What I'm doing here is loading a 1x1-pixel image and checking to see
if "xv" has managed to do so successfully; if it has not, I continue
looping. Once it has - and this only means that X has reached a point
where it will accept those directives from a program - I stick in a 3
second delay (but only if we've done the loop; if "icewm" is already
up, no delay is necessary or wanted). This seems to work very well no
matter what the "startup count" is. Running it this way, I have not
had a single image "overpainted", or a delay of longer than a second
or so. I was a bit concerned about the effect of all those "xv"s
running one after another, but timing the X startup with and without
"bkgr" put that to rest: I found no measurable difference (as a
guess, when "xv" exits with an error code it probably doesn't take
much in a way of resources.)
<P> Note that the resulting script is only slightly longer than the
original - what took all this time was not writing some huge, complex
magical fix but understanding the problem and defining the solution...
even though it was a strange one.
<P> There are a number of programming errors to watch out for: "race
conditions" (a security concern, not just a time conflict), the
`banana problem', the `fencepost/Obi-Wan error'... (Yes, they do have
interesting names; a story behind each one.) Reading up on a bit of
programming theory would benefit anyone who's learning to write shell
scripts; if nothing else, you won't be repeating someone else's
mistakes. My favorite reference is an ancient "C" manual, long out of
print, but there are many fine reference texts available on the net;
take a peek. "Canned" solutions for standard programming errors do
exist, tend to be language-independent, and are very good things to
have in your mental toolbox.
<H2>Coloring Fun with Dick and Jane</H2>
<P> One of the things that I used to do, way back when in the days of BBSs
and the ASCII art that went with them, is create flashy opening
screens that moved and bleeped and blinked and did all sorts of things
- without any graphics programming or anything more complicated than
those ASCII codes and ANSI escape sequences (they could get
complicated enough, thank you very much), since all of this ran on
pure text terminals. Linux, thanks to the absolutely stunning results
of work done by Jan Hubicka and his friends (if you have not seen the
"bb" demo of "aalib", you're missing out on a serious acid trip. As
far as I know, the authorities have not yet caught on, and it's still
legal), has far outstripped everything even the fanciest ASCII artist
could come up with back then ("Quake" and fractal generators on
text-only terminals, as two examples).
<P> What does this have to do with us, since we're not doing any
"aalib"-based programming? Well, there are times when you want to
create a nice-looking menu, say one you'll be using every day - and if
you're working with text, you'll need some specialized tools:
<P> 1) Cursor manipulation. The ability to position it is a must; being
able to turn it on and off, and saving and restoring the position are
nice to have.
<P> 2) Text attribute control. Bold, underline, blinking, reverse - these
are all useful in menu creation.
<P> 3) Color. Let's face it: plain old B&amp;W gets boring after a bit, and
even something as simple as a text menu can benefit from a touch of
spiffing up.
<P> So, let's start with a simple menu:
<A HREF="misc/okopnik/ho-hum.sh.txt">(text version)</A>
<PRE>
#!/bin/bash
#
# "ho-hum" - a text-mode menu
clear
while [ 1 ] # Loop `forever'
do
# We're going to do some `display formatting' to lay out the text;
# a `here-document', using "cat", will do the job for us.
cat &lt;&lt; !
M A I N M E N U
1. Business auto policies
2. Private auto policies
3. Claims
4. Accounting
5. Quit
!
echo -n " Enter your choice: "
# Why have I not followed standard indenting practice here? Because
# the `here-doc' format requires the delimiter ('!') to be on a line
# by itself - and spaces or tabs count. Just consider everything as
# being set back one indent level, and it'll all make sense.
read choice
case $choice
in
1|B|b) bizpol ;;
2|P|p) perspol ;;
3|C|c) claims ;;
4|A|a) acct ;;
5|Q|q) clear; exit ;;
*) echo; echo "\"$choice\" is not a valid option."; sleep 2 ;;
esac
clear
done
</PRE>
<P> If you copy and paste the above into a file and run it, you'll realize
why the insurance business is considered deadly dull. Erm, well, one
of the reasons, I guess. Bo-o-ring (<grin> apologies to one of my
former employers, but it's true...) Not that there's a tremendous
amount of excitement to be had out of a text menu - but surely there's
something we can do to make things just a tad brighter!
<A HREF="misc/okopnik/jazz-it-up.sh.txt">(text version)</A>
<PRE>
#!/bin/bash
#
# "jazz_it_up" - an improved text-mode menu
tput civis # Turn off the cursor
while [ 1 ]
do
echo -e '\E[44;38m' # Set colors: bg=blue, fg=white
clear # Note: colors may be different in xterms
echo -e '\E[41;38m' # bg=red
for n in `seq 6 20`
do
tput cup $n 15
echo " "
done
echo -ne '\E[45;38m' # bg=magenta
tput cup 8 25 ; echo -n " M A I N M E N U "
echo -e '\E[41;38m' # bg=red
tput cup 10 25 ; echo -n " 1. Business auto policies "
tput cup 11 25 ; echo -n " 2. Private auto policies "
tput cup 12 25 ; echo -n " 3. Claims "
tput cup 13 25 ; echo -n " 4. Accounting "
tput cup 14 25 ; echo -n " 5. Quit "
# I would have really liked to make the cursor invisible here -
# but my "xterm" does not implement the `civis' option for "tput"
# which is what does that job. I could experiment and hack it
# into "terminfo"... but I'm not *that* ambitious.
echo -ne '\E[44;38m' # bg=blue
tput cup 16 28 ; echo -n " Enter your choice: "
tput cup 16 48
read choice
tput cup 18 30
case $choice
in
1|B|b) bizpol ;;
2|P|p) perspol ;;
3|C|c) claims ;;
4|A|a) acct ;;
5|Q|q) tput sgr0; clear; exit ;;
*) tput cup 18 26; echo "\"$choice\" is not a valid option.";
sleep 2 ;;
esac
done
</PRE>
<P> This is NOT, by any means, The Greatest Menu Ever Written - but it
gives you an idea of basic layout and color capabilities. Note that
the colors may not work exactly right in your xterm, depending on your
hardware and your "terminfo" version - I did this as a quick slapdash
job to illustrate the capabilities of "tput" and "echo -e". These
things can be made portable - "tput" variables are common to almost
everything, and color values can be set based on the value of `$TERM'
- but this script falls short of that. These codes, by the way, are
basically the same for DOS, Linux, etc., text terminals - they're
dependent on hardware/firmware rather than the software we're running.
Xterms, as always, are a different breed...
<P> So, what's this "tput" and "echo -e" stuff? Well, in order to `speak'
directly to our terminal, i.e., give it commands that will be used to
modify the terminal characteristics, we need a method of sending
control codes. The difference between these two methods is that while
"echo -e" accepts "raw" escape codes (like '\E[H\E[2J' - same thing as
<esc>H<esc>2J), "tput" calls them as `capabilities' ("tput clear" does
the same thing as "echo -e" with the above code) and is
(theoretically) term-independent (it uses the codes in the terminfo
database for the current term-type). The problem with "tput" is that
most of the codes for it are as impenetrable as the escape codes that
they replace: things like `civis' ("make cursor invisible"), `cup'
("move cursor to x y"), and `smso' ("start standout mode") are just as
bad as memorizing the codes themselves! Worse yet, I've never found a
reference that lists them all... well, just remember that the two
methods are basically interchangeable, and you'll be able to use
whatever is available. The "infocmp" command will list the
capabilities and their equivalent codes for a given terminal type;
when run without any parameters, it returns the set for the current
term-type.
<P> Colors and attributes for an ISO6429 (ANSI-compliant) terminal, i.e.,
a typical text terminal, can be found in the "ls" man page, in the
"DISPLAY COLORIZATION" section; xterms, on the other hand, vary so
much in their interpretation of exactly what a color code means, that
you basically have to "try it and see":
<A HREF="misc/okopnik/colsel.sh.txt">(text version)</A>
<PRE>
#!/bin/bash
#
# "colsel" - a term color selector
for n in `seq 40 47`
do
for m in `seq 30 37`
do
echo -en "\E[$m;${n}m"
clear
echo $n $m
read
done
done
</PRE>
<P> This little script will run through the gamut of colors of which your
termtype is capable. Just remember the number combos that appeal to
you, and use them in your "echo -e '\E[&lt;bg&gt;;&lt;fg&gt;m'" statements.
<P> Note that the positions of the numbers within the statement don't
matter; also note that some combinations will make your text into
unreadable gibberish ("12" seems to do that on most xterms). Don't let
it bother you; just type "reset" or "tput sgr0" and hit "Enter".
<H2>Wrapping It Up</H2>
<P> Hmm, I seem to have made it through all of the above without too much
pain or suffering; amazing. :) Yes, some of the areas of Linux still
have a ways to go... but that's one of the really exciting things
about it: they are changing and going places. Given the amazing
diversity of projects people are working on, I wouldn't be surprised
to see someone come up with an elegant solution to the color
code/attribute mess.
<P> Next month, we'll cover things like sourcing functions (pretty
exciting stuff - reusable code!), and some really nifty goodies like
"eval" and "trap". Until then -
<P> Happy Linuxing to all!
<H2>Linux Quote of the Month</H2>
<BLOCKQUOTE>
"The words `community' and `communication' have the same root.
Wherever you put a communications network, you put a community
as well. And whenever you take away that network - confiscate it,
outlaw it, crash it, raise its price beyond affordability - then
you hurt that community.
<P> Communities will fight to defend themselves. People will fight
harder and more bitterly to defend their communities, than they
will fight to defend their own individual selves."<BR>
<CITE> -- Bruce Sterling, "Hacker Crackdown"</CITE>
</BLOCKQUOTE>
<H2>References</H2>
<P> The "man" pages for 'bash', 'builtins', 'tput', 'infocmp', 'startx'
<BR>
<A HREF="../issue53/okopnik.html">"Introduction to Shell Scripting - The Basics", LG #53</A><BR>
<A HREF="../issue54/okopnik.html">"Introduction to Shell Scripting", LG #54</A><BR>
<A HREF="../issue55/okopnik.html">"Introduction to Shell Scripting", LG #55</A><BR>
<!-- *** BEGIN copyright *** -->
<P> <hr> <!-- P -->
<H5 ALIGN=center>
Copyright &copy; 2000, Ben Okopnik<BR>
Published in Issue 57 of <i>Linux Gazette</i>, September 2000</H5>
<!-- *** END copyright *** -->
<!--startcut ==========================================================-->
<HR><P>
<CENTER>
<!-- *** BEGIN navbar *** -->
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="nielsen2.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue57/okopnik.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../faq/index.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="sharma.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
<!-- *** END navbar *** -->
</CENTER>
</BODY></HTML>
<!--endcut ============================================================-->