Home

A Script template for ksh scripts

Some useful techniques for ksh scripts



Bernd Schemmer, 16  August 2015



Contents

  1. Contents
  2. Introduction
    1. History of this document
  3. Credits
  4. Why use a script template?
  5. How to use the script template
  6. Techniques and features used in the template
    1. Inline Documentation
    2. Inline examples
    3. Naming conventions
    4. Logging
    5. Runtime Initialisation
      1. Environment checks
      2. OS and shell dependent settings
      3. Restrict a script to run only once
      4. Using RBAC in Solaris 10
      5. Predefined variables
      6. Using configuration files
        1. Implementation Details
    6. Housekeeping using a trap handler
      1. Additional features of the trap handlers
      2. User defined trap handler
    7. Crash dumps
    8. Builtin tee functionality
    9. Include files
    10. Temporary files
    11. Running the script as daemon
    12. Predefined parameter
    13. Debugging
      1. Adding other extended options
    14. Environment variables
    15. Return codes
    16. Functions defined in the script template
      1. String handling functions
      2. Data convertion functions
      3. UID related functions
      4. Functions to implement a FIFO Stack
      5. Misc functions
      6. Function templates
  7. Links
    1. Scripts based on scriptt.sh


Introduction


This article discusses and documents scriptt.sh, a script template for kornshell scripts.  I use this script template for nearly all of the scripts I write for doing the day-to-day work.

I'm pretty sure that every System Administrator who is responsible for more than a few Solaris machines has her own bag of scripts for maintaining the machines. Nevertheless the script template and the programming techniques discusssed in this article might be useful for them also.

The script template is released under the Common Development and Distribution License, Version 1.0; a link to download the script is at the bottom of this page.

This documentation is based on scriptt.sh version 2.1.0.7, 25.07.2015.

Note that the script was developed mainly for the kornshell that is part of Solaris (/usr/bin/ksh) but it also runs on other Unix platforms like AIX and Linux or cygwin. The script also run on kornshell compatible shells like bash but not all features of the script work in other shells. Therefor I recommend to always use the real ksh to execute the scripts based on the script template.

The article is a rewrite of an article I wrote for the bigadmin section on sunsolve.sun.com which is not available on the net anymore



History of this document


Date
Release
Comment
16.08.2015
Updated documentation for scriptt.sh v2.1.0.7

31.05.2013
Updated documentation for scriptt.sh v2.0.0

22.04.2012
minor changes and error corrections

16.10.2011
minor changes only

15.10.2011
initial release







Credits

The script template uses code from the following sources:

Infos from WPollock about how to implement a semaphore in a shell script:

The source for the function PrintWithTimeStamp is from this web page (in scriptt.sh v2.x and newer):


http://unix.stackexchange.com/questions/26728/prepending-a-timestamp-to-each-line-of-output-from-a-command


The code in the function executeCommandAndLog is based on code on this webpage:

http://www.unix.com/unix-dummies-questions-answers/13018-exit-status-command-pipe-line.html#post47559



Why use a script template?


Simple answer: Because you don't want to invent the wheel every other day.

Writing a script template with the most frequently used functions one time and reuse it for your scripts makes life easier. Doing this as shell script instead of for example perl makes sure, that you can just copy the script to a new machine and it will work. 

Please note that the script template only uses standard Solaris (Unix) commands and does not rely on additional scripts or something special. It's done this way to make sure that it will run on any system simply by copying the script to the machine.



How to use the script template


To use the script template for your own script the following steps are necessary:

First copy the script template to a new file. Than edit the new file:

That's it..



Techniques and features used in the template


This section describes the techniques used in the script template.


Note that this is only a template for a script. Therefore to use it you should copy it to another name and edit that file. To use the features mentioned below with your script please replace "scriptt.sh" in the examples below with the name of your script.



Inline Documentation


Everyone knows that documentation is essential but no one wants to write them. Because of this it's a useful and simple technique to include the documentation inside the script as comments. To distinguish the documentation parts from the normal comments prefix the lines with documentation with a double hash '##' in column 1 and 2. Doing this extracting the documentation for a script can be done using the following code:


echo " ----------------------------------------------------------------------------------" >&2
echo "                         ${__SCRIPTNAME} ${__SCRIPT_VERSION} ">&2
echo "                                Documentation" >&2
echo " ----------------------------------------------------------------------------------" >&2

             grep "^##" "$0" | grep -v "##EXAMPLE##" | cut -c5- 1>&2
             die 0 ;;


This is implemented as action for the parameter "-H". Therefore a

./scriptt.sh -H 2>./scriptt.txt

just creates the documentation for the script.



Inline examples

Another documentation that is very useful for using scripts (especially if you want to use it years after you wrote it ...) are usage examples. For this purpose the script template contains code to extract the examples included in the script source using the parameter "-X":

echo " -----------------------------------------------------------------------------------------------------" >&2
echo "                         ${__SCRIPTNAME} ${__SCRIPT_VERSION} ">&2
echo "                          Documentation" - Examples>&2
echo " -----------------------------------------------------------------------------------------------------" >&2

             grep "^##EXAMPLE##" "$0" | cut -c12- 1>&2
             die 0 ;;


To use this feature add the examples to the script prefixed with '##EXAMPLE##', e.g.

##EXAMPLE## 
##EXAMPLE##  Mount the boot archive for the current platform
##EXAMPLE## 
##EXAMPLE##     ./view_bootarchive.sh
##EXAMPLE## 
##EXAMPLE##  Mount a specific boot archive
##EXAMPLE## 
##EXAMPLE##     ./view_bootarchive.sh mount /var/tmp/other_boot_archive
##EXAMPLE## 
##EXAMPLE##  Umount the boot archive for the current platform
##EXAMPLE## 
##EXAMPLE##     ./view_bootarchive.sh umount
##EXAMPLE## 

Than you can just issue a

./scriptt.sh -X

to get the usage examples.



Naming conventions


To distinguish between predefined global variables and script specific variables and ensure that I can replace the general routines in existing scripts with updated routines from the template if necessary, the names of all global variables are prefixed with a "__", e.g.

## __MUST_BE_ROOT (def.: false)
## set to ${__TRUE} for scripts that must be executed by root only
##
__MUST_BE_ROOT=${__FALSE}

## __REQUIRED_USERID (def.: none)
## required userid to run this script (other than root); use blanks to separate multiple userids
## e.g. "oracle dba sysdba"
## "" = no special userid required
##
__REQUIRED_USERID=""

Therefore you should not use variables beginning with "__" in scripts based on the script template.


Because it's not always clear in scripts if 0 is "okay" and 1 is "not okay" or the other way round the script template defines two constants for this purpose __TRUE and __FALSE. Using these constants instead of 0 or 1 makes the code more human readable.


To avoid problems with duplicate variable names all local variables of a function are defined as local variables using "typeset".

To avoid difficult to find errors all variables are used in the format ${varname}.

All parts of the script template that must be filled with "real" code are marked with three question marks "???".



Logging


Logging is essential for a script; especially if the script is called non-interactive like in cronjobs. One problem here is, that you might want to change the logfile name using a parameter for the script but the script already prints messages before the parameter are processed. The solution used for this problem in the script template is to create a temporary logfile direct after the script starts. Than, after the parameter are processed the temporary logfile is either copied to the "real" logfile or deleted - depending on the value for the logfile parameter.

And to make life easier the script template defines some additional routines to log different kind of messages:


LogMsg is the standard function to log a message to STDOUT and to the logfile (if defined). LogInfo, LogWarning, and LogError just add the particular marker string (INFO, WARNING, or ERROR) to the message and use LogMsg to print and log the message. LogOnly logs only to the logfile and not to STDOUT, PrintDotToSTDOUT prints a string (default one dot) to STDOUT without a carriage return if not in verbose mode, and LogIfNotVerbose prints a log message to STDOUT and the logfile but only if the script is called in non-verbose mode. LogInfo only prints messages if the script is called in verbose mode (parameter "-v").

Another useful function for logging is StartStop_LogAll_to_logfile :

The function StartStop_LogAll_to_logfile redirects all output to STDOUT and STDERR from the script and from all executables called by the script into a logfile.

Example usage:

