Renat Gilmanov Renat Gilmanov - 3 months ago 21
Bash Question

Shell script common template

Millions of developers write shell scripts to solve various types of tasks. I use shell scripts to simplify deployment, life-cycle management, installation or simply as a glue language.

What I've noticed is nobody actually cares about shell scripts style and quality. A lot of teams spend many hours fixing Java, C++, ... style issues, but totally ignore issues in their shell scripts. By the way, usually there is no standard way to implement a shell script within a particular project, so the one may find dozens different, ugly and buggy scripts, spread around the codebase.

To overcome that issue in my projects I decided to create a shell script template, universal and good enough. I will provide my templates as is to make this question a bit more useful. Out of the box these templates provides:


  • command-line arguments handling

  • synchronization

  • some basic help



Arguments handling: getopts (latest version: shell-script-template@github)

#!/bin/bash
# ------------------------------------------------------------------
# [Author] Title
# Description
# ------------------------------------------------------------------

VERSION=0.1.0
SUBJECT=some-unique-id
USAGE="Usage: command -ihv args"

# --- Options processing -------------------------------------------
if [ $# == 0 ] ; then
echo $USAGE
exit 1;
fi

while getopts ":i:vh" optname
do
case "$optname" in
"v")
echo "Version $VERSION"
exit 0;
;;
"i")
echo "-i argument: $OPTARG"
;;
"h")
echo $USAGE
exit 0;
;;
"?")
echo "Unknown option $OPTARG"
exit 0;
;;
":")
echo "No argument value for option $OPTARG"
exit 0;
;;
*)
echo "Unknown error while processing options"
exit 0;
;;
esac
done

shift $(($OPTIND - 1))

param1=$1
param2=$2

# --- Locks -------------------------------------------------------
LOCK_FILE=/tmp/$SUBJECT.lock
if [ -f "$LOCK_FILE" ]; then
echo "Script is already running"
exit
fi

trap "rm -f $LOCK_FILE" EXIT
touch $LOCK_FILE

# --- Body --------------------------------------------------------
# SCRIPT LOGIC GOES HERE
echo $param1
echo $param2
# -----------------------------------------------------------------


Shell Flags (shFlags) allows to simplify command-line arguments handling a lot, so at some moment of time I decided not to ignore such possibility.

Arguments handling: shflags (latest version: shell-script-template@github)

#!/bin/bash
# ------------------------------------------------------------------
# [Author] Title
# Description
#
# This script uses shFlags -- Advanced command-line flag
# library for Unix shell scripts.
# http://code.google.com/p/shflags/
#
# Dependency:
# http://shflags.googlecode.com/svn/trunk/source/1.0/src/shflags
# ------------------------------------------------------------------
VERSION=0.1.0
SUBJECT=some-unique-id
USAGE="Usage: command -hv args"

# --- Option processing --------------------------------------------
if [ $# == 0 ] ; then
echo $USAGE
exit 1;
fi

. ./shflags

DEFINE_string 'aparam' 'adefault' 'First parameter'
DEFINE_string 'bparam' 'bdefault' 'Second parameter'

# parse command line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

shift $(($OPTIND - 1))

param1=$1
param2=$2

# --- Locks -------------------------------------------------------
LOCK_FILE=/tmp/${SUBJECT}.lock

if [ -f "$LOCK_FILE" ]; then
echo "Script is already running"
exit
fi

trap "rm -f $LOCK_FILE" EXIT
touch $LOCK_FILE

# -- Body ---------------------------------------------------------
# SCRIPT LOGIC GOES HERE
echo "Param A: $FLAGS_aparam"
echo "Param B: $FLAGS_bparam"
echo $param1
echo $param2
# -----------------------------------------------------------------


I do think these templates can be improved to simplify developer's life even more.

So the question is how to improve them to have the following:


  • built-in logging

  • better error handling

  • better portability

  • smaller footprint

  • built-in execution time tracking


Answer

I would steer clear of relying on bash as the shell and model your solution on top of shell syntax defined by POSIX and use /bin/sh on the shebang. We had a number of surprises recently when Ubuntu changed /bin/sh to dash.

Another pandemic in the shell world is a general misunderstanding of exit status codes. Exiting with an understandable code is what lets other shell scripts programmatically react to specific failures. Unfortunately, there is not a lot of guidance on this beyond the "sysexits.h" header file.

If you are looking for more information about good shell scripting practices, concentrate on Korn shell scripting resources. Ksh programming tends to focus on really programming as opposed to writing haphazard scripts.

Personally, I haven't found much use for shell templates. The unfortunate truth is that most engineers will simply copy and paste your template and continue to write the same sloppy shell code. A better approach is to create a library of shell functions with well-defined semantics and then convince others to use them. This approach will also help with change control. For example, if you find a defect in a template, then every script that was based on it is broken and would require modifications. Using a library makes it possible to fix defects in one place.

Welcome to the world of shell scripting. Writing shell scripts is a bit of a lost art that seems to be entering a renaissance. There were some good books written on the subject in the late 90's - UNIX Shell Programming by Burns and Arthur comes to mind though the Amazon reviews for the book make it seem awful. IMHO, effective shell code embraces the UNIX philosophy as described by Eric S. Raymond in The Art of Unix Programming.