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.