# log all STDOUT and STDERR to the logfile /var/tmp/output.log
#
StartStop_LogAll_to_logfile start "/var/tmp/output.log"

# do what ever you want ...
ls

# to print something to STDOUT while the redirection is in place use the file handle 3
#
echo "This goes to STDOUT" >&3


# to print something to STDERR while the redirection is in place use the file handle 4
#
echo "This goes to STDERR" >&4

# to stop the redirection use
#
StartStop_LogAll_to_logfile stop


To change the command used to print the log messages to STDOUT you may set the variable LOGMSG_FUNCTION before calling the script. The default value of LOGMSG_FUNCTION is "echo".

Example: To use logger instead of echo for printing the log messages use:

LOGMSG_FUNCTION="logger -s -p user.info scriptt.sh"  ./scriptt.sh

The output to STDERR from logger then looks like:

xtrnaw7: scriptt.sh [22.04.2012 12:35:10] scriptt.sh v0.0.1 started on Sun Apr 22 12:35:10 CEST 2012 
xtrnaw7: scriptt.sh [22.04.2012 12:35:10] Reading the config file "/data/develop/scripts/scriptt.conf" ...
xtrnaw7: scriptt.sh [22.04.2012 12:35:10] Using the log file "/var/tmp/scriptt.LOG" 
xtrnaw7: scriptt.sh [22.04.2012 12:35:10] The log file used was "/var/tmp/scriptt.LOG" 
xtrnaw7: scriptt.sh [22.04.2012 12:35:10] scriptt.sh v0.0.1 ended on Sun Apr 22 12:35:10 CEST 2012.
xtrnaw7: scriptt.sh [22.04.2012 12:35:10] The RC is 0.

And in the OS log file you'll find these messages:

Apr 22 12:35:10 t61p xtrnaw7: scriptt.sh [22.04.2012 12:35:10] scriptt.sh v0.0.1 started on Sun Apr 22 12:35:10 CEST 2012 
Apr 22 12:35:10 t61p xtrnaw7: scriptt.sh [22.04.2012 12:35:10] Reading the config file "/data/develop/scripts/scriptt.conf" ...
Apr 22 12:35:10 t61p xtrnaw7: scriptt.sh [22.04.2012 12:35:10] Using the log file "/var/tmp/scriptt.LOG" 
Apr 22 12:35:10 t61p xtrnaw7: scriptt.sh [22.04.2012 12:35:10] The log file used was "/var/tmp/scriptt.LOG" 
Apr 22 12:35:10 t61p xtrnaw7: scriptt.sh [22.04.2012 12:35:10] scriptt.sh v0.0.1 ended on Sun Apr 22 12:35:10 CEST 2012.
Apr 22 12:35:10 t61p xtrnaw7: scriptt.sh [22.04.2012 12:35:10] The RC is 0.


There are also some variables to change the format and type of messages written by the script:

__NO_HEADERS - if this variable is set to ${__TRUE} the script does not print the start and end headers

__NO_TIME_STAMPS - if this variable is set to ${__TRUE} the messages are not prefixed with a time stamp.

__USE_COLORS - if this variable is set to ${__TRUE} the color variables contain the ANSI ESC sequences to change the color of the messages:

# -----------------------------------------------------------------------------
# color variables

#### Foreground Color variables:
#### __COLOR_FG_BLACK, __COLOR_FG_RED,     __COLOR_FG_GREEN, __COLOR_FG_YELLOW
#### __COLOR_FG_BLUE,  __COLOR_FG_MAGENTA, __COLOR_FG_CYAN,  __COLOR_FG_WHITE
####
#### Background Color variables:
#### __COLOR_BG_BLACK, __COLOR_BG_RED,     __COLOR_BG_GREEN, __COLOR_BG_YELLOW
#### __COLOR_BG_BLUE,  __COLOR_BG_MAGENTA, __COLOR_BG_CYAN,  __COLOR_BG_WHITE
####
if [ ${__USE_COLORS} = ${__TRUE} ] ; then
  __COLOR_FG_BLACK="\033[30m"
  __COLOR_FG_RED="\033[31m"
  __COLOR_FG_GREEN="\033[32m"
  __COLOR_FG_YELLOW="\033[33m"
  __COLOR_FG_BLUE="\033[34m"
  __COLOR_FG_MAGENTA="\033[35m"
  __COLOR_FG_CYAN="\033[36m"
  __COLOR_FG_WHITE="\033[37m"

  __COLOR_BG_BLACK="\033[40m"
  __COLOR_BG_RED="\033[41m"
  __COLOR_BG_GREEN="\033[42m"
  __COLOR_BG_YELLOW="\033[43m"
  __COLOR_BG_BLUE="\033[44m"
  __COLOR_BG_MAGENTA="\033[45m"
  __COLOR_BG_CYAN="\033[46m"
  __COLOR_BG_WHITE="\033[47m"

####
#### Colorattributes:
#### __COLOR_OFF, __COLOR_BOLD, __COLOR_NORMAL, - normal, __COLOR_UNDERLINE
#### __COLOR_BLINK, __COLOR_REVERSE, __COLOR_INVISIBLE
####

  __COLOR_BOLD="\033[1m"
  __COLOR_NORMAL="\033[2m"
  __COLOR_UNDERLINE="\033[4m"
  __COLOR_BLINK="\033[5m"
  __COLOR_REVERSE="\033[7m"
  __COLOR_INVISIBLE="\033[8m"
  __COLOR_OFF="\033[0;m"
fi

If __USE_COLORS is set to ${__FALSE} the color strings are all empty strings and therefore no ANSI ESC sequences are used.

The variables __INFO_PREFIX, __WARNING_PREFIX, __ERROR_PREFIX, and __RUNTIME_INFO_PREFIX can be used to change the marker for info messages, warnings, error messages, and runtime info messages.

And there are three variables to change the behaviour of the script regarding printing summaries for warning and error messages at script end:

__PRINT_LIST_OF_WARNINGS_MSGS - if this variable is set to ${__TRUE} the script prints a summary with  all warnings at program end.

__PRINT_LIST_OF_ERROR_MSGS - if this variable is set to ${__TRUE} the script prints a summary with all error messages at program end.

__PRINT_SUMMARIES is a the combined value of the two variables above.

This behaviour is configured with the parameter "-S" of the script:

     -S n  - print error/warning summaries:
              n = 0 no summariess, 1 = print error msgs,
              2 = print warning msgs, 3 = print error and warning mgs
              Current value: ${__PRINT_SUMMARIES}
              Long format: --summaries


To support multiple levels of info messages the script template implements a VERBOSE_LEVEL variable. This variable is increased each time the parameter "-v" is found. LogInfo can be called with an additional parameter with the requested verbose level for a message. In this case it compares the current VERBOSE_LEVEL with the verbose level requested for the message. LogInfo only prints the message if the current VERBOSE_LEVEL is higher than the level requested for the message:

