Shell Scripting Template

Shell Script Examples

I used to write many scripts as part of the IBM Global Deployment Team.  My scripts broke into two categories.  First, I wrote scripts that allowed me to repeat the installation of various pieces of software or harden the OS.  Second, I wrote scripts that gathered data to verify various configuration files were setup correctly.  I had to write scripts that worked on AIX, Red Hat Linux, Solaris and HP-UX.  I even have a nice Windows audit script that was started by Huw Leighton-Barret, an old team mate of mine.  As a result I tend to avoid commands that only work in one OS or don't work properly in different shells.  Later I became an IT Architect and didn't write as many scripts.  I still had some chances when I got to help the AIX team or the Linux team on certain projects.  But the syntax of certain items would escape me.  As a result I started to gather together a little template of various parts of a good shell script that I could use and edit as a template.  This is the start of that template.

I intend to add more to this page as I review my old scripts.  I will be adding additional loop examples and array examples.  Plus I need to add the shell operators as I forget certain flags that I rarely need.  Plus I need to add little tricks I have learned over the years.  Now that I don't edit these scripts on a regular basis I can't quickly find the example I need to write a new script.  Hence this page to quickly find a lot of the basics.  This page is not intended to explain everything for newbies.  If anyone finds it useful that is great.  But it is mainly intended for myself.

Useful Websites

There are tons of useful websites to help explain any aspect of shell scripting.  But I wanted to link to few sites I like to use for quick references.

The Linux Documentation Project

Tutorials Point

Rosetta Stone  (Useful to find the equivalent command for Linux from an AIX command)

Here is my post to help explain OPTIND and GETOPTS.

About My Scripts

I prefer VI over any other editor.  I was taught to use this many years ago.  The main reason is that it will most likely exist on any UNIX box in the world.  Whereas editors like NANO or EMACS may not exist on all boxes.  I am fairly certain the AIX boxes I worked with the most did not have either NANO or EMACS with the base OS.  But don't quote me on that.  I started with AIX 4.2 in 1999.  AIX 4.3 was available at the time, but not all of the applications were certified on AIX 4.3 at that time.  I also learned to use 4 spaces and not to use tabs.  I still do that and prefer spaces over tabs.  Although on one huge nested loop I created I dropped that down to one space.  I am sure there was a better way to do what I needed to do.  Some of the examples below are from some very old scripts.  And I would definitely change how I write things today.

The Template

The template below will be updated from time to time.  I also attached a copy (SHELL_TEMPLATE.sh.gz).  As the template grows I will be adding headings and brief explanations.  This will make it easier for me to find things and remember why I wrote it a certain way.  Or to help me find the original script.

#!/bin/sh
#
# Enter Brief description of script
#
# @(#)SHELL_TEMPLATE.sh v 1.1 14 JUN 2016 Kurt Erst kurterst@gmail.com
#

#
# Changelog
# v1.0 kerst1 06/01/16 - Original version
# v1.1 kerst1 06/14/16 - Adding Logging and I/O Redirection
# and OPERATORS Section
#

#
# Versioning Info called by -v flag
#
VERSION="1.1"
LASTUPDATE="JUNE 14 2016"
OWNER="Kurt Erst kerst1@us.ibm.com"

#
# Exit Codes - Optional but useful
#
# 1 = Not root
# 3 = Wrong Usage
# 5 = -v Version Number
# 10 = xxx
# 15 = xxx

#
# Set PATH - Edit as required
#
PATH=/usr/bin:/usr/sbin:/bin:/sbin:/etc:/usr/local/bin
export PATH

#
#Optional Debug - uncomment to enable debugging
#
#set -x

#
# Set Default Flags that may be chaged by flags in command line
#
BACREC=NOTSM

#
# Verify running as ROOT
#
if [ `whoami` != root ] ; then
    echo "--- ERROR: You must be root to run $basename $0"
    exit 1
fi

