Skip to content

Latest commit

 

History

History
224 lines (185 loc) · 6.33 KB

L04.md

File metadata and controls

224 lines (185 loc) · 6.33 KB
layout title <!--license
default
Shell functions
  • TOC {: toc}

Introduction

This lab is concerned with improving the quality of your shell scripts by the use of functions:

  1. using functions in your shell scripts to make them easier to read, understand and maintain;

Shell functions

Simple shell functions

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.

  1. What is the value of x before the execution of myFunc()?
  2. What is the value of x inside myFunc()?
  3. What is the value of x after the execution of myFunc()?
  4. Now edit the script to remove the word local from myFunc(). Run the script again. Answer questions 1-3 again. What does this tell you about local variables?

Debugging

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.

Using shell functions in practice

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.