## --------------------------------------
## LogInfo
##
## print a message to STDOUT and write it also to the logfile
## only if in verbose mode
##
## usage: LogInfo [loglevel] message
##
## returns: -
##
## Notes: Output goes to STDERR, default loglevel is 0
##
LogInfo() {
  typeset __FUNCTION="LogInfo"; ${__DEBUG_CODE}

  typeset THISLEVEL=0
 
  if [ ${__VERBOSE_MODE} -eq ${__TRUE} ] ; then
    if [ $# -gt 1 ] ; then
      isNumber $1
      if [ $? -eq ${__TRUE} ] ; then
        THISLEVEL=$1
        shift
      fi
    fi
    [ ${__VERBOSE_LEVEL} -gt ${THISLEVEL} ] && LogMsg "INFO: $*" >&2
  fi
}


for example:

LogInfo   "This message will only be printed if the parameter -v is entered one or more times"
LogInfo 1 "This message will only be printed if the parameter -v is entered two or more times"
LogInfo 2 "This message will only be printed if the parameter -v is entered three or more times"

This feature is used in the function LogRuntimeInfo:

## __RT_VERBOSE_LEVEL - level of -v for runtime messages
##
## e.g. 1 = -v -v is necessary to print info messages of the runtime system
##      2 = -v -v -v is necessary to print info messages of the runtime system
##
##
__RT_VERBOSE_LEVEL=1

# ....

LogRuntimeInfo() {
   typeset __FUNCTION="LogRuntimeInfo"; ${__DEBUG_CODE}
   LogInfo "${__RT_VERBOSE_LEVEL}" "$*"
}


All info messages of the "runtime" system (that is the predefined code in the template) are printed via LogRuntimeInfo and therefore the value of the variable __RT_VERBOSE_LEVEL decides how much times "-v" is necessary before the info messages of the runtime system are printed.


In addtion there are three functions to simplify the logging of binaries or other scripts called by the script :

ExecuteCommand does no logging at all; executeCommandAndLog logs STDOUT and STDERR to the logfile, and executeCommandAndLogSTDERR only logs STDERR to the logfile (useful if you need STDOUT of a command for other purposes).

All executeCommand* functions add the command to execute to the variable __SYSCMDS.  The house keeping routine running at script end will write the contents of the variable __SYSCMDS to the ${__SYSCMDS_FILE} if the variable __SYSCMDS_FILE is defined.



Runtime Initialisation


Befrore calling the main code the script template initializes the predefined variables and executes some common code:

Environment checks


Some scripts only run on specific hardware or software versions or they must be run by root. Therefore the script template implementes the following checks


The checks are only done if the appropriate variables are defined, these are:

## __MUST_BE_ROOT (def.: false)
##   set to ${__TRUE} for scripts that must be executed by root only
##
__MUST_BE_ROOT=${__FALSE}

## __REQUIRED_USERID (def.: none)
##   required userid to run this script (other than root); use blanks to separate multiple userids
##   e.g. "oracle dba sysdba"
##   "" = no special userid required
##
__REQUIRED_USERID=""

#### __REQUIRED_ZONES - required zones (either global, non-global or local
####    or the names of the valid zones)
####   (def.: none)
####   "" = no special zone required
####
__REQUIRED_ZONES="global"


## __ONLY_ONCE (def.: false)
##   set to ${__TRUE} for scripts that can not run more than one instance at the same time
##
__ONLY_ONCE=${__FALSE}

## __REQUIRED_OS_VERSION (def.: none)
##   minimum OS version necessary, e.g. 5.10
##   "" = no special version necessary
##
__REQUIRED_OS_VERSION=""


## __REQUIRED_MACHINE_PLATFORM (def.: none)
##   required machine platform (uname -i) , e.g "i86pc"; use blanks to separate
##   the machine types if more than one entry, e.g "Sun Fire 3800 i86pc"
##   "" = no special machine type necessary
##
__REQUIRED_MACHINE_PLATFORM=""


## __REQUIRED_MACHINE_CLASS (def.: none)
##   required machine class (uname -m) , e.g "i86pc" ; use blanks to separate
##   the machine types if more than one entry, e.g "sun4u i86pc"
##   "" = no special machine class necessary
##
__REQUIRED_MACHINE_CLASS=""


## __REQUIRED_MACHINE_ARC (def.: none)
##   required machine architecture (uname -p) , e.g "i386" ; use blanks to separate
##   the machine types if more than one entry, e.g "sparc i386"
##   "" = no special machine architecture necessary
##
__REQUIRED_MACHINE_ARC=""

# .....

  if [ ${__MUST_BE_ROOT} -eq ${__TRUE} ] ; then
    UserIsRoot || die 249 "You must be root to execute this script"
  fi

  if [ "${__REQUIRED_USERID}"x != ""x ] ; then
    pos " ${__USERID} " " ${__REQUIRED_USERID} " &&
      die 242 "This script can only be executed by one of the users: ${__REQUIRED_USERID}"
  fi
       
  if [ ${__ONLY_ONCE} -eq ${__TRUE} ] ; then
    CreateLockFile
    if [ $? -ne 0 ] ; then
      cat <<EOF

  ERROR:

  Either another instance of this script is already running
  or the last execution of this script crashes.
  In the first case wait until the other instance ends;
  in the second case delete the lock file
 
      ${__LOCKFILE}

  manually and restart the script.

EOF

#...


The script template also checks which shell is running the script and sets some variables depending on the type of the running shell:

__KSH_VERSION is either 88 if the shell is ksh88 compatible or 93 if the shell is ksh93 kompatible. Only one new feature of ksh93 is checked for this decision but that should be sufficient. Depending on the value of __KSH_VERSION the variable __USE_ONLY_KSH88_FEATURES is either set to ${__TRUE} for ksh88 compatible shells or ${__FALSE} for ksh93 compatible shells.

The variable __SHELL holds the name of the shell running this script, e.g. ksh, sh, bash, etc.


OS and shell dependent settings

The script template sets these OS or shell dependent settings for compatibility reasons:

OS
Shell
setting
comment
cygwin
all
set +o noclobber
all
bash
shopt -s expand_aliases

all
all
LANG=C

all
non ksh shell
the function whence is defined
only if there is no builtin function called whence





To add additional OS dependent settings search for "specific settings for the various operating systems and shells" in the source code; to add additional shell dependent settings serach for "specific settings for various shells".


Restrict a script to run only once


The code to ensure that not more than one instance of the script is running at the same time is a little bit tricky. Important here is to use a check that is known to be atomic. Currently I use code contributed by WPollock (see credits) for this purpose but the "old code" using "ln" is still available in the script and can be activated by setting the local variable __USE_OLD_CODE in the function CreateLockFile to ${__TRUE}:

# --------------------------------------
#### CreateLockFile
#
# Create the lock file (which is really a symbolic link if using the "old method") if possible
#
# usage: CreateLockFile
#
# returns: 0 - lock created
#          1 - lock already exist or error creating the lock
#
# Note: The old method uses a symbolic link because this is should always be a atomic operation
#
function CreateLockFile {
  typeset __FUNCTION="CreateLockFile";    ${__FUNCTION_INIT} ; ${__DEBUG_CODE}
 
# for compatibilty reasons the old code can still be activated if necessary
  typeset __USE_OLD_CODE=${__FALSE}
 
  typeset LN_RC=""

  LogRuntimeInfo "Trying to create the lock semaphore ..."
  if [ ${__USE_OLD_CODE} = ${__TRUE} ] ; then   
# old code using ln 
    ln -s  "$0" "${__LOCKFILE}" 2>/dev/null
    LN_RC=$?
  else   
    __INSIDE_CREATE_LOCKFILE=${__TRUE}
   
# improved code from wpollock (see credits)
    set -C  # or: set -o noclobber
    : > "${__LOCKFILE}" 2>/dev/null
    LN_RC=$?
    __INSIDE_CREATE_LOCKFILE=${__FALSE}
  fi
 
  if [ ${LN_RC} = 0 ] ; then
    __LOCKFILE_CREATED=${__TRUE}
    return 0
  else
    return 1
  fi
 
}



Using RBAC in Solaris 10


To enable RBAC control for the scripts these variables can be used:

# -----------------------------------------------------------------------------
#### __USE_RBAC - set this variable to ${__TRUE} to execute this script
####   with RBAC
####   default is ${__FALSE}
####
####   Note: You can also set this environment variable before starting the script
####
: ${__USE_RBAC:=${__FALSE}}

# -----------------------------------------------------------------------------
#### __RBAC_BINARY - pfexec binary
####  
####   default is /usr/bin/pfexec
####
####   Note: You can also set this environment variable before starting the script
####
: ${__RBAC_BINARY:=/usr/bin/pfexec}

...

# -----------------------------------------------------------------------------
#
# Set the variable ${__USE_RBAC} to ${__TRUE} to activate RBAC support
#
# Allow the use of RBAC to control who can access this script. Useful for
# administrators without root permissions
#
if [ "${__USE_RBAC}" = "${__TRUE}" ] ; then
  if [ "$_" != "${__RBAC_BINARY}" -a -x "${__RBAC_BINARY}" ]; then
    __USE_RBAC=${__FALSE} "${__RBAC_BINARY}" $0 $*
    exit $?
  else
    echo "${0%%*/} ERROR: \"${__RBAC_BINARY}\" not found or not executable!" >&2
    exit 238
  fi
fi


Now if you want to activate RBAC control for a script just set the variable __USE_RBAC to ${__TRUE}. Note that you can also add the automatic call via sudo instead of pfexec using this feature by changing the variable __RBAC_BINARY to the fully qualified path of the sudo binary.


Predefined variables


The script template initializes the IMHO most frequently used variables:

Variable
Value
Comment
__SCRIPTNAME
name of the script without the path

__SCRIPTDIR
path of the script (as entered by the user!)

__REAL_SCRIPTDIR
path of the script (real path, maybe a link)

__ABSOLUTE_SCRIPTDIR
path of the script (symbolic links resolved)

__HOSTNAME
hostname (uname -n)

__NODENAME
nodename (either /etc/nodename or hostname if the file does not exist)

__OS
Operating system (uname -s )

__OS_FULLNAME
Operating system name, in Solaris this is the name from the file /etc/release

__ZONENAME
name of the current zone if running under Solaris 10 or newer

__OS_RELEASE
Operating system release (uname -v)

__MACHINE_CLASS
Machine class (e.g. sun4u, uname -m)

__MACHINE_PLATFORM
Machine type (e.g. SUNW,Ultra-4, uname -i)

__MACHINE_SUBTYPE
Machine type (e.g. Sun Fire 3800)
This variable is always empty now because prtdiag is to slow on newer machines like the M5000
__MACHINE_ARC
Machine architecture (e.g. sparc, uname -p)

__START_DIR
working directory at start of the script

__LOGIN_USERID
ID of the user opening the session (who am I)

__USERID
ID of the user executing the script  (/usr/ucb/whoami)

__RUNLEVEL
current runlevel of the operating system

__DEF_LOGFILE
default logfile to use

__COLOR_FG_*
ANSI ESC sequences to change the foreground color
only set if __USE_COLOR is set to ${__TRUE}
__COLOR_BG_*
ANSI ESC sequences to change the background color
only set if __USE_COLOR is set to ${__TRUE}
__COLOR_*
ANSI ESC sequences to set the attributes bold, normal, underline, blink, reverse, and invisible
only set if __USE_COLOR is set to ${__TRUE}



__SHEBANG
The shebang line (= the first line) of the script

__SCRIPT_SHELL
the shell/binary used in the shebang line

__SCRIPT_SHELL_OPTIONS
the options used in the shebang line

__SHELL
the shell/binary currently executing the script












Using configuration files


The script template also supports a config file to be as much flexible as possible. The config file is searched and read (if found) at script start. The default name of the config file is

<scriptname_without_extension>.conf

E.g. the default config file name for scriptt.sh is scriptt.conf. The script template searches the config file in this order

  1. use the config file in the current directory if it exists
  2. use the config file in the home directory of the user running the script if it exists
  3. use the config file in /etc if it exists


Only one config file is read.


Because the config file is read before processing the parameter this syntax must be used to change the name of the config file :

CONFIG_FILE=<configfile_name> ./scriptt.sh

e.g.

xtrnaw7@t61p:/data/develop/scripts$ CONFIG_FILE=./test.conf ./scriptt.sh
[15.10.2011 11:08:44] scriptt.sh v0.0.1 started on Sat Oct 15 11:08:44 CEST 2011 
[15.10.2011 11:08:44] Reading the config file "./test.conf" ...
[15.10.2011 11:08:44] Using the log file "/var/tmp/scriptt.LOG" 
[15.10.2011 11:08:44] The log file used was "/var/tmp/scriptt.LOG" 
[15.10.2011 11:08:44] scriptt.sh v0.0.1 ended on Sat Oct 15 11:08:44 CEST 2011.
[15.10.2011 11:08:44] The RC is 0.


To disable the use of the config file use

CONFIG_FILE=none ./scriptt.sh

or set the variable __CONFIG_FILE in the script to "none".

To write a configuration file with the default values use

./scriptt.sh -C
 
e.g.:

[16.10.2011 10:59:02] scriptt.sh v0.0.1 started on Sun Oct 16 10:59:02 CEST 2011 
[16.10.2011 10:59:02] Using the log file "/var/tmp/scriptt.LOG" 
[16.10.2011 10:59:02] Creating the config file "scriptt.conf" ...
[16.10.2011 10:59:02] Creating a backup of "scriptt.conf" in "scriptt.conf.12057.backup" ...
[16.10.2011 10:59:02] Writing the config file "scriptt.conf" ...
[16.10.2011 10:59:03] Configfile "scriptt.conf" successfully written.
[16.10.2011 10:59:03] The log file used was "/var/tmp/scriptt.LOG" 
[16.10.2011 10:59:03] scriptt.sh v0.0.1 ended on Sun Oct 16 10:59:03 CEST 2011.
[16.10.2011 10:59:03] The RC is 0.

You can now edit the configuration file scriptt.conf to the values you need.

Implementation Details

I don't want to maintain duplicate code - one for the initialization of the variables in the script and one for the processing of the configuration file. Because of this the config file is implemented via the source-in functionality of ksh.

In detail:

The configuration variables are defined in the variable __CONFIG_PARAMETER:


## __CONFIG_PARAMETER
##   The variable __CONFIG_PARAMETER contains the configuraton variables
##
# The defaults for these variables are defined here. You
# can use a config file to overwrite the defaults.
#
# Use the parameter -C to create a default configuration file
#
# Note: The config file is read and interpreted via ". configfile" -> you can add also some code her!
#
_CONFIG_PARAMETER="__CONFIG_FILE_VERSION=\"${__SCRIPT_VERSION}\"
"'

# extension for backup files

DEFAULT_BACKUP_EXTENSION=".$$.backup"

# ??? example variables for the configuration file - change to your need

# master server with the directories to synchronize
# The rsync daemon must run either on this host or on localhost
# If the rsync daemon runs on localhost the master server must export
# the directories to synchronize using NFS. In this case the directories
# on the master server must be the same as on the rsync client
#
# overwritten by the parameter -m
  DEFAULT_MASTER_SERVER="linst2"

# server with the rsync daemon. This is either the master server or
# localhost
#
# overwritten by the parameter -s
  DEFAULT_RSYNC_SERVER="localhost"


# only change the following variables if you know what you are doing #

## sample debug code:
## __DEBUG_CODE=" eval echo Entering the subroutine \$__FUNCTION ... "

## Note: Use an include script for more complicate debug code, e.g.
## __DEBUG_CODE=" eval . /var/tmp/mydebugcode"
##

# no further internal variables defined yet
'
# end of config parameters



Because there is no evaluation of variable names in strings in single quotes (' ') you don't have to think about escaping the special characters here. And you can also add code to execute in a config file if necessary.


Now, to initialize the variables in the script a simple

eval "${__CONFIG_PARAMETER}"

is necessary.


And the code to read and execute a configuration file is

. "${THIS_CONFIG_FILE}"


To write a config file with default values the code is

cat <<EOT >"${THIS_CONFIG_FILE}"
# config file for ${__SCRIPTNAME} ${__SCRIPT_VERSION}, created $( date )
${__CONFIG_PARAMETER}
EOT
THISRC=$?


This functionality is used if the script is called with the parameter "-C".


That's it - very simple and straight forward.

For more indepth information please study the functions ReadConfigFile and WriteConfigFile in the source code.


Note:

To be able to distinguish between the default value and the current value of a variable all variables in the configuration and config file are prefixed with DEFAULT_.

After reading the default configuration and the config file (if it exists) all variables from the configuration are copied into the "real" variables for further processing using this code:

# to process all variables beginning with DEFAULT_ use
#
  for CURVAR in $( set | grep "^DEFAULT_"  | cut -f1 -d"=" ) ; do
    P1="${CURVAR%%=*}"
    P2="${P1#DEFAULT_*}"

# for debugging
#    push_and_set __VERBOSE_MODE ${__TRUE}
#    push_and_set __VERBOSE_LEVEL ${__RT_VERBOSE_LEVEL}
#    LogInfo 0 "Setting variable $P2= \"$( eval "echo \"\$$P1\"")\" "
#    pop __VERBOSE_MODE
#    pop __VERBOSE_LEVEL
       
    eval "$P2="\"\$$P1\"""
  
  done




Housekeeping using a trap handler


One thing a lot of scripts are missing is the house keeping at script end, e.g. delete temporary files, directories, etc.

House keeping is already implemented in the script template. For this purpose the script template discussed her defines five variables:


Variable
Value
Comment
__LIST_OF_TMP_MOUNTS
mount points that should be umounted at script end

__LIST_OF_TMP_DIRS
directories that should be removed at script end

__LIST_OF_TMP_FILES
files that should be removed at script end
this variable is initialized with the names of the default temporary files
__EXITROUTINES
functions that should be called at script end before the mounts, directories, and files are removed

__FINISHROUTINES
functions that should be called at script end after the mounts, directories, and files are removed


To use these variables for the house keeping simply add the new item to the list, e.g. to ensure that a temporary directory is always removed at script end use:

TMPDIR="/tmp/mydir.$$"
mkdir "${TMPDIR}"  && __LIST_OF_TMP_DIRS="${__LIST_OF_TMP_DIRS} ${TMPDIR}" || die 2 "Error creating the directory"



Please note that the variables for house keeping do not support file, directory or function names with white spaces (blanks, tabs). You should only append values to these variables and not overwrite them because they may also be used by the runtime system.

The variables for house keeping are evaluated and processed by the routine cleanup which is called in the function die.

The house keeping is done in this order:

  1. call the exit routines from ${__EXITROUTINES}
  2. remove the files from ${__LIST_OF_TMP_FILES}
  3. umount the mount points ${__LIST_OF_TMP_MOUNTS}
  4. remove the directories ${__LIST_OF_TMP_DIRS}
  5. call the finish routines from ${__FINISHROUTINES}


There are some additional variables to configure the house keeping behaviour (for example for debugging purpose):

Variable
takes care of
Default value
Comment
__NO_CLEANUP
general cleanup
${__FALSE}

__NO_EXIT_ROUTINES
exit routines
${__FALSE}
__NO_TEMPFILES_DELETE
delete temporary files
${__FALSE}
__NO_TEMPDIR_DELETE
delete temporary directories
${__FALSE}
__NO_TEMPMOUNTS_UMOUNT
umount temporary mount points
${__FALSE}
__NO_FINISH_ROUTINES
finish routines
${__FALSE}
__CLEANUP_ON_ERROR
do cleanup in case of an error
${__FALSE}


You may set one or more of the variables __NO_* variables at runtime to ${__TRUE} to suppress one or more of the house keeping actions. To disable or enable all house keeping actions at once you can use the function SetHousekeeping:


#### --------------------------------------
#### SetHousekeeping
####
#### do or do not house keeping (remove tmp files/directories; execute exit routines/finish routines) at script end
####
#### usage: SetHousekeeping [${__TRUE}|${__FALSE}]
####
#### parameter: ${__TRUE} - do house keeping
####            ${__FALSE} - no house keeping
####
#### returns:  0 - okay
####           1 - invalid usage
####
####


To use the house keeping feature it's necessary to exit the script always using the function die. To make sure that the function die is also called in case of an error, trap handlers are used:

# install trap handler
    trap "GENERAL_SIGNAL_HANDLER ERR   \$LINENO" ERR
    trap "GENERAL_SIGNAL_HANDLER  1    \$LINENO"  1
    trap "GENERAL_SIGNAL_HANDLER  2    \$LINENO"  2
    trap "GENERAL_SIGNAL_HANDLER  3    \$LINENO"  3
    trap "GENERAL_SIGNAL_HANDLER 15    \$LINENO" 15
    trap "GENERAL_SIGNAL_HANDLER exit  \$LINENO" EXIT



This must be done in every function also. Therefore there is a variable defined with the approviate code to install the trap handler

#### __FUNCTION_INIT - code executed at start of every sub routine
####   (see the hints for __DEBUG_CODE)
####         Default init code : install the trap handlers
####
#  __FUNCTION_INIT=" eval __settrap; echo  \"Now in function \${__FUNCTION}\" "
  __FUNCTION_INIT=" eval __settrap "


and it's important for this feature to work that every function starts with this code:


function ReadConfigFile {
  typeset __FUNCTION="ReadConfigFile"; ${__FUNCTION_INIT} ; ${__DEBUG_CODE}

....

and ends with this code:

  ${__FUNCTION_EXIT}
  return ${THISRC}


Note:

__DEBUG_CODE is another variable that can be used to define code that should be executed at start of every function,  __FUNCTION_EXIT can be used to define code that should be used at end of every funtion.


Note 1:

The most simple method to add your own function to the script is to copy the function YourRoutine and change it (replace YourRoutine with the name of your function and add the code to the body of the function):

### --------------------------------------
#### YourRoutine
####
#### template for a user defined function
####
#### usage: YourRoutine
####
#### returns:  ${__TRUE} - ok
####           ${__FALSE} - error
####           255 - invalid usage
####         
####
function YourRoutine {
  typeset __FUNCTION="YourRoutine";   ${__FUNCTION_INIT} ; 
  ${__DEBUG_CODE}
   
# init the return code
  typset THISRC=${__FALSE}


# add code here

  ${__FUNCTION_EXIT}
  return ${THISRC}

}

Be aware that the return code of a function must be in the range from 0 to 255. To "return" other data you must either use global variables or print the results to STDOUT and read that in the calling function.


Additional features of the trap handlers


The trap handler also implements the handling of CTRL-C via the variable __USER_BREAK_ALLOWED.
To suppress CTRL-C just set the variable __USER_BREAK_ALLOWED to ${__FALSE}.

The other predefined trap handlers are:

The signal SIGHUP will toggle the verbose switch, the signal handler for the signal SIGUSR1 writes a dump of the current environment (see below), and the signal SIGUSR2 calls an interactive shell (this is an experimental features).



User defined trap handler

The trap handler also supports user defined trap handler. The variables used for the user defined trap handlers are:

Variable
SIGNAL
default behaviour
comment
__GENERAL_SIGNAL_FUNCTION
(all)

If this signal handler returns 0 a signal specific signal handler is called if defined. If this handler returns a value not equal zero no other signal handler is called.
__SIGNAL_SIGUSR1_FUNCTION
USR1
write a dump of the current environment

__SIGNAL_SIGUSR2_FUNCTION USR2
call an interactive shell

__SIGNAL_SIGHUP_FUNCTION HUP
toggle the verbose switch

__SIGNAL_SIGINT_FUNCTION
INT
process user breaks (CTRL-C)

__SIGNAL_SIGQUIT_FUNCTION
QUIT


__SIGNAL_SIGTERM_FUNCTION
TERM



To imlement a user defined trap handler add the function for the trap handler and assign the name of the function to the approbiate variable.

E.g. to define a user defined trap handler for the signal SIGQUIT do:


# define the function for the trap signal, for example called my_sigquit_handler
#
# than set the variable for this trap handler

 __SIGNAL_SIGQUIT_FUNCTION="my_sigquit_handler"


That's it. Now your user defined trap handler is called if the signal SIGQUIT is received. Depending on the return code of the user defined trap handler the default trap handler for that trap will be called (returncode = 0) or not (returncode <> 0).


An example user defined trap handler is also included; you can use it as template for your own trap handler:


#### --------------------------------------
#### USER_SIGNAL_HANDLER
####
#### sample user defined trap handler
####
#### usage: __SIGNAL_<signal>_FUNCTION="USER_SIGNAL_HANDLER"
####
####        e.g. __SIGNAL_SIGUSR1_FUNCTION="USER_SIGNAL_HANDLER"
####
#### returns:  0 - execute the default action for this signal
####           else - do not execute the default action for this signal
####
####
function USER_SIGNAL_HANDLER {
  typeset THISRC=0
 
  LogMsg "***"
  LogMsg "User defined signal handler called"
  LogMsg ""
  LogMsg "Trap signal is \"${__TRAP_SIGNAL}\" "
  LogMsg "Interrupted function: \"${INTERRUPTED_FUNCTION}\", Line No: \"${__LINENO}\" "
  LogMsg "***"

  return ${THISRC}
}



Crash dumps

The script template supports some kind of "crash dump" for post mortem debugging. The "crash dump" is created in case of an error. It's also possible to manually create a "crash dump" at any time while the script is running.

The "crash dump" is really "only" a copy the current variables of the running process but in a lot of cases this is sufficient to find the bug in a script. To make this feature even more usefull you should save as much information as possible in variables. Example:

Instead of

ps -ef | grep "myprocess" >/dev/null
if [ $? -eq 0 ] ; then
 ...

use

PS_EF_OUTPUT="$( ps -ef )"
GREP_OUTPUT="$( echo "${PS_EF_OUTPUT}" | grep "myprocess" )"
if [ $? -eq 0 ] ; then

  ...


Using this code the output of the ps command and also the output of the grep command will be in the crash dump.


There are two files created for each crash dump:

<crashdumpdir>/<scriptname>.sh.envvars.$$
<crashdumpdir>/<scriptname>.exported_envvars.$$

e.g. the filenames for scriptt.sh are:

<crashdumpdir>/scriptt.sh.envvars.$$
<crashdumpdir>/scriptt.sh.exported_envvars.$$


Please note that the default signal handler for the signal USR1 will create a crash dump on request:

The default action for the signal handler USR1 is "Create an env dump in /var/tmp".  The filenames for the dumps are

/var/tmp/<scriptname>.envvars.dump_no_<no>_<PID>
/var/tmp/<scriptname>.exported_envvars.dump_no_<no>_<PID>

where <no> is a sequential number, <PID> is the PID of the process with the script,  and <scriptname> is the name of the script without the path.


The variables and functions to use the crash dump feature are

__CREATE_DUMP - if this variable contains the name of an existing directory a dump will be created at program end in that directory (even if the scripts ends without error). If __CREATE_DUMP is not empty but does not contain the name of an existing directory the dump will be created in the directory /tmp. If this variable is empty no automatic dump will be created at normal program end.

__DUMPDIR - this variable defines the location for the dump in case of an error.

__DUMP_ALREADY_CREATED - set this variable to 0  to suppress creating the dump in case of an error.


The function  CreateDump can be used in the script to manually create a crash dump.


Example usage oft the crash dump feature:

__CREATE_DUMP=1 ./scriptt.sh


will create a dump of the environment variables in the files

   /tmp/scriptt.sh.envvars.$$
   /tmp/scriptt.sh.exported_envvars.$$


before the script ends (with or without error).


__CREATE_DUMP=/var/tmp/debug ./scriptt.sh

will create a dump of the environment variables in the files

   /var/tmp/debug/scriptt.sh.envvars.$$
   /var/tmp/debug/scriptt.sh.exported_envvars.$$


before the script ends (the directory /var/tmp/debug must already exist).


To change the directory for the crash dumps that are created in case of an script error to /var/core use:

export __DUMPDIR=/var/core  ; ./scriptt.sh


In your script use

CreateDump <uniqdirectory> [filename_add]

to manually create a crash dump.

e.g.

CreateDump /var/debug

 will create the files

   /var/debug/scriptt.sh.envvars.$$
  /var/debug/scriptt.sh.exported_envvars.$$


This call
  
CreateDump /var/debug pass2.

will create the files

   /var/debug/scriptt.sh.envvars.pass2.$$
   /var/debug/scriptt.sh.exported_envvars.pass2.$$



Another function to save only some environment variables to a file is SaveEnvironmentVariables. Example usage:

# save all environment variables beginning with "GNOME"
#
SaveEnvironmentVariables "/var/tmp/envvars.out" "^GNOME"

# save all environment variables that contain MAN or SSH in their name:
#
SaveEnvironmentVariables "/var/tmp/envvars1.out" "MAN" "SSH"



The function SaveEnvironmentVariable uses egrep to select the environment variables to save.



Builtin tee functionality

The script template also implements an extended tee functionality: that is, all output written to STDOUT and STDERR by the script and all executables called by the script are automatically logged to a file (similar to calling the script with ./scriptt.sh 2>&1 | tee ./scriptt.output ).  To use this feature just call the script with the parameter "-T".

The default file used for this purpose is 

/var/tmp/${0##*/}.$$.tee.log}

e.g. for scriptt.sh this would be

/var/tmp/scriptt.sh.<pid>.tee.log

To change the file used for this feature use

__TEE_OUTPUT_FILE=<tee_output_file> ./scriptt.sh -T


Examples

xtrnaw7@t61p:/data/develop/scripts$ ./scriptt.sh -T
Saving STDOUT and STDERR to "/var/tmp/scriptt.sh.32090.tee.log" ...
[15.10.2011 10:23:52] scriptt.sh v0.0.1 started on Sat Oct 15 10:23:52 CEST 2011 
[15.10.2011 10:23:52] Reading the config file "/data/develop/scripts/scriptt.conf" ...
[15.10.2011 10:23:52] Using the log file "/var/tmp/scriptt.LOG" 
[15.10.2011 10:23:52] The log file used was "/var/tmp/scriptt.LOG" 
[15.10.2011 10:23:52] scriptt.sh v0.0.1 ended on Sat Oct 15 10:23:52 CEST 2011.
[15.10.2011 10:23:52] The RC is 0.
STDOUT and STDERR saved in "/var/tmp/scriptt.sh.32090.tee.log".


xtrnaw7@t61p:/data/develop/scripts$ __TEE_OUTPUT_FILE=/var/tmp/mylog.txt ./scriptt.sh -T
Saving STDOUT and STDERR to "/var/tmp/mylog.txt" ...
[15.10.2011 10:25:22] scriptt.sh v0.0.1 started on Sat Oct 15 10:25:22 CEST 2011 
[15.10.2011 10:25:22] Reading the config file "/data/develop/scripts/scriptt.conf" ...
[15.10.2011 10:25:22] Using the log file "/var/tmp/scriptt.LOG" 
[15.10.2011 10:25:22] The log file used was "/var/tmp/scriptt.LOG" 
[15.10.2011 10:25:22] scriptt.sh v0.0.1 ended on Sat Oct 15 10:25:22 CEST 2011.
[15.10.2011 10:25:22] The RC is 0.
STDOUT and STDERR saved in "/var/tmp/mylog.txt".




Include files


To make sure that all features of the script template also work for sourced-in files you should always use the function includeScript to source-in another file. Therefore use

includeScript scriptname

instead of

. scriptname

to include another script file.


Temporary files

A lot of scripts, if not all, need temporary files. Therefore the script template contains code to create temporary files that are automatically deleted at program end. In the default configuration the script template defines 2 temporary files. The variables used for the names of the temporay files are

__TEMPFILE1
__TEMPFILE2

So you can just use them without thinking of the house keeping for these files, e.g.

vxprint >"${__TEMPFILE1}"


The variables that control this functionality are:

__TEMPDIR - this variable contains the name of the directory for temporary files. It's initialized with the contents of the environment variable $TEMP or $TMP if set.

__NO_OF_TEMPFILES - this variable defines the number of temporary files that should be created automatically, the default is 2.

__TEMPFILE_MASK - this variable contains the umask for the temporary files




Running the script as daemon

To run as a daemon the script template also contains code to write a PID file with the PID of the current process running the script.  To use this feature add the name of the PID file to use to the variable __PIDFILE:

#### __PIDFILE - save the pid of the script in a file
####
#### example usage: __PIDFILE="/tmp/${__SCRIPTNAME%.*}.pid"
__PIDFILE=""

Note: If the script you're writing is allowed to run more than once at the same time you should use a unique filename for each run; something like

__PIDFILE="/var/run/${__SCRIPTNAME%.*}.$$.pid"




Predefined parameter


Some parameter are the same for all scripts and it make sense to define them in the script template. The standard ksh shell in Solaris supports long parameters (beginning with "--") so the script template also supports them.

The predefined parameter in this script template are:

Parameter

Long format

Meaning

-v

--verbose

turn verbose mode on
Use "-v -v" to get also the verbose messages from the runtime system

-q

--quiet

turn quiet mode on

-y

--yes

assume yes to all questions

-n

--no

assume no to all questions

-a

--color

use colors

-O

--overwrite

overwrite existing files

-f

--force

force the execution

-l logfile

--logfile

use "logfile" as logfile

-h

--help

show a short usage and exit immediately

-h -v

--help --verbose

show a long usage and exit immediately

-H

--doc

print the documentation to STDERR and exit immediately

-X
--view_examples
print the usage examples to STDERR and exit immediately

-D

--debug
used for additional debug parameter  - see below

Please note that the debugger functionality  from scriptt.sh v1.x is not implemented anymore in v2.x or later; use scriptt v1.x if you need it

-S n

--summaries

print error message and warning message summaries at script end, n can be
0 - print no summaries (default)
1 - print error message summaries
2 - print warning message summaries
3 - print error and warning message summaries

-C

--writeconfigfile

write a default config file and exit immediately

-V

--version

write the version number to STDOUT and exit immediately ; use "-v -V" to also print the scriptt template version used for the script

-T
--tee
append STDOUT and STDERR to a file


The parameters "-v", "-q", "-y", "-n", "-a", "-O", and "-f" can also used with a plus sign, e.g. "+v", "+q", to turn off the feature. This applies also to the parameter in long format, e.g. "++verbose" instead of "--verbose" to turn of verbose mode.

The parameter -o sets the variable __OVERWRITE_MODE. This variable is checked by the function BackupFileIfNecessary. So before changing an existing file just call

BackupFileIfNecessary filename

and be sure there will be a backup of the file you're going to change if requested (via parameter "-o" / "+o" ).

The parameter are processed sequential in the order entered, e.g.

./scriptt.sh -v -q -v

In this example verbose is turned on because the parameter "-v" is the last parameter.


To add further parameter do the following tasks:

- add the parameter to the variable __SHORT_USAGE_HELP

- add the parameter description to the variable __LONG_USAGE_HELP

- add the parameter to the parameter string for getopts __GET_OPTS. Note: There are two definitions for __GET_OPTS: one for shells that support long parameter and one for shells that do not support long parameter:

  __GETOPTS="+:ynvqhHDfl:aOS:CVTX"
  if [ "${__OS}"x = "SunOS"x -a "${__SHELL}"x = "ksh"x ] ; then
    if [ "${__OS_VERSION}"x  = "5.10"x -o  "${__OS_VERSION}"x  = "5.11"x ] ; then
      __GETOPTS="+:y(yes)n(no)v(verbose)q(quiet)h(help)H(doc)D(debug)f(force)l:(logfile)a(color)O(overwrite)S:(summaries)C(writeconfigfile)V(version)T(tee)X(view_examples)"
    fi
  fi

- add the code to process the parameter in the "while getopts .." loop:

while getopts ${__GETOPTS} CUR_SWITCH  ; do
...
    case ${CUR_SWITCH} in
...
      "S" ) case ${OPTARG} in

                0 | 1 | 2 | 3 ) __PRINT_SUMMARIES=${OPTARG}
                                    ;;

                * )  LogError "Unknown value for -S found: \"${OPTARG}\""
                      INVALID_PARAMETER_FOUND=${__TRUE}
                      ;;
                esac
                ;;


