Bourneshell
Bourneshell
Bourneshell
Programming
Notes
The Bourne Shell
The Bourne shell is one of
a number of Unix shells (C
shell, Bash shell etc.). Like
the others, it is both a
command language and a
programming language. (A
shell is frequently called a
command interpreter.) As a
command language it
provides a user interface to
Unix/Linux. It executes
commands entered by the
user or from a file.
As a programming
language, each shell
provides I/O, variables,
conditionals, loops, and
switches. The syntax is
aimed at ease of use at a
terminal so that strings for
example do not have to be
"User-friendly''
commands
System
administration utilities
Application
utilities
A knowledge of shell
programming is essential
for the serious Unix user,
and will more than reward
the few hours invested in
exploring the shell, in time
saved developing new
commands and utilities.
Shell Programs
Shell commands may be
stored in a file. There are a
number of ways to execute
the commands in such a
file. One is to use the `sh'
command with the file as a
command line parameter
e.g. prog.sh is a file
containing 3 Unix
commands
Usage:
$ sh prog.sh
Code in prog.sh:
#!/bin/sh
pwd
ls -l
Commands may be
separated using newlines
as above or using
semicolons:
pwd ; ls -l ; date
$ chmod +x prog.sh
Make file executable - only
done once
$ prog.sh
Comments in Bourne
Shell
mybin=/usr/user/joe/jbin
name=`Bill
Bloggs'
Note that there are no
spaces around the '='
symbol.
Input
read
FILENAME
read word1
word2 rest
with input
Output
The echo command is
used to display messages
and the values of variables.
The program echo simply
prints its command line
parameters on the
standard output.
echo "Hello
Second Science"
echo
Directory name is
$mybin
echo $name
echo -n
"Enter a file name: ''
$ cp prog
$mybin
The shell simply
substitutes the value of the
variable for each
occurrence of the variable
name. So you can use the
variable value as part of a
string:
$ ls -l
$mybin/prog
Program prog1.sh
contains:
export mybin
mybin=/usr/user/joe/jbin
current=/usr/user/joe
prog2.sh # Call prog2.sh
echo "Back in
prog1.sh''
echo "Value
of mybin'' $mybin
echo "Value
of current'' $current
Program prog2.sh
contains
echo
"Prog2.sh:
echo "Value
of mybin'' $mybin
echo "Value
of current'' $current
current=/usr/user
mybin=/bin
echo "Value
of mybin'' $mybin
date >
/tmp/record$$
ps -a >>
/tmp/record$$
The file will have a name
like /tmp/record102373.
A special parameter $* is
used to stand for all the
command line parameters
except $0.
So echo $* is equivalent to
echo $1, $2, $3 ....... e.g.
echo $*
Then executing prog.sh:
$ prog.sh
arg1 arg2 word3 abc
outputs
Example:
$ prog word1
word2 word3
Code:
echo $1
shift
echo $1
shift
echo $1
Output:
word1
word2
word3
HOME
Home directory -- default
argument for cd command.
Set up in the password file
/etc/passwd.
PATH
Search path for
commands. A list of
directories that is searched
by shell for command
binary files. By default it is
the current directory, /bin
and /usr/bin. The directory
names are separated by a
:
e.g. PATH=.:/bin:/usr/bin:
$HOME/bin
PS1
Primary shell prompt, by
default '$' (for Bourne
shell).
PS2
Secondary shell prompt, by
default '> ' (for Bourne
shell).
It is displayed when a
command such as a loop is
being entered over a
number of lines.
$ echo
$PATH
$ echo
$HOME
General Format:
for var in
word1 word2 word3.......
do
commands
done
Example:
for i in
screen.c menu.c
compare.c
do
echo
"File : " $i
lpr $i
done
$ create file1
file2 # Create is a
shell program
# to create files
Code of create:
for File in $*
do
>$File
echo
$File created
done
for File
do
>$File
echo
$File created
done
Joe Bloggs
Dept. of Physics
6767
Fred Smith
5678
The tel program can
search for phone numbers
by name or any text in the
file.
Usage:
or
$ tel 6767
Output:
Code:
# tel program
for i in $*
do
grep –i
$i $HOME/phone.dat
done
It simply loops over the
command line arguments
and uses the 'grep'
program to search the file
phone.dat in the user's
home directory.
Try:
echo *
echo *.c
It is important to realise
that the shell generates
this list of file names before
running the echo
command. As far as echo
is concerned, the user may
well have typed the list of
files on the command line.
Usage:
$ shutdown
Code:
for i in 5 4 3 2
1
do
echo
"Going down in $i
minutes'' | /etc/wall
sleep 60
done
/etc/wall <<!
System
Going Down Now
Bye
!
kill -1 1
The program goes around
the for loop 5 times,
sleeping for 60 seconds
during each pass. 'sleep' is
a program that simply
causes your program to be
suspended for the number
of seconds specified i.e. it
does not waste CPU time.
General format:
while
command(s)
do
command(s)
done
The command after "while''
is executed and if it returns
true (exit status 0) then the
#asp
program-you could get
bitten for using it !
while true
do
echo
"Sleeping.......
sleep
240 # Sleep for 4
minutes
done
Usage:
$ watchfor
tom
tom has
logged on
Code:
# Watchfor
program
until who |
grep $1
do
sleep
120 # check every
2 mins
done # User has logged on
write $1 <<!
Hello $1, Can
you contact me
!
echo $1 "has
logged on''
The pipeline " who | grep
$1 '' causes the output of
the who command to
become the input for grep,
which searches it for the
specified user e.g. tom in
this example.
General form:
test
expression
test exp1 -o
exp2
test exp1 -a
exp2
It returns 0 if expression
evaluates to true, non-zero
otherwise. The "-o'' is used
for to combine two
expressions with logical
OR, "-a'' for logical AND.
Examples:
test -s File
True if File exists
and is non-empty
test -f File True if
File exists and is not a
directory
test ! -d
true if file is not a
directory.
while test -r
/tmp/lockfile
do
sleep 5
done
This code waits for a file
called '/tmp/lockfile' to
disappear. This could be
used to implement a crude
form of semaphore system,
and is used in the program
myprint below to provide a
mechanism for preventing
users from trying to access
the printer simultaneously :
Usage:
$ myprint
file.c file2.c
Code:
# myprint
while test -r
/tmp/lockfile # Wait
until printer free
do
sleep 5
done
>/tmp/lockfile # Lock printer
for i in $*
do
pr $i
> /dev/lp # pr file on
print device done
if conditional-
command
then
commands
fi
if conditional-
command
then
commands
else
commands
fi
if conditional-
command
then
commands
elif conditional-
command
then
commands
else
commands
fi
Example 1
if test $# = 0
then
# safecreate
program
for i in $*
do
if test -f $i
then
echo $i
"already exists''
else
>$i
done
This program loops over
the command line
parameters, testing if each
one exists, creating a file if
it does not already exist.
for i in $*
do
if test -f
$i
then
echo $i
"already exists''
echo -n
"Truncate it (Y|N)''
read ANS
if
test $ANS = Y -o $ANS
=y
then
>$i
elif
test $ANS = N -o $ANS
=n
then
continue #
Go around loop again
else
echo "Enter Y
or N''
Usage:
$ lsdir
directory-name
Example:
$ lsdir
/usr/user/year3
Code:
# lsdir
program
PATH=/bin:/usr/bin:/use
r/usr/joe/bin
if test $# = 0
then
lsdir .
# Use current directory
elif test ! -d
$1 # Check
if $1 is a directory
then
then
echo "Directory: '' $i
( cd $i ; lsdir . )
else
ls -l $i
\# ordinary file
fi
done
fi
If no arguments are
supplied to lsdir, it lists the
current directory by calling
itself recursively with
parameter ".''. The for
structure, takes the first
parameter, expands it to a
list of files in that directory
and loops over each file in
the list.
For example
lsdir
( cd $i ; lsdir $i )
monlist=/tmp/monlist
if test ! -r
$monlist
then
>$monlist
fi
while :
do
date >>
$monlist
echo " ''
>> $monlist
who >>
$monlist
echo " ''
>> $monlist
ps -a >>
$monlist
echo " ''
>> $monlist
sleep
300 # 5
minutes
done
It first checks to see if the
file '/tmp/monlist' exists,
creating it if it is not
present. This program uses
":'' after the "while'' which
always returns true, but is
built-in and so is more
efficient than using the
'true' program.
General Format:
case word in
pattern1)
command(s) ;;
pattern2)
command(s) ;;
esac
Note the double
semicolons terminating
each case.
case $# in
0) Echo
"No arguments
supplied'' ; exit ;;
2) Echo
"Correct '' ;;
*) Echo
"Incorrect number of
arguments'' ;;
esac
case $# in
1) cat
>> $1 ;;
2) cat
>> $2 < $1 ;;
*) echo
'Usage: append [from-
file] to-file' ;;
esac
Usage:
$ append
thisfile ontothatfile
case $1 in
-o) echo
"-o option entered'' ;;
-c) echo
"-c option entered'' ;;
esac
A range of characters may
be enclosed in []
Y|y|yes|Yes)
echo "Y or y or yes or Yes
entered'' ;;
# safecreate3
program using case
for i in $*
do
if test -f
$i
then
echo $i
"already exists''
echo -n
"Truncate it (Y|N) ''
read ANS
case $ANS in
y|Y) >$i ;;
n|N) continue
;; \# Go around
loop again
safecreate3
$i ;;
esac
else
>$i
fi
done
Usage:
$ tidy f.c f2.c
t.c
or
$ tidy *
Exit
x
Next file
n
Display file
t
Delete file
d
List file
l
Print file
p
!
# Process command line
parameters
for i in $*
do
echo "File : ''
$i # Display file
name
FIN=n
# Process each
file
while test
$FIN = n
do
echo -n "Enter
command: ''
case $COM in
n|N) break ;;
x|X) exit ;;
t|T) cat $i ;;
p|P) lp $i ;;
d|D) rm -i $i ;;
l|L) ls -l $i ;;
*) echo
"Unknown command
$COM'';;
esac
echo
echo -n
"Finished this file (y/n) ''
read FIN #
Only n is checked
done
# Next
command for this file
done
# Next file
m) cat menu-
file ;;
Command
Substitution
This is a very powerful
mechanism allowing the
output of a command to be
used inline e.g.
Command substitution
allows you build some very
useful programs. Suppose
you wish to mail a group of
users a particular message
then if you create a file of
user names called name-
list:
mail 'cat
name-list' < message
The shell executes the
command "cat name-list''
and the output is produced
inline just as if you entered:
mail 'cat
name-list' <<!
This is the
text
of the
message
.......
!
If you want to loop over the
files in a directory, in order
Arithmetic
The Bourne shell provides
no built-in commands for
arithmetic operations.
However, a command
called 'expr' is available
which evaluates arithmetic
expressions on its
command line and outputs
the result to the standard
output.
$ expr 2 + 2
4
By executing the command
inline, we can perform
arithmetic on shell
variables. This can be used
to loop over command a
specific number of times.
For example, we could
rewrite the shutdown
command presented
earlier, to use $1 as the
number of minutes until
shutdown time. If no
command line parameters
are specified then 5 is
chosen as the default
Usage:
$ shutdown2
10 # Shut
system down in 10 mins
if test $# = 0
then
count=5 #
Default is 5 minutes
else
count=$1
fi
i=1
tleft=$count
while test $i -le
$count
do
echo "System
going down in $tleft
minutes'' | /etc/wall
i='expr $i + 1'
# increment i
tleft='expr $tleft
- 1'
sleep 60
done
echo "System
going down now !!!!!''
| /etc/wall
kill -1 1
Output:
System going
down in 10 minutes
System going
down in 9 minutes
....
1) The \
character
quotes the the
next character
e.g.
echo The
star
character: \*
yields
The star
character: *
The
backslash
character: \
2) Single
quotes ' ' are
used to quote a
group of
characters:
echo
'***** Warning
**** ???? '
displays
*****
Warning
**** ????
3) Double
quotes are also
used but only
quote file
generation
characters *
and ? e.g.
echo
''$HOME has
no filenames
with a * in
them''
displays
“Here Documents”
ed file <<!
1,\$s/\^Mr/Mister/
g/Jones/d
g/\^$/d
w
q
!
The g command instructs
ed to perform the
command on all lines,
the /Jones/ locates line
with the string "Jones''. See
the ed manual for details.
Note that the caret has to
be quoted since otherwise
it will be interpreted as a
shell metacharacter
(alternative symbol for
pipe) instead of being
passed to ed. This is a
good example of where
knowledge of a Unix utility,
ed in this case, can save a
lot of work. It is almost
always quicker to learn
how to use existing utilities
to do a job, than to write
even a simple C program
from scratch.
uname:pwd:uid:gid:misc:ho
me:shell
pwd: encrypted
password, empty if not set
e.g.
tom:xHyzio89-
ws:68:10:Tom Thumb
X2134:/usr/user/tom:/bin/s
h
A directory must be
created for the user (his
home directory), and the
user made the owner of
this directory. The
protection on this directory
may be set appropriately,
and the user added to the
appropriate group. A
if test $# ! = 2
then
echo "Usage:
mkuser user-name
user-id''
exit
fi
# Add entry to
password file
ed /etc/passwd <<!
.a
$1::$2::10::/usr/us
er/$1:/bin/sh
.
w
q
!
mkdir /usr/user/$1
chown $1
/usr/user/$1
chgrp user
/user/usr/$1
chmod go-w
/usr/user/$1
This is a useful command
especially since the
password file entry is
Signal Handling
In Unix, programs are sent
signals by the kernel in the
event of a program error,
(such as divide by zero, or
memory addressing error)
or when the user hits the
interrupt key (CTRL C), the
quit key (CTRL \), logs out
(Hangs up) or the user
generates a signal with the
'kill' command.
1 Hangup
logout
2 Interrupt
Ctrl/C
3 Quit Ctrl \
10 Bus error
Memory
addressing error
15 Software
termination kill -15 pid
rm /etc/lockfile ; exit
thus freeing the printer and
terminating lpr. This line
should be inserted at the
start of the lpr program.
trap '' 1 2 3
trap 1 2 3
As an exercise you could
rewrite the 'asp' program,
to read a user code-word,
go into the sleep loop as
before, and when
interrupted, prompt for the
code-word terminating if it
matches that previously
entered, otherwise
returning to the sleep loop.
Efficiency of Shell
Programs
The efficiency of shell
programs can be improved
in a number of ways. By
setting the value of PATH
so that the shell only
searches the necessary
for i in $*
do
cat $i
done
cat $*
When you use a shell loop,
then the shell must locate
and load the program each
time around the loop.
When you write 'cat $*' the
'cat' program is only loaded
once, and it performs the
argument processing (after
the shell has expanded $*).
The use of a "here
document'' instead of a
series of 'echo' commands
is also worthwhile.
exec newprog
Debugging Shell
Programs
Finally, when debugging
shell programs, you can
invoke them "verbosely''
with
sh -v prog
set -v
set -n
set -x