To see how this is used return to this tutorial index.
#!/bin/ksh -u
# This is a skeleton for a daemon shell script with short & long argument processing.
# The point about a daemon is that it is not run at a terminal but either started
# at system boot or perhaps from cron.
# It is similar to Skeleton.sh but has some extra standard bits to perform logging
# to a file and option to mail people of success/failure.
# Note that if this is being run from cron that the output will be mailed to the user,
# so there is some sense in generating messages to stderr.
# This probably contains features that you might not want or need, just delete those bits.
# Festures:
# * Logging to a log file or stderr. Verbose & quiet options
# * Send email to named people, perhaps only on error
# * Test mode/switch
# * Site and then user specific startup - set defaults
# * Lock to prevent multiple instances running at once
# Look for 'blah' - you will need to change things there.
# This also works with modern versions of bash
# Copyright (c) Alain D D Williams <addw@phcomp.co.uk> April 2012-2020
# It is expected that you will modify this skeleton for your own use.
# This code is released as free software under the BSD 3 clause license:
# https://directory.fsf.org/wiki/License:BSD-3-Clause
# SCCS: @(#)SkeletonDaemon.sh 1.6 10/26/20 13:44:53
PROGNAME=${0##*/}
# Log & Mail the error message and abort the script.
# Quiet mode is switched off, ie something always said to stderr.
function Die {
Error=y
quiet=0
Log "$@"
MailThem "$@"
exit 2
}
# Generate a note in verbose mode
# Always log to the log file
# If the first arg is a digit, only log to stderr if $verbose is at least that number.
function Note {
typeset -i level=1
[[ $# -gt 1 && $1 == [0-9] ]] && level=$1 && shift
echo "$( date '+%Y''%m%d:%H''%M' ): $@" >> $LogFile
(( verbose < level )) && return
echo "$*" >&2
}
# Log to file. Also log to the user (stderr) unless quiet.
# If option '-b' put a blank line out first.
function Log {
if [[ $# -ge 1 && $1 = -b ]]
then echo "" >> $LogFile
(( quiet == 1 )) || echo "" >&2
shift
fi
echo "$*" >> $LogFile
(( quiet == 1 )) && return
echo "$PROGNAME: $*" >&2
}
# Put the contents of a file to the log file.
# Send it to the user unless quiet.
# Args: introduction_string filename end_comment_string
# First output the introduction_string unless it is zero length (emtpy)
# Then the file
# Then the end_comment_string if it is present and not empty
function LogFile {
[[ -n $1 ]] && echo "$1" >> $LogFile
cat $2 >> $LogFile
[[ $# -ge 3 && -n $3 ]] && echo "$3" >> $LogFile
[[ $quiet = 1 ]] && return
[[ -n $1 ]] && echo "$1"
cat $2
[[ $# -ge 3 && -n $3 ]] && echo "$3"
}
# Log to user & file, to user (stderr) only in verb mode.
# If option '-b' put a blank line out first:
function VerbLog {
if [[ $# -ge 1 && $1 = -b ]]
then echo "" >> $LogFile
(( verbose == 0 )) || echo "" >&2
shift
fi
echo "$( date '+%Y''%m%d:%H''%M' ): $@" >> $LogFile
(( verbose == 0 )) && return
echo "$@" >&2
}
# Mail the arguments to those who we have been asked to email - if any
# Also show disk usage - if mounted
function MailThem {
[[ -z $MailTo ]] && return
[[ $Error = n ]] && Worked=Success || Worked=Failure
( echo "$*"
echo ""
# Maybe some more information here
) | mail -s "$PROGNAME: blah, blah: $Worked" $MailTo
}
# Generate a usage message and exit. If there is an argument print it and exit code is 2
function Usage {
(( $# > 0 )) && echo "$PROGNAME: $*" >&2 && MailThem "$*"
sed -e 's/^^//' <<-!
Description ... blah, blah
Multiple instances running in parallel is prevented by use of: $LockFile
Usage: $PROGNAME [-opts] [files]
-h --help
^ Help message
-L file Log file - default $LogFile
-M addr --mail-to=addr
^ Mail a success/fail message to addr, space separated list. May be given more than once.
--mail-only-on-error
^ Send mail only if/when there is an error. The --mail-to option must be given.
--na=arg
^ A long option that needs an argument
^ Can use space instead of '=': --na arg
--oa[=arg]
^ A long option that has an optional argument
^ Here the '=' cannot be replaced by a space
-q --quiet
^ Don't complain about non errors
-t --test
^ Use test account and files, blah, blah
-v Verbose - say a bit more than usual about what is happening.
^ Give this more than once for increased verbosity.
- -- Denotes the end of the options. Arguments after this will be handled as file names
^ even if they start with a '-'.
^ This may either be a '-' or '--'.
More words, blah, blah
Version: 1.6 10/26/20
!
exit $#
}
# **** Start ****
# Default values
Error=n # Set to 'y' it something goes wrong
verbose=0 # May be multi level
quiet=0
testing=0
MailTo= # Who to email results to
MailOnlyOnError=n
LogFile=/var/log/${PROGNAME%.sh}
LockFile=/var/run/${PROGNAME%.sh}.pid
LogFileTest=/tmp/${PROGNAME%.sh}.log
LockFileTest=/tmp/${PROGNAME%.sh}.pid
LockTimeout=$(( 19 * 60 )) # Assume a program stuck if still around for this long
# Look for a site or user specific configuration file(s) for this application.
# This should be a script that might (typically) set configuration values above.
# Use:
# * /etc/default/program
# * $home/.program - in the home directory of the effective user (not the value in $HOME)
for confroot in /etc/default/ ~$( id --user --name )/.
do [[ -f $confroot${PROGNAME%.sh} ]] && . $confroot${PROGNAME%.sh}
done
# Long Options, value: 0 - no argument, 1 - required argument, 2 - optional argument
typeset -A LongOpts=([help]=0 [quiet]=0 [mail-only-on-error]=0 [mail-to]=1 [na]=1 [oa]=2 [test]=0)
ShortOpts=:hL:M:qtv
# Parse options, recognise --options
while [[ $# -ge $OPTIND ]] && eval opt=\${$OPTIND} || break
[[ $opt == - ]] && shift && break
if [[ $opt == --?* ]]
then opt=${opt#--}
shift
# Argument to option ?
[[ $opt == *=* ]] && OPTARG=${opt#*=} && opt=${opt%=$OPTARG} && hasArg=1 || typeset OPTARG= hasArg=0
# Check if known option and if it has an argument if it must:
if [[ -z ${LongOpts[$opt]:-} ]]
then OPTARG=$opt && opt='?'
else if [[ $hasArg = 0 && ${LongOpts[$opt]} = 1 ]]
then # Required argument. Can grab next arg ?
if (( $# >= $OPTIND ))
then OPTARG=$1 && shift
else OPTARG=$opt && opt=:
fi
fi
[[ $hasArg = 1 && ${LongOpts[$opt]} = 0 ]] && OPTARG=$opt && opt=::
fi
true # for the while
else getopts $ShortOpts opt
fi
do case "$opt" in
h|help) Usage ;;
q|quiet)quiet=1 ;;
L) LogFile="$OPTARG" ;;
M|mail-to)
MailTo="$MailTo $OPTARG" ;;
mail-only-on-error)
MailOnlyOnError=y ;;
na) echo "Option --na has the argument that it needs: '$OPTARG'" ;;
oa) if [[ $hasArg = 1 ]]
then echo "Option --oa has optional argument: '$OPTARG'"
else echo "Option --oa does not have an optional argument"
fi ;;
v) (( verbose++ )) ;;
t|test) testing=1 ;;
::) Usage "Unexpected argument to option '$OPTARG'" ;;
:) Usage "Missing argument to option '$OPTARG'" ;;
\?) Usage "Unknown option '$OPTARG'" ;;
*) Usage "Internal program error, unrecognised argument '$opt'" ;;
esac
done
shift $((OPTIND - 1))
# Different parameters when testing.
# Do this early since it will affect where errors are written to.
if (( testing ))
then LockFile=$LockFileTest
LogFile=$LogFileTest
# .....
fi
# Consistency checking:
[[ $MailOnlyOnError = y && -z $MailTo ]] &&
Die "Option --mail-only-on-error given without the --mail-to option"
# Ensure that there is a writable log file:
[[ -f $LogFile ]] || touch $LogFile || Die "Can't write to logfile: $LogFile"
# Stop 2 instances running at once.
# Put this program's PID into the lockfile
lockfile -r 0 -l $LockTimeout $LockFile 2>/dev/null || Die "Cannot acquire lock on $LockFile, owned by PID $(<$LockFile)"
chmod u+w $LockFile
echo $$ > $LockFile
trap "rm -f $LockFile" EXIT
# Easy to find line in the log file marking the start of a new run:
Log -b "**************** Starting at $( date ). Running as: $( id ). Pid: $$"
Log "verbose=$verbose quiet=$quiet testing=$testing"
# Actually do the work that this is supposed to do:
for a
do echo "arg='$a'"
done
# Perhaps send email unconditionally, or just on error:
if [[ $MailOnlyOnError = n || $Error = y ]]
then VerbLog "Sending email to: $MailTo"
MailThem "Report at end of job"
fi
# Show that this ended and when:
Log "**** Ending $( date ) ****"
# end
Return to this tutorial index.