# ??? add additional parameter here
    
        \? ) LogError "Unknown parameter found: \"${OPTARG}\" "
             INVALID_PARAMETER_FOUND=${__TRUE}
             break
          ;;
...
    esac
  done

The remaining parameter after evaluating the default parameter are saved in the variable NOT_PROCESSED_PARAMETER.



Debugging


The script supports the parameter  "-D extended_option" to add other not so often used parameter to the script, e.g -D msg  or -D debugcode="x".

To get alist of the currently implemented extended options use ./scriptt.sh -D help, e.g.


[root@t540p xtrnaw7]# /data/develop/scripts/scriptt.sh -v -V
[16.08.2015 10:53:47] scriptt.sh v1.0.0 started at Sun Aug 16 10:53:47 CEST 2015.
[16.08.2015 10:53:47] No config file ("scriptt.conf") found (use -C to create a default config file)
[16.08.2015 10:53:47] Script version: v1.0.0
[16.08.2015 10:53:47] Script template version: 2.1.0.7 25.07.2015
[16.08.2015 10:53:47] The log file used was "/tmp/scriptt.sh.6949.TEMP" 
[16.08.2015 10:53:47] scriptt.sh v1.0.0 started at Sun Aug 16 10:53:47 CEST 2015 and ended at Sun Aug 16 10:53:47 CEST 2015.
[16.08.2015 10:53:47] The time used for the script is 0 minutes and 0 seconds.
[16.08.2015 10:53:47] The RC is 0.
[root@t540p xtrnaw7]# /data/develop/scripts/scriptt.sh -D help
[16.08.2015 11:29:13] scriptt.sh v1.0.0 started at Sun Aug 16 11:29:13 CEST 2015.
[16.08.2015 11:29:13] No config file ("scriptt.conf") found (use -C to create a default config file)
Known debug switches (for -D / --debug):

  help          -- show this usage and exit
  create_documentation
                -- create the script documentation
  list_rc       -- list return codes used by this script
                   Works only if you only use "die" to end the script
  msg           -- log debug messages to the file /tmp/scriptt.sh.7718.debug
                   This parameter should be the first parameter.
  trace         -- activate tracing to the file /tmp/scriptt.sh.7718.trace
  fn_to_stderr  -- print the function names to STDERR
  fn_to_tty     -- print the function names to /dev/tty
  fn_to_handle9 -- print the function names to the file handle 9
  fn_to_device=filename
                -- print the function names to the file "filename"
  debugcode="x" -- execute the debug code "x" at every function start
  tracefunc=f1[,...,f#]
                -- enable tracing for the functions f1 to f#
                   Note: Use either debugcode=x or tracefunc=f1 - but NOT both
  debug         -- start debug env
  setvar:name=value
                -- set the variable "name" to "value"
  listfunc      -- list all functions defined and exit
  create_dump=d -- enable environment dumps; target directory is d
  SyntaxHelp    -- print syntax usage examples for the functions in the template
                   and exit
  dryrun        -- dry run only, do not execute commands
  dryrun=prefix -- dry run only, add the prefix "prefix" to all commands
[16.08.2015 11:29:13] The log file used was "/tmp/scriptt.sh.7718.TEMP" 
[16.08.2015 11:29:13] scriptt.sh v1.0.0 started at Sun Aug 16 11:29:13 CEST 2015 and ended at Sun Aug 16 11:29:13 CEST 2015.
[16.08.2015 11:29:13] The time used for the script is 0 minutes and 0 seconds.
[16.08.2015 11:29:13] The RC is 0.
[root@t540p xtrnaw7]#


The predefined extended options are:

Option
Usage
help
print the list of known extended options
create_documentation
creates the documentation for the script
list_rc
list return codes used by the script and exit
Note: To use this feature for your code always use the function die to end the script
msg
log debug messages to a log file, to use this feature use the function LogDebugMsg in your code
trace
activate tracing (set -x ) for the script, the trace message are written to a separate log file, e.g.

[root@t540p xtrnaw7]# /data/develop/scripts/scriptt.sh -D trace
[16.08.2015 11:35:45] scriptt.sh v1.0.0 started at Sun Aug 16 11:35:45 CEST 2015.
[16.08.2015 11:35:45] No config file ("scriptt.conf") found (use -C to create a default config file)
[16.08.2015 11:35:45] Using the log file "/var/tmp/scriptt.LOG" 
[16.08.2015 11:35:45] The log file used was "/var/tmp/scriptt.LOG" 
[16.08.2015 11:35:45] The trace messages are logged to "/tmp/scriptt.sh.8001.trace" 
[16.08.2015 11:35:45] scriptt.sh v1.0.0 started at Sun Aug 16 11:35:45 CEST 2015 and ended at Sun Aug 16 11:35:45 CEST 2015.
[16.08.2015 11:35:45] The time used for the script is 0 minutes and 0 seconds.
[16.08.2015 11:35:45] The RC is 0.


fn_to_stderr
print the name of every function executed to STDERR
fn_to_tty
print the name of every function executed to /dev/tty
fn_to_handle9
print the name of every function executed to the file handle 9; to use this feature call the script like this:

[root@t540p xtrnaw7]# /data/develop/scripts/scriptt.sh -D fn_to_handle9 9>/tmp/file9
[16.08.2015 11:38:10] scriptt.sh v1.0.0 started at Sun Aug 16 11:38:10 CEST 2015.
[16.08.2015 11:38:10] No config file ("scriptt.conf") found (use -C to create a default config file)
[16.08.2015 11:38:10] Using the log file "/var/tmp/scriptt.LOG" 
[16.08.2015 11:38:10] The log file used was "/var/tmp/scriptt.LOG" 
[16.08.2015 11:38:10] scriptt.sh v1.0.0 started at Sun Aug 16 11:38:10 CEST 2015 and ended at Sun Aug 16 11:38:10 CEST 2015.
[16.08.2015 11:38:10] The time used for the script is 0 minutes and 0 seconds.
[16.08.2015 11:38:10] The RC is 0.



fn_to_device=filename
print the name of every function executed to the file "filename"
debugcode="x"
execute the code "x" at every function start

Note: It's a little bit tricky to use variables in the code for "x"
tracefunc=f1[...,f#]
enable tracing (set -x) for one or more functions
debug
start a debug shell
setvar:name=value
set the variable "name" to the value "value", e.g. to temporary disable the cleanup at script end you can use:

./scriptt.sh -D setvar:__NO_CLEANUP=0

listfunc
list all functions defined in the script and exit
create_dump=d
enable environment dumps to the target directory "d"
SyntaxHelp
print some usage help for using the features of the script template
dryrun
dryrun=<newcode>
do a dry run; only print the commands to execute to STDOUT
To use this feature in your code prefix every call of a binary or script with the variable ${PREFIX}; the default value for PREFIX is "echo ";
to change the value for PREFIX use "dryun=<newcode>"









Adding other extended options


To add another extended option do


- add the usage help for the new extended options to the help text in the function ProcessDebugSwitch


- add another statement to the case in the function ProcessDebugSwitch; for simple debug options like "-D myaction" use

    <myaction>  )
        DEBUG_PARAMETER_OKAY=${__TRUE}
        # your code for this action
         ;;


  for debug options like "-D keyword=value" use


     <keyword>=* )
        DEBUG_PARAMETER_OKAY=${__TRUE}
        <value>="${CUR_DEBUG_SWITCH#*=}"
       ;;


  Note: The code DEBUG_PARAMETER_OKAY=${__TRUE} is mandatory for every new statement in the case structure.




