Last active
March 22, 2020 09:33
-
-
Save mmyers1474/8b1b149053444bbb91d13d1590b76fcd to your computer and use it in GitHub Desktop.
Robust Shell Script Template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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