Bash Best(ish) practices part 2

March 17, 2013
bash Food for Thought on scriptogr.am IFTTT

Part 2 of our series has us beginning to use functions as a part of script development. Bash functions are pretty simple, and much has already been written about them. Today we will primarly be concerned with how they can help your script flow better and potentially speed up development and troubleshooting.

Lets start with some code…

<code>#!/bin/bash
# Copy some files

# Print usage information and exit
print_usage(){
    echo -e "\n" \
    "usage: ./copy_stuff -s sourcedir -d destdir \n" \
    "\n" \
    "-s <sourcedir> source directory\n" \
    "-d <destdir>   destination directory\n" \
    "-h             this help\n" \
    "\n" && exit 1
}

# Loop through $@ to find flags
while getopts ":hs:d:" FLAG; do
    case "${FLAG}" in
        s) # Our source
            SOURCE="${OPTARG}" ;;
        d) # Our destination
            DEST="${OPTARG}" ;;
        h) # Print usage information
            HELP=1;;
        [:?]) # Print usage information
            print_usage;;
    esac
done

# Copy some files from one place to another
copy_files(){
    echo "Copying ${SOURCE}/foo to ${DEST}/foo"
    echo "Copying ${SOURCE}/bar to ${DEST}/bar"
    echo "Copying ${SOURCE}/baz/foo to ${DEST}/baz/foo"
    echo "Copying ${SOURCE}/baz/bar/foo to ${DEST}/baz/bar/foo"
    echo "Copying ${SOURCE}/dir/somefile to ${DEST}/dir/someotherfile"
}

# Restart some service
restart_service(){
    echo "Stopping the service"
    echo "Making sure the service is stopped"
    echo "Reticulating splines..."
    echo "Starting the service"
}

# Test to see if the service is running
test_service(){
    echo "service is tested"
}

[ "${HELP}" ] && print_usage

if [ ${SOURCE} ] && [ ${DEST} ]
then
    echo "Copying files from ${SOURCE} to ${DEST}"
    copy_files
    echo "Restarting the service"
    restart_service
    echo "Testing the service"
    test_service
fi
</code>

There is a lot happening in this script, but you can see that we are expanding upon ideas from last week.

Lets break down the key elements.

<code>print_usage(){
    echo -e "\n" \
    "usage: ./copy_stuff -s sourcedir -d destdir \n" \
    "\n" \
    "-s <sourcedir> source directory\n" \
    "-d <destdir>   destination directory\n" \
    "-h             this help\n" \
    "\n" && exit 1
}
</code>

We define a function called print_usage which, you guessed it, prints the usage information for our script. I cannot understate how usefull this is when you are expecting your script to be run by other people. Please don’t write scripts that by default just do something or use the -h flag to perform some operation other than printing usage.

The format used here allows for more readable help.

<code>[:?]) # Print usage information
    print_usage;;
</code>

The second thing you should notice is that we have added a new option check. : and ? are triggered if you fail to provide an argument to a flag that requires one or you provide a flag that does not exist. In this case we want to let the user know that they do not know how to invoke our program and they should read the “friendly” manual.

<code>[ "${HELP}" ] && print_usage
</code>

This line was added to preempt other operations from taking place if you have specified the -h flag.

<code># Copy some files from one place to another
copy_files(){
    echo "Copying ${SOURCE}/foo to ${DEST}/foo"
    echo "Copying ${SOURCE}/bar to ${DEST}/bar"
    echo "Copying ${SOURCE}/baz/foo to ${DEST}/baz/foo"
    echo "Copying ${SOURCE}/baz/bar/foo to ${DEST}/baz/bar/foo"
    echo "Copying ${SOURCE}/dir/somefile to ${DEST}/dir/someotherfile"
}

# Restart some service
restart_service(){
    echo "Stopping the service"
    echo "Making sure the service is stopped"
    echo "Reticulating splines..."
    echo "Starting the service"
}

# Test to see if the service is running
test_service(){
    echo "service is tested"
}
</code>

The next thing you should see is that we put all of the work that the script is doing within function declarations. As you will see below, this allows us to implement and test each component separately.

<code>if [ ${SOURCE} ] && [ ${DEST} ]
then
    echo "Copying files from ${SOURCE} to ${DEST}"
    copy_files
    echo "Restarting the service"
    restart_service
    echo "Testing the service"
    test_service
fi
</code>

It should also become apparent that you can more easily wrap function calls in argument checks which can help your script be more readable. If we want to perform work on the test service function, we can simply comment out the other three calls.

Let’s see this script in action.

<code># ./copy_stuff -h

 usage: ./copy_stuff -s sourcedir -d destdir

 -s <sourcedir> source directory
 -d <destdir>   destination directory
 -h             this help 

# ./copy_stuff -h -s /dir1

 usage: ./copy_stuff -s sourcedir -d destdir

 -s <sourcedir> source directory
 -d <destdir>   destination directory
 -h             this help

# ./copy_stuff -h -s /dir1 -d /dir2

 usage: ./copy_stuff -s sourcedir -d destdir

 -s <sourcedir> source directory
 -d <destdir>   destination directory
 -h             this help

# ./copy_stuff -s /dir1 -d /dir2
Copying files from /dir1 to /dir2
Copying /dir1/foo to /dir2/foo
Copying /dir1/bar to /dir2/bar
Copying /dir1/baz/foo to /dir2/baz/foo
Copying /dir1/baz/bar/foo to /dir2/baz/bar/foo
Copying /dir1/dir/somefile to /dir2/dir/someotherfile
Restarting the service
Stopping the service
Making sure the service is stopped
Reticulating splines...
Starting the service
Testing the service
service is tested
</code>

For more information regarding functions see http://www.cyberciti.biz/faq/bash-shell-script-function-examples/

Bash Best(ish) practices part 4 Bash Best(ish) practices part 3 Bash Best(ish) practices part 1

Bash Nagios plugin

April 29, 2013
bash Food for Thought on scriptogr.am IFTTT

Bash Parallel Execution

April 14, 2013
bash Food for Thought on scriptogr.am IFTTT

Turboprop

April 7, 2013
bash Food for Thought on scriptogr.am IFTTT