You are not logged in.
Hi all,
I use the following template for my bash scripts. It includes predefined variables, functions, sanity checks, logfile generation and cleanup. Its been written as part of the process ive taken while learning bash scripting, and has been derived and modified from many other scripts that were not mine - so many thanks to contributors!
Inviting comment/suggestions, or as a contrbution to others in the hope its helpful. This is compatible with Bash 4+.
In case you are wondering, i name variables as V_VARIABLENAME and functions as f_functionname.
#!/bin/bash -
#
# SCRIPT: NAME_of_SCRIPT
# AUTHOR: AUTHORS_NAME
# DATE: DATE_of_CREATION
# REV: 1.1.A
# (A, B, D, T and P. For Alpha, Beta, Dev, Test and Production)
#
# PLATFORM: (SPECIFY: AIX, HP-UX, Linux, OpenBSD, Solaris
# or Not platform dependent)
#
# PURPOSE: Give a clear, and if necessary, long, description of the
# purpose of the shell script. This will also help you stay
# focused on the task at hand.
#
# REV LIST:
# DATE: DATE_of_REVISION
# BY: AUTHOR_of_MODIFICATION
# MODIFICATION: Describe what was modified, new features, etc--
#
# set -n # Uncomment to check script syntax, without execution.
# # NOTE: Do not forget to put the comment back in or
# # the shell script will not execute!
# set -x # Uncomment to debug this shell script
#
##########################################################
# PERMANENT VARIABLES HERE
##########################################################
## Script metadata
V_SCRIPTNAME=${0##*/}
V_SCRIPTDIR="`dirname $0`"
V_SCRIPTVER="`grep -m1 "# REV: " $0 | cut -d " " -f3`"
V_USAGE="[sudo] $V_SCRIPTNAME [-x|-y|-z] [\$1-] "
V_USER="`who -m | awk '{print $1;}'`" #Calling user name if using sudo
V_RUNUSERID="`id -u`" #Run user ID
## Script runtime data
V_SYSINFO="${V_USER}@`uname -n` (`uname -m` `uname -r` `uname -s`"\) #Host (running) OSver, OS, Type, 32/64Bit
V_DTG="`date +%d%b%y-%H%M`"
V_TMPDIR="$(mktemp -dp /var/tmp --suffix="_${V_SCRIPTNAME}_${V_DTG}")" #export V_TMPDIR="$(mktemp -dp /home/${V_USER}/tmp --suffix="_${V_SCRIPTNAME}_${V_DTG}")"
V_LOGFILE="${V_SCRIPTNAME}_${V_DTG}.log"
##########################################################
# USER DEFINED VARIABLES HERE
##########################################################
V_OUTPUT="/home/${V_USER}/Backups" #Base directory for saving backups/logfiles (subdirectory created by script)
V_BUPDIR="${V_OUTPUT}/${V_SCRIPTNAME}_${V_DTG}"
V_KEEPFILES="confirm" #[yes|no|confirm] #Copy temp and backup files to backup location at end of script.
## Used shell built-in variables
# $HOSTNAME
# $USER
# $BASH_VERSION
# $LINENO
# $HOME
# $FUNCNAME
##########################################################
# SECURITY FEATURES
##########################################################
IFS=$' \t\n' || ( echo "[!] $V_SCRIPTNAME line${LINENO}: Critical failure whilst implementing security features. Quitting. "; exit 1 ) #RESET IFS
SYSPATH="$(command -p getconf PATH 2>/dev/null)" || ( echo "[!] $V_SCRIPTNAME line${LINENO}: Critical failure whilst implementing security features. Quitting. "; exit 1 )
if [[ -z "$SYSPATH" ]]; then
SYSPATH="/usr/local/bin:/bin:/usr/bin" || ( echo "[!] $V_SCRIPTNAME line${LINENO}: Critical failure whilst implementing security features. Quitting. "; exit 1 )
fi
PATH="$SYSPATH:$PATH" || ( echo "[!] $V_SCRIPTNAME line${LINENO}: Critical failure whilst implementing security features. Quitting. "; exit 1 ) #DEFINE CLEAN PATH
unset -f unalias
\unalias -a || ( echo "[!] $V_SCRIPTNAME line${LINENO}: Critical failure whilst implementing security features. Quitting. "; exit 1 ) #REMOVE ALIASES
unset -f command
##########################################################
# PERMANENT FUNCTIONS HERE
##########################################################
f_usage() #@ DESCRIPTION: print usage information
{ #@ USAGE: f_usage
printf "%s - USAGE: %s\n" "$V_SCRIPTNAME" "$V_USAGE"
}
f_exittrap() #@ DESCRIPTION: Capture exit signals and process cleanup requirements.
{ #@ USAGE: <trap f_exittrap 0 x y z>, where x y z are other interrupt signals.
tput smso
echo -en "[!] Processing script shutdown sequence... "
while [[ $V_KEEPFILES != "done" ]]; do
if [[ $V_KEEPFILES = "confirm" ]]; then
echo; f_yesno "[Q] Keep logfile and backup files?" N && local V_KEEPFILES="yes" || local V_KEEPFILES="no"
elif [[ $V_KEEPFILES = "yes" ]]; then
if [[ ! -d "$V_BUPDIR" ]]; then
mkdir -pm 0700 ${V_BUPDIR} || ( echo ; f_failed $LINENO "Failed to create permanent backup directory. Backup files will be retained in temporary directory \"${TMPDIR}\" until session logout/reboot." ; local V_BUPDIR="$V_TMPDIR" )
fi
cp $0 ${V_BUPDIR}/${V_SCRIPTNAME}_v${V_SCRIPTVER}_.backup || f_failed $LINENO "Command failed! Continuing.\n"
if [[ -e "${V_TMPDIR}/${V_LOGFILE}" && "${V_BUPDIR}" != "${V_TMPDIR}" ]]; then
mv ${V_TMPDIR}/${V_LOGFILE} ${V_BUPDIR} || f_failed $LINENO "Command failed! Continuing.\n"
fi
if [[ "${V_BUPDIR}" != "${V_TMPDIR}" && -d "$V_TMPDIR" ]]; then
rm -r ${V_TMPDIR} || f_failed $LINENO "Failed to delete TMPDIR! Delete \"${V_TMPDIR}\" manually. Continuing.\n"
fi
local V_KEEPFILES="done"
elif [[ $V_KEEPFILES = "no" ]]; then
if [[ -d "$V_TMPDIR" ]]; then
rm -r ${V_TMPDIR} || f_failed $LINENO "Failed to delete TMPDIR! Delete \"$V_TMPDIR\" manually. Continuing.\n"
fi
local V_KEEPFILES="done"
fi
done
echo -e "[>] FINISHED!\n"
tput sgr0
exec 1>&- 2>&-
exit 0
}
f_exec() #@ DESCRIPTION: Create temporary working directory (MKTEMP) and logfile (TOUCH), activate (EXEC) redirection to print stdout to screen, and TEE stdin/stdout/stderr to logfile.
{ #@ USAGE: Required as first item in the runtime part of the script. Also requires "exec 1>&- 2>&-" as the last entry of the runtime section to make std-in/-out/-err sane.
mkdir -pm 0700 ${V_TMPDIR} || f_die $LINENO
cd $V_TMPDIR
touch ${V_LOGFILE} || f_die $LINENO
exec 1> /dev/tty >&1
exec 1> >(tee ${V_TMPDIR}/${V_LOGFILE}) 2>&1
exec 2> /dev/null
}
f_logfile() #@ DESCRIPTION: Check for and create runtime directory for logfile and backups. Transfer temporary logfile. Create copy of running script. Display/log usage metadata.
{ #@ USAGE: <exec XYZ> (stdout/stderr output options) ; f_logfile
echo -e "\n### Initialising:\t${V_SCRIPTNAME^^} v${V_SCRIPTVER}\t\t@ ${V_DTG}"
echo -e "### Metadata:\t\t${V_SYSINFO}"
#clear ; sleep 3 ; clear
}
f_sanity() #@ DESCRIPTION: Execute sanity checks for script execution (incl root check - if required)
{ #@ USAGE: f_sanity $V_RUNUSERID
if [[ "$1" = "0" ]]; then f_failed $LINENO "Critical failure! This script must be run by root. Exiting." 1; fi #ROOT CHECK
}
f_die() #@ DESCRIPTION: print error message and line number and "exit 1"
{ #@ USAGE: f_die $LINENO
tput smso
if [[ -e ${V_TMPDIR}/${V_LOGFILE} ]]; then
printf "[!] %s\n[!]%s\n" "$V_SCRIPTNAME line${1}: Critical failure! Check logfile: \"$V_TMPDIR/${V_LOGFILE}\"." "\"${V_SCRIPTNAME}\" cannot continue and must quit... "
elif [[ -e ${V_BUPDIR}/${V_LOGFILE} ]]; then
printf "[!] %s\n[!]%s\n" "$V_SCRIPTNAME line${1}: Critical failure! Check logfile: \"$V_BUPDIR/${V_LOGFILE}\"." "\"${V_SCRIPTNAME}\" cannot continue and must quit... "
else
printf "[!] %s\n[!] %s" "$V_SCRIPTNAME line${1}: Critical failure!" "\"${V_SCRIPTNAME}\" cannot continue and must quit... "
fi
tput sgr0
exit 1
}
f_failed() #@ DESCRIPTION: print error message and optionally exit with specified code
{ #@ USAGE: f_failed $LINENO [MESSAGE] STATUS
tput smso
printf "[!] %s %s\n" "${V_SCRIPTNAME} line${1}:" "$2"
tput sgr0
if [[ -n "$3" ]] && [[ "$3" = *[[:digit:]]* ]]; then exit "$3"; fi
}
f_success() #@ DESCRIPTION: print success message and return (success) code
{ #@ USAGE: f_success [MESSAGE] STATUS
printf "[>] %s\n" "$1"
if [[ -n "$2" ]] && [[ "$2" = *[[:digit:]]* ]]; then return "$2"; fi
}
f_yesno() #@ DESCRIPTION: yes/no prompt - single letter (y|Y/n|N) accepted, with [optional] default answer at $2
{ #@ USAGE: yesno "QUESTION" [Y/N] && <true cmds> || <false cmds>
REPLY=
while [[ "$REPLY" != "Y" || "$REPLY" != "N" ]]; do
if [[ "$2" = "Y" ]]; then
echo -en "$1 [Y]n >>> "
read -i "Y" -e REPLY
elif [[ "$2" = "N" ]]; then
echo -en "$1 y[N] >>> "
read -i "N" -e REPLY
else
echo -en "$1 (yn) >>> "
read REPLY
fi
case ${REPLY} in
y|Y) return 0 ;;
n|N) return 1 ;;
*) echo -e "[!] Invalid selection. Try again.\n" ;;
esac
done
}
##########################################################
# USER DEFINED FUNCTIONS HERE (including formatting functions repeat/_repeat/f_alert)
##########################################################
repeat() #@ DESCRIPTION: Function used to repeat a ($1) string ($2) times
{ #@ USAGE: repeat "string" 50
_repeat "$@"
printf "%s\n" "$_REPEAT"
}
_repeat() #@ DESCRIPTION: Function (and subfunction) used to generate repeated strings, or title/alert text box with <f_alert>
{ #@USAGE: Called by <f_alert>, passing data to <repeat> subfunction.
_REPEAT=$1
while [ ${#_REPEAT} -lt $2 ]; do
_REPEAT=$_REPEAT$_REPEAT$_REPEAT
done
_REPEAT=${_REPEAT:0:$2}
}
f_alert() #@ DESCRIPTION: Display a message ($1) bounded by a (=) text box, or optionally set by $2
{ #@ USAGE: f_alert "MESSAGE"
#@ REQUIRES: Functions: <repeat> and <_repeat>
if [[ "$2" = "" ]]; then local V_STRING="="; else V_STRING="$2" ; fi
_repeat "${2:-"$V_STRING"}" $(( ${#1} + 6 ))
printf '%s\n' "$_REPEAT"
printf '%2.2s %s %2.2s\n' "$_REPEAT" "$1" "$_REPEAT"
printf '%s\n' "$_REPEAT"
}
##########################################################
# SCRIPT STARTS:
# PERMANENT RUNTIME REQUIREMENTS: exit trap, exec stdin/-out/-err , temp directories and log files
##########################################################
clear
trap f_exittrap 0 SIGHUP SIGINT SIGQUIT SIGTERM SIGSTOP
f_exec
f_logfile
#f_sanity $V_RUNUSERID
##########################################################
# BEGINNING OF MAIN SCRIPT
##########################################################
## End of script
Last edited by emile (2012-06-11 11:44:35)
Offline
Post-edit: If a moderator thinks this is better posted in https://bbs.archlinux.org/viewforum.php?id=33 then please feel free to move across.
Offline
Interesting idea - a lot of it looks pretty good - but there is one error I see, and one oddity.
First the error: you call f_die before it is declared, this will cause an error if the conditionals it calls from every return false (which is why that function is there in the first place).
The oddity: there probably is no need to export all those variables. Where do you intend to export them to? Just set them.
Also, this does a lot of setup work. This would be horribly inefficient for any script that doesn't use all that stuff. This reads more like a bash library than a script. Good practice no doubt, and a good way to learn, but real scripts (IMHO) should be streamlined to do just what they need to do.
Edit: your conditional tests need work. "=" is not the same as "==".
Last edited by Trilby (2012-06-10 12:12:03)
"UNIX is simple and coherent" - Dennis Ritchie; "GNU's Not Unix" - Richard Stallman
Offline
Hi Trilby, thank you for your feedback. I have taken this on board - particularly the early function call and have made changes to reflect this.
I agree that this is close to a function library - and I use it as such. Some scripts have a number of sections removed if unneeded.
Regarding your other comments - I exported my variables thinking this was required for functions called by the script. Obviously mistaken! Ammended. Also, changed some of my tests, though I am unsure about the distinction when my variables are quoted (???). I will do some more reading...
Thanks again.
Offline