#
# VERSION Function w/exit code
#
version_num () {
    /usr/bin/echo "--- Running Script = `basename $0` ---"
    /usr/bin/echo "--- Version = $VERSION ---"
    /usr/bin/echo "--- Last Updated on $LASTUPDATE ---"
    /usr/bin/echo "--- Written by $OWNER ---"
    exit 5
}

#
# Usage Statement Example - Function
#
#
# [-xyz] Options without operands - may be case sensitive if desired
# [-c US|UK] Options with exclusive operands US or UK
# args Argument(s) If required use req1 or req_arg - may be more than one
#
usage () {
  echo "usage: $(basename $0) [-xyz] [-c US|UK] [ -b YES|NO ] [ -e user@me.com ] args"
  echo " -x = turns on feature X (optional)"
  echo " -y = turns on feature Y (optional)"
  echo " -z = turns on feature Z (optional)"
  echo " -c US|UK = Enter Country Code either US or UK (OPTARG)"
  echo " -b YES|NO = Enter YES or NO to check backup and recovery (OPTARG)"
  echo " -e email address = Enter valid email address (OPTARG)"
  echo " -h = Displays USAGE Statement aka help"
  echo " -v = Displays VERSION Information"
  echo " args = Must enter valid argument"
  echo " PRE = Pre-build checks"
  echo " VER = Verify Settings"
  echo " AUD = Perform Audit Checks"
  echo " Example"
  echo " `basename $0` -x -c uk -b yes -e kerst1@us.ibm.com aud"
  exit 3
}

#
# GETOPTS - Adjust based on required flags for script
#
# x: flag x has required variable OPTARG (COLON = Required OPTARG)
# v flag v no variable argument - may set variable
#
# Note - flag options are nonsense and used as examples only
# Edit as requried
#
while getopts xyzc:b:e:hv OPTIONS
do
    case "$OPTIONS"
    in
        x) XRAY=YES ;;
        y) CHECKSSH=TRUE ;;
        z) CHECKID=YES ;;
        c) LOCA="$OPTARG" ;;
        # Note BACREC set to NOTSM by default
        b) BACREC="$OPTARG" ;;
        e) EMAIL=YES ; EMAILADDR="$OPTARG" ;;
        v) version_num ;;
        h) usage ;;
        *) usage ;;
    esac
done

#
# Verify Options
#
# argc - argument count / argv - argument array
# OPTIND - Index of next element to be processed by argv. Intialized to 1
# $# is number of parameter passed on command line to script
# $* & $@ - all command line arguments - double quoted output on one line
# No quotes output is on separate lines
# $* - entire list one arg with spaces
# $@ - entire list separated into separate args
# $? - Exit status / typical 0 = success 1 = failure / other values may exist
#
if [ "$OPTIND" -gt "$#" ]
then
    usage
    exit 3
fi

#
# Shift removes N strings from posistional parameters list. Removes all options
# parsed by getopts so that $1 will refer to FIRST NON-OPTION argument passed to script
#
shift `expr $OPTIND - 1`

#
# Translate varibles to use all UPPER CASE
# Just examples from various scripts
#
LOCA=`echo $LOCA | tr '[usk]' '[USK]'`
AUDIT=`echo $AUDIT | tr '[verpaudct]' '[VERPAUDCT]'`
BACREC=`echo $BACREC | tr '[notsm]' '[NOTSM]'`
OSTYPE=`uname -s`
# Set SITE based on $1 - shift has taken place
SITE=$1
SITE=`echo $SITE |tr '[umiflv3]' '[UMIFLV3]'`

#
# LOGGING and I/O Redirection
#

# Send command output to /dev/null - Redirect stderr to stdout
# command > /dev/null 2>&1
#
# STDIN = fd 0 / STDOUT = fd 1 / STDERR = fd 2
#
# Create LOGFILE - Send STDOUT (1) to LOGFILE.PID
# STDERR (2) will not be redirected - sent to console STDOUT
LOGFILE=`hostname`.out
exec >/tmp/$LOGFILE.$$

