465 lines
16 KiB
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 #!/bin/sh</DD>
|
|
|
|
<DD>
|
|
2</DD>
|
|
|
|
<DD>
|
|
3 DIR=$1</DD>
|
|
|
|
<DD>
|
|
4</DD>
|
|
|
|
<DD>
|
|
5 for a in `ls $DIR`</DD>
|
|
|
|
<DD>
|
|
6 do</DD>
|
|
|
|
<DD>
|
|
7
|
|
fname=`echo $a | tr A-Z a-z`</DD>
|
|
|
|
<DD>
|
|
8
|
|
mv $DIR/$a $DIR/$fname</DD>
|
|
|
|
<DD>
|
|
9 done;</DD>
|
|
|
|
<DD>
|
|
10 exit 0</DD>
|
|
|
|
<DD>
|
|
11 #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`. 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 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. To get this to run on your machine you would have to chmod
|
|
the file to be executable. Like this `chmod +x LCem.sh` . 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. 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 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> 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 #!/bin/sh
|
|
<BR>2
|
|
<BR>3 # set the variable ME to the first argument after
|
|
the command.
|
|
<BR>4 ME=$1
|
|
<BR>5
|
|
<BR>6 # grep through the passwd file discarding the output
|
|
and see if $ME is in the file
|
|
<BR>7 if grep $ME /etc/passwd > /dev/null
|
|
<BR>8 then
|
|
<BR>9 # if $ME is in the file out put the following line
|
|
<BR>10 echo "You are a user"
|
|
<BR>11 fi
|
|
<BR>
|
|
<BR>
|
|
<BR> 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. So lets add
|
|
this new knowledge to our program.
|
|
<BR>#!/bin/sh
|
|
|
|
<P>1 if [ ! $1 ]
|
|
<BR>2 then
|
|
<BR>3 echo "Usage: `basename $0` directory_name"
|
|
<BR>4 exit 1
|
|
<BR>5 fi
|
|
<BR>6
|
|
<BR>7 DIR=$1
|
|
<BR>8
|
|
<BR>9 for a in `ls $DIR`
|
|
<BR>10 do
|
|
<BR>11 fname=`echo
|
|
$a | tr A-Z a-z`
|
|
<BR>12 mv $DIR/$a $DIR/$fname
|
|
<BR>13 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> 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>
|
|
<TABLE BORDER COLS=2 WIDTH="100%" NOSAVE >
|
|
<CAPTION> </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 </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 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 #!/bin/sh
|
|
<BR>2
|
|
<BR>3 if [ ! $1 ]
|
|
<BR>4 then
|
|
<BR>5 echo "Usage: `basename $0` directory_name"
|
|
<BR>6 exit 1
|
|
<BR>7 fi
|
|
<BR>8
|
|
<BR>9 if [ -d $1 ]
|
|
<BR>10 then
|
|
<BR>11 DIR="/$1"
|
|
<BR>12 fi
|
|
<BR>13
|
|
<BR>14 if [ -f $1 ]
|
|
<BR>15 then
|
|
<BR>16 DIR=""
|
|
<BR>17 fi
|
|
<BR>18
|
|
<BR>19 for a in `ls $DIR`
|
|
<BR>20 do
|
|
<BR>21 fname=`echo $a | tr A-Z
|
|
a-z`
|
|
<BR>22 mv $DIR$a $DIR$fname
|
|
<BR>23 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 to the number of arguments. There are special test options
|
|
for evaluating integers, they are as follows
|
|
<BR>
|
|
<BR>
|
|
<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>
|
|
|
|
|
|
<P>Using this new knowledge we'll modify our program.
|
|
|
|
<P>1 #!/bin/sh
|
|
<BR>2
|
|
<BR>3 if [ ! $1 ]
|
|
<BR>4 then
|
|
<BR>5 echo "Usage: `basename
|
|
$0` directory_name"
|
|
<BR>6 exit 1
|
|
<BR>7 fi
|
|
<BR>8
|
|
<BR>9 while [ $# -ne 0 ]
|
|
<BR>10 do
|
|
<BR>11
|
|
|
|
if [ -d $1 ]
|
|
<BR>12
|
|
|
|
then
|
|
<BR>13
|
|
|
|
DIR="/$1"
|
|
<BR>14
|
|
|
|
fi
|
|
<BR>15
|
|
<BR>16
|
|
|
|
if [ -f $1 ]
|
|
<BR>17
|
|
|
|
then
|
|
<BR>18
|
|
|
|
DIR=""
|
|
<BR>19
|
|
|
|
fi
|
|
<BR>20
|
|
<BR>21
|
|
|
|
for a in `ls $DIR`
|
|
<BR>22
|
|
|
|
do
|
|
<BR>23
|
|
|
|
fname=`echo $a | tr A-Z a-z`
|
|
<BR>24
|
|
|
|
if [ $fname != $a ]
|
|
<BR>25
|
|
|
|
then
|
|
<BR>26
|
|
|
|
|
|
mv $DIR$a $DIR$fname
|
|
<BR>27
|
|
|
|
fi
|
|
<BR>28
|
|
|
|
done;
|
|
<BR>29
|
|
<BR>30
|
|
|
|
shift
|
|
<BR>31 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>
|
|
|
|
<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 programming environment
|
|
<BR>by Brian W. Kernighan & Rob Pike
|
|
<BR>Published by Prentice Hall
|
|
|
|
<P>Inside UNIX
|
|
<BR>Published by New Riders
|
|
|
|
<!--===================================================================-->
|
|
<P> <hr> <P>
|
|
<center><H5>Copyright © 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 ============================================================-->
|
|
|