Environment variables


The script template uses the environment variables from the table below if defined before running the script. Some of the variables can also be set with a parameter. In this case the value from the parameter overwrites the value from the environment variable.


Environment variable
parameter
comment
  LOGMSG_FUNCTION

   __DEBUG_CODE

  __RT_VERBOSE_LEVEL

  __QUIET_MODE -q

  __VERBOSE_MODE -v

  __VERBOSE_LEVEL

  __OVERWRITE_MODE -O

  __USER_BREAK_ALLOWED

  __NO_TIME_STAMPS

  __NO_HEADERS

  __USE_COLORS -a

  __USE_RBAC

  __RBAC_BINARY

  __TEE_OUTPUT_FILE

  __INFO_PREFIX

  __WARNING_PREFIX

  __ERROR_PREFIX

  __RUNTIME_INFO_PREFIX

  __NO_CLEANUP

  __NO_EXIT_ROUTINES

  __NO_TEMPFILES_DELETE

  __NO_TEMPMOUNTS_UMOUNT

  __NO_TEMPDIR_DELETE

  __NO_FINISH_ROUTINES

  __CLEANUP_ON_ERROR

  __CREATE_DUMP

  __DUMP_ALREADY_CREATED

  __DUMPDIR

  __USE_ONLY_KSH88_FEATURES

