Note: all examples in this lab use the
#!/bin/bash
she-bang as some of the operations are much more difficult to do in
the default shell
sh
.
Shell scripts normally treat variables as strings. However, strings
that look like numbers can be easily used in arithmetic expressions.
This is called arithmetic expansion. There are a number of
methods of performing arithmetic expression, we shall look only at
using double parantheses:
var=((expression))
, as in the following script
expand
:
#!/bin/bash x=5 echo "x=5" y=6 echo "y=6" echo "Concatenation" a=$x+$y echo "\$x+\$y: $a" echo "Addition" b=$((x+y)) echo "\$((x+y)): $b" echo "Comparison (<)" c=$((x<y)) echo "\$((x<y)): $c" echo "Comparison (>)" d=$((x>y)) echo "\$((x>y)): $d" exit 0
which results in:
x=5 y=6 Concatenation $x+$y: 5+6 Addition $((x+y)): 11 Comparison (<) $((x<y)): 1 Comparison (>) $((x>y)): 0
Arithmetic expansion in scripts supports the typical operators:
+
,
-
,
/
,
%
,
++
,
+=
, etc.
The
if
structure tests whether
condition
is 0 (the unix definition of success), and executes the commands
within the appropriate code block. There are a wide number of tests
available: comparisons, file tests, the results of arithmetic
expressions, the exit status of commands, etc. The
if
may use one of the
[...]
or
((...))
conditions.
The
if
structure is:
if [ condition ] | (( condition ))
then
...
elif
then
...
else
...
fi
You may use multiple
elif
s and one
else
options. Both are optional.
if
structures may be nested.
The operators used differ depending on whether the conditions are
tested inside square brackets
[ ... ]
or as arithmetic expansion within
(( ... ))
.
[ ... ] |
(( ... )) |
Purpose |
|---|---|---|
-eq |
== |
equals |
-ne |
!= |
not equals |
-gt |
> |
greater than |
-ge |
>= |
greater than equal to |
-lt |
< |
less than |
-le |
<= |
less than equal to |
-a |
&& |
and |
-o |
|| |
or |
Note:
&&
and
||
do a short-circuit comparison,
-a
and
-o
do not.
Strings must be compared within square brackets
[]
.
The string comparison operators are
==
,
!=
,
<
, and
>
, for their usual meanings. Strings are compared by their ASCII
values, so case is significant.
#!/bin/bash
echo -n "Enter your name: "
if [ $name == "David" ]
then
echo "David!"
else
echo "what?"
fi
exit 0
There are two further string comparison operations:
-z : string is null-n : string is not null (same as ! -z)The following script checks for the existence of a 2nd argument to the script:
#!/bin/bash
if [ -n "$2" ]
then
echo "The 2nd argument is $2"
else
echo "There is no 2nd argument"
fi
exit 0
Of course, a far better way to do this is to check whether the number of parameters is correct.
The system provides a number of file tests - these are the most important ones:
| Operator | Purpose |
|---|---|
-e |
file exist (not in sh!)
|
-f |
file is a regular file - not a directory or device |
-s |
file size is greater than zero |
-d |
file is a directory |
-r |
file has read permission |
-w |
file has write permission |
-x |
file has execute permission |
-N |
file modified since it was last read |
! |
not |
Example: the script
ftest
takes a single file name as an argument and validates the file name
with various file tests:
#!/bin/bash #if (( $# != 1 )) if [ "$#" -ne "1" ] then echo "Must have one filename as an argument" exit 1 elif [ ! -e "$1" ] then echo "file $1 does not exist" exit 2 elif [ ! -f "$1" ] then echo "file $1 is not a regular file" exit 3 elif [ ! -s "$1" ] then echo "file has a size of 0" exit 4 elif [ ! -r "$1" ] then echo "file lacks read permission" exit 5 fi echo "File $1 is OK" exit 0
The
case
structure allows you to evaluate a string against a number of
options. Options are separated by double semi-colons
;;
. The
case
structure is:
case "string" in
"comparator_1")
...
;;
"comparator_n")
...
;;
esac
The
case
structure is excellent for menus and validiting inputs and options.
The script
say
looks at its first argument and decides how to respond based upon
that argument. Subsequent arguments are ignored.
#!/bin/bash
case "$1" in
"") # No argument given.
echo "So say something!"
;;
"David"|"Professor"|"Sir") # Multiple options.
echo "Yes, can I help you?"
;;
"Dave") # I hate Dave.
echo "You fail the course!"
;;
*) # This is a pattern.
echo "Who are you talking to?"
;;
esac
exit 0
The
while
structure works much like a typical
while
you've seen in many languages. It may use either the
[ ... ]
or
(( ... ))
conditions. You may use multiple conditions within a
while
, but you must put all conditions within a single set of the
brackets. The while structure is:
while [...] | ((...))
do
...
done
Example: The script
key
asks the user to enter a single character, evaluates it, then keeps
asking until the user enters a '
*
'.
#!/bin/bash
# Testing ranges of characters.
echo "Hit a key, then hit return (* to exit)"
read KEY
while [ "$KEY" != "*" ]
do
case "$KEY" in
[a-z])
echo "Lowercase letter";;
[A-Z])
echo "Uppercase letter";;
[0-9])
echo "Digit";;
*)
echo "Punctuation, whitespace, or other";;
esac
echo "Hit a key, then hit return (* to exit)"
read KEY
done
exit 0
Example: The script
sum
asks the user to input a number from the keyboard and sum the values
together.
#!/bin/bash SUM=0 echo "Enter a number (0 to stop): " read N while (( N > 0 )) do SUM=$((SUM+N)) echo "Enter a number (0 to stop): " read N done echo "Sum: $SUM" exit 0
Note that this is an
until
with the same structure as a
while
, but its condition is a stop condition, rather than an continue
condition.
The
for
loop is used to loop through a collection of elements as opposed to
being a count-controlled loop, which is typically how a
for
is used in many languages. (
while
loops can easily be used to loop a certain number of times.) The for
structure is:
for element in collection
do
...
done
The
seq n
(sequence) command generates a list of numbers from 1 to
n
. This can be very useful in a
for
loop, as in the script
seqtest
:
#!/bin/bash
if (($#==1))
then
prod=1
n=`seq "$1"`
for i in $n
do
prod=$((prod*i))
echo "$i : $prod"
done
echo "Product: $prod"
else
echo "Must have 1 argument."
fi
exit 0
When executed with an integer argument, this produces:
/home/dbrown/CP367> ./seqtest 5 1 : 1 2 : 2 3 : 6 4 : 24 5 : 120 Product: 120
The script
args
gives information about the arguments to a script and then displays
the value of each argument using a
for
loop.
#!/bin/bash # Get the name of the script. echo "The name of the script is in \$0" echo "Name of script: $0" echo # Get the number of arguments to the script. echo "The number of arguments passed is in \$#" echo "Number of arguments: $#" echo echo "The arguments are stored in \$@" echo "$@" echo # Display the actual arguments from within a loop. echo "Looping through the variables:" for arg in $@ do echo $arg done exit 0
Here is an example of executing
args
:
/home/dbrown/CP367> ./args a b 1 2 Hello The name of the script is in $0 Name of script: ./args The number of arguments passed is in $# Number of arguments: 5 The arguments are stored in $@ a b 1 2 Hello Looping through the variables: a b 1 2 Hello
This final example shows how the commands used in earlier labs can
be reused within a script. The
wrap
script calls
sed
(which in turn calls a sed script file) to wrap HTML code around the
contents of the files whose names were given as arguments to the
script.
#!/bin/bash
for arg in "$@"
do
echo "file $arg"
if [ ! -e "$arg" ]
then
echo "$arg does not exist"
else
newfile=${arg}.htm
`sed -f wraphtml.sed $arg > $newfile`
echo "$arg was wrapped as $newfile"
fi
done
exit 0
Set up a small script calculator to ask the user for two numbers and add them together.
Create a second version of your calculator to ask the user what operation to perform in +, -, *, /, and %.
Create a third version of your calculator to loop until the user wants to stop.
Create a fourth version of your calculator to store the inputs, operations, and outputs to a file (like a cash register tape).