old-www/LDP/LG/issue25/dearman.html

465 lines
16 KiB
HTML

<!--startcut ==========================================================-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<title>Bourne / Bash Issue 25</title>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#A000A0"
ALINK="#FF0000">
<!--endcut ============================================================-->
<H4>
"Linux Gazette...<I>making Linux just a little more fun!</I>"
</H4>
<P> <HR> <P>
<!--===================================================================-->
<center>
<H2>Bourne/Bash:
<BR>Shell Programming Introduction
</H2>
<H4>By <a href="mailto:rick@ricken.demon.co.uk">Rick Dearman</a></H4>
</center>
<P> <HR> <P>
Sooner or later every UNIX user has a use for a shell script. You may
just want to do a repetitive task easier, or you may want to add a bit
more kick to an existing program. An easy way to accomplish this is to
use a shell script. One of the first shell scripts I wanted was something
that would change a directory full of files which were all in capital letters
to lowercase. I did it with this script:
<P>LCem.sh
<DD>
1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #!/bin/sh</DD>
<DD>
2</DD>
<DD>
3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DIR=$1</DD>
<DD>
4</DD>
<DD>
5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for a in `ls $DIR`</DD>
<DD>
6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; do</DD>
<DD>
7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
fname=`echo $a | tr A-Z a-z`</DD>
<DD>
8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
mv $DIR/$a $DIR/$fname</DD>
<DD>
9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; done;</DD>
<DD>
10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit 0</DD>
<DD>
11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #this script will output and error
if the file is already lowercase, and assumes argument is a directory</DD>
<P>Line one tells the computer which shell to use, in this case it is "sh"
the bourne shell ( or this may be a link to the bash shell ). The combination
of the two symbols #! are special to the shell and indicates what shell
will run this script. It IS NOT IGNORED like other comment lines. Line
3 sets a variable called DIR to equal the first argument of the input.
(Arguments start at $0, which is the name of the shell script or in this
case LCem.sh ).
<P>In line 5 we enter a control loop. In this case it is a for loop. Translated
into english this line means for every entry "a" that I get back from the
command `ls $DIR` I want to do something. The shell will replace the variable
name $DIR to whatever was typed on the command line for you. Line 6 starts
the loop.
<P>Now in line seven we make use of the UNIX utilities available , `echo`
and `tr`.&nbsp; So what we are doing is echoing whatever the current value
of $a is and piping it into tr which is short for translate. In this case
we are translating uppercase to lowercase, and setting a new variable called
fname to the result.
<P>In line eight we move the file $DIR/$a, whatever it may be to $DIR/$fname.
Line nine tells the shell to go back and do all the other $a variables
until it is done. And finally line 10 we exit the script with an error
code of zero. Line eleven is a comment.
<P>This script&nbsp; wouldn't have been needed to change one or two file
names, but because I needed to change a couple of hundred it saved me lots
of typing.&nbsp; To get this to run on your machine you would have to chmod
the file to be executable. Like this `chmod +x LCem.sh` .&nbsp; Or you
could evoke the shell command directly and give it the name of your script
like this `sh LCem.sh`. Using the comment and exclamation mark combination
would tell the kernel what shell to evoke and is the normal way to do things.
But remember if you use the #! then the file itself needs to have execution
permissions.
<P>It is only eleven lines but it shows us a lot about shell scripting.
We have learned how to get the computer to run the script using the #!
combination. This combination of a comment mark and a bang operator, or
as some people call it an exclamation mark, is used to start a shell script
without having to evoke the shell first.&nbsp; We learned that a # is how
we can write a comment into our script and have them ignored when the script
is processed. We learned how to pass arguments to the script to get input
from the user, and&nbsp; we know how to set a variable. We have glanced
at one of the many control structures we can use to control the functionality
of a script.
<P>Don't worry if you didn't really get all of that. We shall now move
on to explaining some of the most common decision making / control structures.
The first one we want to look at is the `if` statement. In every programming
language we want to be able to change the flow of the program based on
various conditions. For example if a file is in this directory do one thing.
If it isn't do something else. The syntax for the if command is:
<P>if expression then
<BR>&nbsp;&nbsp;&nbsp; commands
<BR>fi
<P>So if the expression is true the statements inside the if block are
executed. Lets look at a simple example of the if statement.
<P>WhoMe.sh
<P>1&nbsp;&nbsp;&nbsp; #!/bin/sh
<BR>2
<BR>3&nbsp;&nbsp;&nbsp; # set the variable ME to the first argument after
the command.
<BR>4&nbsp;&nbsp;&nbsp; ME=$1
<BR>5
<BR>6&nbsp;&nbsp;&nbsp; # grep through the passwd file discarding the output
and see if $ME is in the file
<BR>7&nbsp;&nbsp;&nbsp; if&nbsp; grep $ME /etc/passwd > /dev/null
<BR>8&nbsp;&nbsp;&nbsp; then
<BR>9&nbsp;&nbsp;&nbsp; # if $ME is in the file out put the following line
<BR>10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; echo "You are a user"
<BR>11&nbsp;&nbsp;&nbsp; fi
<BR>&nbsp;
<BR>&nbsp;
<BR>&nbsp;Notice the extensive use of comments on lines 3, 6, and 9. You
should try to comment you scripts as much as possible because someone else
may need to look at it later. In six months you may not remember what you
were doing, so you might need the comments as well.
<P>Using the if statement we can now correct some of the errors which would
occur in the lowercasing script. In LCem.sh the script will hang if the
user doesn't input a directory as an argument. To check for an empty string,
we would use the following syntax:
<P>if [ ! $1 ]
<P>This means if not $1. The two new things here are the use of the bang
operator, or exclamation mark as the symbol for NOT.&nbsp; So lets add
this new knowledge to our program.
<BR>#!/bin/sh
<P>1&nbsp;&nbsp;&nbsp; if [ ! $1 ]
<BR>2&nbsp;&nbsp;&nbsp; then
<BR>3&nbsp;&nbsp;&nbsp; echo "Usage: `basename $0` directory_name"
<BR>4&nbsp;&nbsp;&nbsp;&nbsp; exit 1
<BR>5&nbsp;&nbsp;&nbsp; fi
<BR>6
<BR>7&nbsp;&nbsp;&nbsp; DIR=$1
<BR>8
<BR>9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for a in `ls $DIR`
<BR>10&nbsp;&nbsp;&nbsp; do
<BR>11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fname=`echo
$a | tr A-Z a-z`
<BR>12&nbsp;&nbsp;&nbsp;&nbsp; mv $DIR/$a $DIR/$fname
<BR>13&nbsp;&nbsp;&nbsp; done;
<P>Now if the user types in the command but not the directory then the
script will exit with a message about the proper way to use it, and an
error code of one.
<P>&nbsp;But what if we really did want to change the name of a single
file? We have already got this command wouldn't it be nice if it could
cope. If we want to do that then we need to be able to test if the argument
is a file or directory. Here is a list of the file test operators.
<BR>&nbsp;
<TABLE BORDER COLS=2 WIDTH="100%" NOSAVE >
<CAPTION>&nbsp;</CAPTION>
<TR NOSAVE>
<TD NOSAVE>
<H4>
Parameter</H4>
</TD>
<TD>
<H4>
Test</H4>
</TD>
</TR>
<TR>
<TD>-b file</TD>
<TD>True is file is a block device</TD>
</TR>
<TR>
<TD>-c file</TD>
<TD>True if file is a character special file</TD>
</TR>
<TR>
<TD>-d file</TD>
<TD>True if the file is a directory</TD>
</TR>
<TR>
<TD>-f file</TD>
<TD>True if file is a ordinary file</TD>
</TR>
<TR>
<TD>-r file&nbsp;</TD>
<TD>True if file is readable by process</TD>
</TR>
<TR>
<TD>-w file</TD>
<TD>True if file is writeable by process</TD>
</TR>
<TR>
<TD>-x file</TD>
<TD>True if file is executable</TD>
</TR>
</TABLE>
There&nbsp; are more operators but these are the most commonly used ones.
Now we can test to see if the user of our script has input a directory
or a file. so lets modify the program a bit more.
<P>1&nbsp;&nbsp;&nbsp; #!/bin/sh
<BR>2
<BR>3&nbsp;&nbsp;&nbsp; if [ ! $1 ]
<BR>4&nbsp;&nbsp;&nbsp; then
<BR>5&nbsp;&nbsp;&nbsp;&nbsp; echo "Usage: `basename $0` directory_name"
<BR>6&nbsp;&nbsp;&nbsp;&nbsp; exit 1
<BR>7&nbsp;&nbsp;&nbsp; fi
<BR>8
<BR>9&nbsp;&nbsp;&nbsp; if [ -d $1 ]
<BR>10&nbsp;&nbsp;&nbsp; then
<BR>11&nbsp;&nbsp;&nbsp;&nbsp; DIR="/$1"
<BR>12&nbsp;&nbsp;&nbsp; fi
<BR>13
<BR>14&nbsp;&nbsp;&nbsp; if [ -f $1 ]
<BR>15&nbsp;&nbsp;&nbsp; then
<BR>16&nbsp;&nbsp;&nbsp;&nbsp; DIR=""
<BR>17&nbsp;&nbsp;&nbsp; fi
<BR>18
<BR>19&nbsp;&nbsp;&nbsp; for a in `ls $DIR`
<BR>20&nbsp;&nbsp;&nbsp; do
<BR>21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fname=`echo $a | tr A-Z
a-z`
<BR>22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mv $DIR$a $DIR$fname
<BR>23&nbsp;&nbsp;&nbsp; done;
<P>We inserted lines nine through seventeen to do our file/directory checks.
If it is a directory we set DIR to equal "/$1" if not we set it blank.
Notice we now put the directory slash in with the DIR variable and we've
modified line 22 so that there is no slash between $DIR and $a. This way
the paths are correct.
<P>We still have a few problems with our script. One of them is that if
the file which is getting moved already exists then the scripts outputs
an error. What we want to do is check the file name before we attempt to
move it. Another thing is what if someone puts in more than two arguments?
We'll modify our script to accept more than one path or filename.
<P>The first problem is easily corrected by using a simple string test
and an if statement like we have use earlier. The second problem is slightly
more difficult in that we need to know how many arguments the user has
input. To discover this we'll use a special shell variable which is already
supplied for us. It is the $# variable, this holds the number of arguments
present on the command line. Now what we want to do is loop through the
arguments until we reach the end. This time we'll use the While loop to
do our work. Finally we shall need to know how to compare integer values,
this is because we want to check the number of time we have gone through
the loop&nbsp; to the number of arguments. There are special test options
for evaluating integers, they are as follows
<BR>&nbsp;
<BR>&nbsp;
<TABLE BORDER COLS=2 WIDTH="100%" NOSAVE >
<TR>
<TD>Test</TD>
<TD>Action</TD>
</TR>
<TR>
<TD>int1 -eq int2</TD>
<TD>True if integer one is equal to integer two</TD>
</TR>
<TR>
<TD>int1 -ge int2</TD>
<TD>True if integer one is greater than or equal to integer two</TD>
</TR>
<TR>
<TD>int1 -gt int2</TD>
<TD>True if integer one is greater than integer two</TD>
</TR>
<TR>
<TD>int1 -le int2</TD>
<TD>True if integer one is less than or equal to integer two</TD>
</TR>
<TR>
<TD>int1 -lt int2</TD>
<TD>True if interger one is less then interger two.</TD>
</TR>
<TR>
<TD>int1 -ne int2</TD>
<TD>True if integer one is not equal to integer two</TD>
</TR>
</TABLE>
&nbsp;
<P>Using this new knowledge we'll modify our program.
<P>1&nbsp;&nbsp;&nbsp; #!/bin/sh
<BR>2
<BR>3&nbsp;&nbsp;&nbsp; if [ ! $1 ]
<BR>4&nbsp;&nbsp;&nbsp; then
<BR>5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; echo "Usage: `basename
$0` directory_name"
<BR>6&nbsp;&nbsp;&nbsp;&nbsp; exit 1
<BR>7&nbsp;&nbsp;&nbsp; fi
<BR>8
<BR>9&nbsp;&nbsp;&nbsp; while [ $# -ne 0&nbsp; ]
<BR>10&nbsp;&nbsp;&nbsp; do
<BR>11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
if [ -d $1 ]
<BR>12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
then
<BR>13&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
DIR="/$1"
<BR>14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
fi
<BR>15
<BR>16&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
if [ -f $1 ]
<BR>17&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;
then
<BR>18&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
DIR=""
<BR>19&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
fi
<BR>20
<BR>21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
for a in `ls $DIR`
<BR>22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
do
<BR>23&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
fname=`echo $a | tr A-Z a-z`
<BR>24&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
if [ $fname != $a ]
<BR>25&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
then
<BR>26&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;
mv $DIR$a $DIR$fname
<BR>27&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
fi
<BR>28&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
done;
<BR>29
<BR>30&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;
shift
<BR>31&nbsp;&nbsp;&nbsp; done
<P>What we've done here is to insert a while loop on line 9 which checks
to see if the arguments listing is equal to zero. This may seem like we
just created an infinite loop but the command on line 30 the shift saves
us. You see the shift command basically discards the command nearest the
command name. (LCem.sh) and replaces it with the one to the right. This
loop will succeed in discarding all the arguments eventually and then will
equal zero and exit our loop.
<P>And finally note the if statement on line 24, this checks to see if the
file name is already lowercase and if so ignores it.
<P>I hope you have enjoyed this brief introduction to Bourne / Bash programming.
I would encourage you to try some of these examples for yourself. In fact
if you want you could make this script much better by using a switch like
-l to lowercase and -u to uppercase and modifying the script to handle
it.
<BR>&nbsp;
<P>I take full responsibility for any errors or mistakes in the above documentation.
Please send any comments or questions to <A HREF="mailto:rick@ricken.demon.co.uk">rick@ricken.demon.co.uk</A>
<P>REFERENCES:
<P>The UNIX&nbsp;programming environment
<BR>by Brian W. Kernighan &amp; Rob Pike
<BR>Published by Prentice Hall
<P>Inside UNIX
<BR>Published by New Riders
<!--===================================================================-->
<P> <hr> <P>
<center><H5>Copyright &copy; 1998, Rick Dearman <BR>
Published in Issue 25 of <i>Linux Gazette</i>, February 1998</H5></center>
<!--===================================================================-->
<P> <hr> <P>
<A HREF="./index.html"><IMG ALIGN=BOTTOM SRC="../gx/indexnew.gif"
ALT="[ TABLE OF CONTENTS ]"></A>
<A HREF="../index.html"><IMG ALIGN=BOTTOM SRC="../gx/homenew.gif"
ALT="[ FRONT PAGE ]"></A>
<A HREF="./doyle.html"><IMG SRC="../gx/back2.gif"
ALT=" Back "></A>
<A HREF="./clueless.html"><IMG SRC="../gx/fwd.gif" ALT=" Next "></A>
<P> <hr> <P>
<!--startcut ==========================================================-->
</BODY>
</HTML>
<!--endcut ============================================================-->