CONFIG_FILE

TMP / TEMP

default for __TEMPDIR if set (in this order)


Return codes

The script template uses the return codes 1, 2, and 210 to 254. You should not use these return codes in your code.

The list of predefined return codes for the script template is:

Predefined return codes:

   RC Error Message
    0 "Configfile "${NEW_CONFIG_FILE}" successfully written."
    0 ok, no error
    1 show usage and exit
    2 invalid parameter found
   10 "Error creating the directory "${TMPDIR}" "
   10 "Error creating the file "${TMPFILE}" "
  229 Script aborted by the user
  230  "${SCRIPTFILE} does not exist or is not readable"
  231 "Can not write to the file "${CUR_VAR}""
  232 "Function SyntaxHelp NOT defined."
  233 "Can not write to file handle 9"
  234 "The return value ${THISRC} is greater than 255 in function "${__FUNCTION}""
  234 "The return value is greater than 255 in function "${__FUNCTION}""
  235 "Invalid debug switch found: "${CUR_DEBUG_SWITCH}" -- use "-d help" to list the known debug switches"
  236 "You should use the function "die" to end the program"
  237 "Can not write to the debug log file "${__DEBUG_LOGFILE}" "
  238 "This script can not run on this operating system (${__OS}); known Operating systems are "${__REQUIRED_OS}""
  239 "This script can not run in the global zone"
  239 "This script must run in one of the zones "${__REQUIRED_ZONES}"; the current zone is "${__ZONENAME}" "
  239 "This script must run in the global zone; the current zone is "${__ZONENAME}""
  242 "This script can only be executed by one of the users: ${__REQUIRED_USERID}"
  243 "This script can not run on this machine architecture (${__MACHINE_ARC}); necessary machine architectures are "${__REQUIRED_MACHINE_ARC}""
  244 "This script can not run on this machine class (${__MACHINE_CLASS}); necessary machine classes are "${__REQUIRED_MACHINE_CLASS}""
  245 "This script can not run on this platform (${__MACHINE_PLATFORM}); necessary platforms are "${__REQUIRED_MACHINE_PLATFORM}""
  246 "Error writing the config file "${NEW_CONFIG_FILE}""
  247 "Include script "$1" not found"
  248 "Unsupported OS Version: ${__OS_VERSION}; necessary OS version is ${__REQUIRED_OS_VERSION}"
  249 "You must be root to execute this script"
  250 Script is already running
  251 "QUIT signal received"
  252 "Script aborted by the user via signal BREAK (CTRL-C)"
  253 "Script aborted by the external signal SIGTERM"
  254 "Unknown signal caught: ${__TRAP_SIGNAL}"

 



