layout | title | <!--license |
---|---|---|
default |
Shell functions |
- TOC {: toc}
This lab is concerned with improving the quality of your shell scripts by the use of functions:
- using functions in your shell scripts to make them easier to read, understand and maintain;
Just as with any programming task, when writing a shell script it is often clearer to break your script into a number of functions that are called from the main body of the script. This modular decomposition makes your script easier to understand and maintain. It is also easier to reuse your code.
The syntax of a shell function looks like this
hello() {
echo "Hello world"
}
{: .code}
It starts with the function name, hello
, that you can choose yourself; then come the parentheses ()
and an
opening curly brace {
; then you write the commands that you'd like the function to execute when it's called;
and finally there is a closing curly brace }
.
Here's an example of how the function can be called from the main body of the script:
hello() {
echo "Hello world"
}
echo "This is the hello script"
hello
{:.code}
Type the code above into an editor and save it in a file called hellofunc
. Make the file executable,
chmod +x hellofunc
, and run it, ./hellofunc
. You should see the following output:
This is the hello script
Hello world
{: .code}
You can pass arguments to a shell function, just like you can pass arguments to the main shell
script on the command line. And you can refer to the arguments that have been passed using the
same positional notation, $1, $2, $3, ...
etc. For example,
hello() {
echo "Hello world, $1"
echo $2
}
echo "This is the hello script"
hello David "Have a nice day"
{: .code} shows a function that expects two arguments, a name followed by a greeting. It will generate the output:
This is the hello script
Hello world, David
Have a nice day
{: .code}
Functions can have local variables. They are preceded by the key word local
. For example,
#! /bin/bash
myFunc() {
local x=10
echo "The value of x in myFunc is $x"
}
x=2
echo "The value of x in main is $x"
myFunc
echo "After myFunc the value of x in main is now $x"
{: .code}
Use an editor to create this script. Save it in a file called funclocals
.
Make the script executable, chmod +x funclocals
. Run the script, ./funclocals
.
- What is the value of
x
before the execution ofmyFunc()
? - What is the value of
x
insidemyFunc()
? - What is the value of
x
after the execution ofmyFunc()
? - Now edit the script to remove the word
local
frommyFunc()
. Run the script again. Answer questions 1-3 again. What does this tell you about local variables?
Put set -x
immediately after the shebang line in one of your scripts and run it again.
You'll see that the script now gives you information about the progress of its execution.
This can be very helpful when you are trying to work out why a script is not behaving as you
would like it to. Try the example below.
#! /bin/bash
set -x
hello() {
echo "Hello world, $1"
echo $2
}
echo "This is the hello script"
hello David "Have a nice day"
{: .code} You should see the following output.
+ echo 'This is the hello script'
This is the hello script
+ hello David 'Have a nice day'
+ echo 'Hello world, David'
Hello world, David
+ echo Have a nice day
Have a nice day
{: .code}
Notice the lines that start with the +
symbol. Those lines have been added by
the debugger. These lines allow you to see exactly what is being executed by
the shell. For example, you can see exactly how the hello()
function has been
called and what values have been assigned to parameters $1
and $2
. This can
be very helpful. Use this technique when you have a script that is not behaving
as you want.
The script below is taken from the lecture in week 3. There are several opportunities to improve the script by making use of functions.
#!/bin/bash
if [ $# -ne 3 ]; then
echo "Your command: $0 $*"
echo "Usage: tidy4 <tidy dir> <src dir> <obj dir>"
exit 1
else
tidyDir="$1"
srcDir="$2"
objDir="$3"
fi
if [ -d "$tidyDir" ]; then
echo "Tidying directory $tidyDir ..."
else
echo "$tidyDir does not exist"
exit 1
fi
if [ ! -d "$srcDir" ]; then
if [ -f "$srcDir" ]; then
echo "$srcDir exists and is not a directory"
exit 1
else
mkdir "$srcDir"
fi
fi
CFILES=$tidyDir/*.c*
if stat -t $CFILES >/dev/null 2>&1; then
mv $CFILES $srcDir;
echo "Moved C and C++ files to $srcDir"
fi
if [ ! -d "$objDir" ]; then
if [ -f "$objDir" ]; then
echo "$objDir exists and is not a directory"
exit 1
else
mkdir "$objDir"
fi
fi
OBJFILES=$tidyDir/*.o
if stat -t $OBJFILES >/dev/null 2>&1; then
mv $OBJFILES $objDir
echo "Moved object files to $objDir"
fi
echo "... done"
{: .code} Modify this script to use the following functions:
-
A
usage()
function that checks the number of command line arguments and prints a helpful message before exiting with an error code if the number is not what is expected. The actual and expected number of arguments should be parameters to the function. -
A
dirExists()
function that checks if a name exists as a directory, and prints a helpful message before exiting with an error code, if not. The directory name should be a parameter of the function. You could pass an informative message as a second argument, if you wish. -
An
ensureDir()
function that checks if a name exists as a directory and returns successfully if so. If the name does not exist as a directory, the function should create a directory with the given name, if possible, or exit with an error code if not. -
A
moveFiles()
function that moves all the files with a given filename extension (e.g..c
) from one directory to another, if possible. The function should do nothing if there are no files in the given directory with the given extension. The function should have the following parameters: the name of the directory containing the files; the filename extension; the name of the target directory; and an informative message.
Rewrite the main body of the script to use these functions.