#
#
# Create LOGFILE // Create FD3 and redirect output to STDOUT (1)
# Send STDOUT (1) to LOGFILE // 2>&1 = Send STDERR (2) to STDOUT (1)
# So both STDOUT & STDERR to LOGFILE.PID and also have FD3 as STDOUT
LOGFILE=`hostname`.out
exec 3>&1 1> ${LOGFILE}.$$ 2>&1
# To redirect STDOUT (1) to fd3 in script temporarily to display to console
# some command 1>&3
# Close fd3 = exec 3>&-

#
# Log STDOUT and stDERR to Separat Files and Allow Redirection to Console as well
# Create LOGFILE and ERRFILE
# FD3 .. Create FD3 and Redirect to stdout (1) then send STDOUT to LOGFILE.PID
# FD4 .. Create FD4 and Redirect to STDERR (2) then send STDERR to ERRFILE.PID
LOGFILE=`hostname`.SUCCESS
ERRFILE=`hostname`.ERROR
exec 3>&1 1>> /tmp/${LOGFILE}.$$
exec 4>&2 2>> /tmp/${ERRFILE}.$$
# In script redirect output to console .. xxx 1>&3
# If desire to send script output to ERRFILE .. xxx >> ${ERRFILE} 2>&1
# from my fw flow test script - forget why I was doing this
# Close fd3 & fd4 // exec 3>&- // exec 4>&-
#
# create picklist for user
# Function of SELECT LOOP
# Creates Numbered List for User
# Torn from another script to use as reference
#
select_old () {
    select PR in `ls $DIR`
do
    echo you picked $PR \($REPLY\)
    break;
done
}

#
# Loop to ask user questions
# UNTIL LOOP - WHILE LOOP
# Execute set of commands until condition is TRUE
# NOTE - VERY OLD SCRIPT - Would change how I do it today
#
VALID=""
until [ -n "$VALID" ]
do
    echo "Is this a Master Server or Slave Server? (M/S): \c"
    read CHOICE
    case "$CHOICE"
    in
        [MmSs]) echo "Enter Hostname and IP Information"
            touch /tmp/MASTERSERVER >/dev/null
            echo "Enter Hostname of THIS BOX"
            read NS1
            echo "Enter IP Address of THIS BOX"
            echo "-- Separate Octets with Spaces --"
            echo "ie 129 41 48 4"
            read IP1NS1 IP2NS1 IP3NS1 IP4NS1
            echo "Domain for the site"
            echo "ie emea.umi.ibm.com or na.umi.ibm.com"
            echo "Do NOT Include the trailing dot!!!"
            read DOMAIN
            echo "A caching server needs one or two forwarder IPs"
            echo "Enter IP Address DNS Forward 1"
            echo "ie 149.131.116.135"
            read CACHE1
            echo "Enter IP Address DNS Forward 2"
            echo "Enter an IP even if you only have one forwarder"
            echo "Manually edit /etc/resolv.conf afterwards"
            echo "Manually edit /tmp/forward.txt before reading it"
            echo "into /usr/local/bind-jail/named.conf"
            echo "ie 149.131.116.136"
            read CACHE2
            VALID=TRUE;;
      *) echo "Enter M or S"
    esac
done

#
# OPERATORS
#
# Some of the operators string and file that I forget about and
# just some useful stuff as well
#
# String tests (double quote variables to avoid issues)
# STRING='' # NULL
# -z // Checks string is NULL - TRUE if string is EMPTY (false NOT null)
# -n // Checks string is NOT NULL - TRUE if string is NOT EMPTY (false if null)
# File Tests
# -f // true if file exits and is regular file
# -d // true if file exists and is a directory
# true if file exists and is // -c char special file // -b block // -p pipe // -S socket
# true if file exists and is // -g sgid bit set // -u suid bit set
# true if file exists and is // -r readable // -x executable // -w writeable
# -s // true if file exists and bigger than 0 not empty
# if [ $X != $Y ] X NOT EQUAL Y
# if [ ! -d /path/to/dir ] Directory does NOT Exist
# if [ ! -f /path/to/file ] FILE does NOT Exist
#EOF