Bash Best(ish) practices part 2


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…

#!/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

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.

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
}

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.

[:?]) # Print usage information
    print_usage;;

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.

[ "${HELP}" ] && print_usage

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

# 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"
}

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.

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

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.

# ./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

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

Advertisements

One thought on “Bash Best(ish) practices part 2

  1. Pingback: Bash Best(ish) practices part 1 | Food for Thought

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s