You are not logged in.

#1 2012-06-10 12:00:42

emile
Member
Registered: 2011-11-19
Posts: 6

Bash script TEMPLATE - Feedback welcome

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

#2 2012-06-10 12:06:32

emile
Member
Registered: 2011-11-19
Posts: 6

Re: Bash script TEMPLATE - Feedback welcome

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

#3 2012-06-10 12:09:34

Trilby
Inspector Parrot
Registered: 2011-11-29
Posts: 30,332
Website

Re: Bash script TEMPLATE - Feedback welcome

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

#4 2012-06-11 11:48:50

emile
Member
Registered: 2011-11-19
Posts: 6

Re: Bash script TEMPLATE - Feedback welcome

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

Board footer

Powered by FluxBB