Functions defined in the script template



String handling functions


Coming from a more string oriented language like REXX one thing that is really missing in ksh are string manipulating functions. Therefore I wrote some functions for doing this. The functions use internal ksh functions (like pattern matching, typeset, etc) as much as possible to avoid the use of external binaries like sed, or awk.

All function returning a string either return the value in a variable or print it to STDOUT; this is done with the code:

  if [ "$4"x != ""x ] ; then
    eval $4=\"${resultstr}\"
  else
    echo "${resultstr}"
  fi


The string handling functions defined are:



Data convertion functions


Also missing in standard ksh scripts are functions for converting data. The functions defined for this purpose in the script template are:



UID related functions

Other often used functions are those to process UID and usernames:



Functions to implement a FIFO Stack

There are also functions to implement a simple LIFO stack. This is a very handy feature to temporary save the contents of variables.

The functions for the stack handling are:


Example usage of these functions:

# for debugging
    push_and_set __VERBOSE_MODE ${__TRUE}
    push_and_set __VERBOSE_LEVEL ${__RT_VERBOSE_LEVEL}
    LogInfo 0 "Setting variable $P2= \"$( eval "echo \"\$$P1\"")\" "
    pop __VERBOSE_LEVEL
    pop __VERBOSE_MODE




Misc functions


There are other functions defined in the script template that may or may not be useful. Use

./scriptt.sh -H 2>./scriptt.txt

to create the documentation which lists also all implemented functions.

Other functions defined in this version of the script template:



Function templates


There are some function templates in the script template which should you use for your own functions to make sure all template features also work for your new functions:

YourRoutine - use this function as template for your function(s)

USER_SIGNAL_HANDLER - use this function as template for a user defined trap handler



Links


The source code of the script template: http://bnsmb.de/files/public/solaris/templates/scriptt.sh


The source code of the 1.x version of scriptt.sh is here:  http://bnsmb.de/files/public/solaris/templates/scriptt.sh.1.x

A test suite for the script template: http://bnsmb.de/files/public/solaris/templates/scriptt_testsuite.sh

Output of scriptt.sh -Hhttp://bnsmb.de/files/public/solaris/templates/scriptt.txt



Scripts based on scriptt.sh


view_bootarchive.sh

dtrace_syscalls

create_zones.sh

execute_on_all_hosts.sh



Back to top