Skip to content

Instantly share code, notes, and snippets.

@mmyers1474
Last active March 22, 2020 09:33
Show Gist options
  • Save mmyers1474/8b1b149053444bbb91d13d1590b76fcd to your computer and use it in GitHub Desktop.
Save mmyers1474/8b1b149053444bbb91d13d1590b76fcd to your computer and use it in GitHub Desktop.
Robust Shell Script Template
#!/bin/bash
#===============================================================================
# HEADER
#==============================================================================
# IMPLEMENTATION
#% name bash_template
#% title Bash Shell Template
#% version 0.0.1
#% checksum 5926ed74eb9baf0dc74b08e2e9be26a0
#% author Matthew Myers
#% copyright Copyright (c) http://www.metatechlabs.com
#% license GNU General Public License
#
#===============================================================================
# SYNOPSIS
#? ${scriptname} [-hv] [-o[file]] args ...
#?
#? DESCRIPTION
#? This is a script template
#? to start any good shell script.
#?
#? OPTIONS
#? -o [file], --output=[file] Set log file (default=/dev/null)
#? use DEFAULT keyword to autoname file
#? The default value is /dev/null.
#? -t, --timelog Add timestamp to log ("+%y/%m/%d@%H:%M:%S")
#? -x, --ignorelock Ignore if lock file exists
#? -h, --help Print this help
#? -v, --version Print script information
#?
#? EXAMPLES
#? ${scriptname} -o DEFAULT arg1 arg2
#
#===============================================================================
# OPTIONS
# This section has several options you can select form that control the behavior
# of the script at runtime. DO NOT UNCOMMENT ANYTHING. Just set the value and
# the script parses the section below without taking up additonal memory or variables.
#
# Option Validation Regexp Description
#& outputtype=both #(screen|logfile|both) Send ouptput to the screen, a log file, or both.
#& errortrapping=yes #(yes|no) Using built-in error handling or not.
#& runlocking=no #(yes|no) Use RUNLOCKING to prevent multiple instances.
#===============================================================================
# HISTORY
#@
#===============================================================================
# END_OF_HEADER
#===============================================================================
# --- Load Properties ----------------------------------------------------------
# ----- Read only properties ---------------------------------------------------
readonly __scriptbase="$( cd $( dirname ${BASH_SOURCE[0]} ) && pwd )"
readonly __scriptname="$(basename ${0})"
readonly __this="${__scriptbase}/${__scriptname}"
# ----- Read/Write Properties --------------------------------------------------
__name=$(sed -n 's/^#%[ \t]\+name[ \t]\+//gp' "${__this}")
__title=$(sed -n 's/^#%[ \t]\+title[ \t]\+//gp' "${__this}")
__version=$(sed -n 's/^#%[ \t]\+version[ \t]\+//gp' "${__this}")
__checksum=$(sed -n 's/^#%[ \t]\+checksum[ \t]\+//gp' "${__this}")
__about=$(sed -n 's/^#% //gp' "${__this}")
__usage=$(sed -n 's/^#? //gp' "${__this}")
#__subject=od -x /dev/urandom | head -1 | awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}'
# --- Self Maintenance Operations ----------------------------------------------
# ----- Debugging Output -------------------------------------------------------
# A simple colorized debugging output function. If the DEBUG environment variable
# is set then any statements using the debug function call will be output to the
# screen with colorization via the colorized echo function defined above.
function debug() {
declare -A color
color=(
[n]='\033[0m' # Text Reset
[k]='\033[0;30m' # Black
[r]='\033[0;31m' # Red
[g]='\033[0;32m' # Green
[y]='\033[0;33m' # Yellow
[b]='\033[0;34m' # Blue
[p]='\033[0;35m' # Purple
[c]='\033[0;36m' # Cyan
[w]='\033[0;37m' # White
[K]='\033[0;90m' # Black
[R]='\033[0;91m' # Red
[G]='\033[0;92m' # Green
[Y]='\033[0;93m' # Yellow
[B]='\033[0;94m' # Blue
[P]='\033[0;95m' # Purple
[C]='\033[0;96m' # Cyan
[W]='\033[0;97m' # White
)
if [[ ${DEBUG} ]]
then
in="$*"
out="${in//|/|\}}"
out="${out//[/\${color[}"
echo -e "${out}"
fi
}
DEBUG=$(echo "${DEBUG}")
if [[ ${DEBUG} ]]; then
echo "DEBUGGING output enabled!"
fi
# ----- Self Editing -----------------------------------------------------------
if [[ "$1" == "checksum" ]]
then
IFS='.' read major minor rev <<< "${__version}"
debug "Major: ${major} Minor: ${minor} Revision: ${rev}"
rev=$((rev+1))
if [[ "${rev}" -gt 99 ]]
then
rev=0
((minor++))
if [[ "${minor}" -gt 9 ]]
then
((major++))
fi
fi
__xx__="${major}.${minor}.${rev}"
debug "version = ${__xx__}"
cmd="sed -i"
script="${__this}"
regexp="s/^\\(#%[ \\t]\\+version[ \\t]\\+\\)[0-9.]\\+/\\1${__xx__}/g"
echo "sed -i '${regexp}' ${script}" > "./update.sh"
__xx__=$(md5sum "${__this}" | awk '{ print $1 }')
debug "checksum = ${__xx__}"
regexp="s/^\\(#%[ \\t]\\+checksum[ \\t]\\+\\)[a-zA-Z0-9]\\+/\\1${__xx__}/g"
echo "sed -i '${regexp}' ${script}" >> "./update.sh"
echo 'rm -f "./update.sh"' >> "./update.sh"
.update.sh &
exit
fi
if [[ "$1" == "name" ]]
then
sed -i "s/^\(#%[ \t]\+name[ \t]\+\)[a-zA-Z0-9]\+/\1${__scriptname}/g" "${__this}"
fi
if [[ "$1" == "title" ]]
then
sed -i "s/^\(#%[ \t]\+title[ \t]\+\)[a-zA-Z0-9]\+/\1$2/g" "${__this}"
fi
if [[ "$1" == "author" ]]
then
sed -i "s/^\(#%[ \t]\+name[ \t]\+\)[a-zA-Z0-9]\+/\1$2/g" "${__this}"
fi
# ----- Self Validation --------------------------------------------------------
__xx__=$(md5sum "${__this}" | awk '{ print $1 }')
debug "Saved checksum: ${__checksum}"
debug "Actual checksum: ${__xx__}"
if [[ "${__checksum}" != "${__xx__}" ]]
then
echo "The checksum for this script has changed. If you have not modified this script recently verify the time/date stamp and the contents before running, it may be compromised."
echo -n "Do you wish to continue with the execution of this potentialy compromised script? [Y/N] "
read -n1 yorn
echo
if [[ "${yorn,,}" == "n" ]]
then
exit 127
fi
fi
# - Script/Interpreter Configuration -------------------------------------------
# --- Command Interpreter Configuration ----------------------------------------
set -e # exit immediate if an error occurs in a pipeline
set -u #
set -o pipefail # trace ERR through pipes
set -o errtrace # trace ERR through 'time command' and other functions
# set -x # Uncomment to debug this shell script
# set -n # Uncomment to check your syntax, without execution.
# --- Declarations -------------------------------------------------------------
# ----- Constants --------------------------------------------------------------
readonly MINARGCOUNT=0
readonly LOGFILE="/var/log/${__scriptname}.log"
# ----- Variables --------------------------------------------------------------
declare -A color
# ----- Functions --------------------------------------------------------------
# --- Internal Features --------------------------------------------------------
# ----- Output Type Selection --------------------------------------------------
# Simple output redirection to the screen, a log file, or both all based on a simple
# setting at the top of the script.
__xx__=$(sed -ne "s/^#& outputtype=//gp" "${__this}" | sed -ne "s/#.*//gp" | xargs)
debug "outputtype = ${__xx__}"
case "${__xx__,,}" in
'screen')
echo "Sending all output to the screen only."
;;
'logfile')
echo "Sending all output to ${LOGFILE} only."
exec > "${LOGFILE}"
exec 2>&1
;;
'both')
echo "Sending all output to your screen and to ${LOGFILE}."
# exec > "${LOGFILE}"
exec 2>&1 | tee -a "${LOGFILE}"
;;
esac
# ----- Signal and Error Trapping ----------------------------------------------
# Cover trapping errors as well as a few other signals first and foremost.
__xx__=$(sed -ne "s/^#& errortrapping=//gp" "${__this}" | sed -ne "s/#.*//gp" | xargs)
debug "errortrapping = ${__xx__}"
if [[ "${__xx__,,}" == "yes" ]]
then
trap 'echo -n "${__scriptname}: FATAL ERROR at $(date "+%HH%M") on or near line ${lineno}.\n The returned code or message was: $?." 2>&1; exit $?;' ERR INT QUIT TERM
fi
trap 'echo -n "Cleanup: To Do"' EXIT
# ----- Locking & PID ----------------------------------------------------------
__xx__=$(sed -ne "s/^#& runlocking=//gp" "${__this}" | sed -ne "s/#.*//gp" | xargs)
__pidfile="/var/run/${__scriptname}.pid"
debug "runlocking = ${__xx__}"
debug "__pidfile = ${__pidfile}"
if [[ "${__xx__,,}" == "yes" ]]; then
if [[ -f "${__pidfile}" ]]; then
echo "${__scriptname} is already running"
exit
fi
echo "$$" > "${__pidfile}"
fi
unset __xx__
debug "Variable __xx__ has been unset."
# --- Featured Functions -------------------------------------------------------
# ----- Featured Function Initialization ---------------------------------------
# Assign colors to a single letter color table in a global array.
function assign_colors() {
color=(
[n]='\033[0m' # Text Reset
[k]='\033[0;30m' # Black
[r]='\033[0;31m' # Red
[g]='\033[0;32m' # Green
[y]='\033[0;33m' # Yellow
[b]='\033[0;34m' # Blue
[p]='\033[0;35m' # Purple
[c]='\033[0;36m' # Cyan
[w]='\033[0;37m' # White
[K]='\033[0;90m' # Black
[R]='\033[0;91m' # Red
[G]='\033[0;92m' # Green
[Y]='\033[0;93m' # Yellow
[B]='\033[0;94m' # Blue
[P]='\033[0;95m' # Purple
[C]='\033[0;96m' # Cyan
[W]='\033[0;97m' # White
)
}
# ----- Colorized echo ---------------------------------------------------------
# Colorized echo with simplistic implementation
# Use a single letter enclosed in square brackets to indicate which color you want.
function cecho() {
if [[ -z ${color} ]]; then assign_colors; fi
in="$*"
out="${in//]/]\}}"
out="${out//[/\${color[}"
echo -e "${out}"
}
# --- User Segment -------------------------------------------------------------
# ----- CLI Option Processing --------------------------------------------------
if [[ $# -lt "${MINARGCOUNT}" ]] ; then
echo "${__USAGE__}"
exit 1;
fi
while getopts ":hAV" optname
do
case "$optname" in
"h")
echo "${__USAGE__}"
exit 0;
;;
"A")
echo "${__ABOUT__}"
exit 0;
;;
"V")
echo "Version ${__VERSION__}"
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))
# ------------------------------------------------------------------------------
# SCRIPT LOGIC GOES HERE
# ------------------------------------------------------------------------------
ZWNobyAiJHtfX2NoZWNrc3VtfSIK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment