A
Script template for ksh scripts
Some useful techniques for ksh scripts
Bernd
Schemmer, 03 Dezember
2017
I'm pretty sure that every System Administrator who is responsible for more than a few machines running Solaris, Linux, AIX or another Unix operating system has her own bag of scripts for maintaining the machines. Nevertheless the script template and the programming techniques discussed 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.3.0.0, 10.11.2017.
Note that the script was developed mainly for the kornshell (this is a
ksh88 shell) 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
There is now a minimal version of scriptt.sh available: scriptt_mini.sh. This is a much smaller shell script template with only the most important functionality for shell scripts.
Date |
Release |
Comment |
03.12.2017 | Updated documentation for scriptt.sh v2.3.0.0 | |
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 |
|
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:
The code in the function executeCommandAndLog
is based on code on this webpage:
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.
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:
Please note that the main function where you should add your code is at the bottom of the script
There are some marker defined in the source code that you can use with the search function of your editor while editing the script template :
#
---------------------------------------------------------------------
# **** Note: The main code starts after the line containing "#
main:" ****
#
The main code for your script should start after "# main - your code"
#
# Additional jump targets:
#
#
#main main code
#
#mit init
main function
#
#svd set
variable defaults
#
#uds user
defined sub routines
#
#auh add.
usage help
#
#udf user
defined functions
#
#udp user
defined parameter
#
Some of the comment lines in the script use a specific format, e.g.
##EXAMPLE#
####
##C#
##T#
These are used to for the inline documentation and should not be changed.
To create the documentation for the current version of the script template use the parameter "-D create_documentation" ; this will create all files with the inline documentation, e.g.
[xtrnaw7@t540p /data/develop/scripts]$ ./scriptt.sh -D
create_documentation
[03.12.2017 12:33:31] scriptt.sh v1.0.0 started at Sun Dec 3
12:33:31 CET 2017.
[03.12.2017 12:33:31] Reading the config file
"/data/develop/scripts/scriptt.conf" ...
[03.12.2017 12:33:31] DEBUG: Writing the long usage documentation to scriptt.sh.long_usage.txt ...
[03.12.2017 12:33:31] DEBUG: Writing the debug switch documentation to scriptt.sh.debug_switches.txt ...
[03.12.2017 12:33:31] DEBUG: Writing the script documentation to scriptt.sh.txt ...
[03.12.2017 12:33:32] DEBUG: Writing the script usage examples to scriptt.sh.usage_examples.txt ...
[03.12.2017 12:33:32] DEBUG: Writing the function usage examples to scriptt.sh.function_examples.txt ...
[03.12.2017 12:33:32] The log file used was
"/tmp/scriptt.sh.8962.TEMP"
[03.12.2017 12:33:32] The debug messages are logged to
"/tmp/scriptt.sh.8962.debug"
[03.12.2017 12:33:32] scriptt.sh v1.0.0 started at Sun Dec 3
12:33:31 CET 2017 and ended at Sun Dec 3 12:33:32 CET 2017.
[03.12.2017 12:33:32] The time used for the script is 0 minutes and 1
seconds.
[03.12.2017 12:33:32] The RC is 0.
[xtrnaw7@t540p /data/develop/scripts]$
This section describes some of the techniques used in the script template.
Note again 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.
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.
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.
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 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
LogOnly
LogInfo
LogWarning
LogError
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
executeCommandAndLog
executeCommandAndLogSTDERR
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 ${__SYS_CMDS} to the ${__SYSCMDS_FILE} if the
variable ${__SYSCMDS_FILE}
is defined.
The script template checks if the script is running in a session with a tty at startup and sets the variable ${__RUNNING_IN_TERMINAL_SESSION} to ${__FALSE} if not.
If the script is running in a session without an tty and the parameter "-q" was not used all output of the script and all executables used by the script will be redirected to the file
/var/tmp/${0##*/}.STDOUT_STDERRR.$$
This is implemented with this code:
#
----------------------------------------------------------------------
#
# redirect STDOUT and STDERR of the script and all commands executed by
# the script to a file if called in an session without tty
#
if ! tty -s ; then
__RUNNING_IN_TERMINAL_SESSION=${__FALSE}
if [[ " $* " != *\ --quiet\ * && " $* " != *\ -q\ *
]] ; then
__STDOUT_FILE="/var/tmp/${0##*/}.STDOUT_STDERRR.$$"
echo "${SCRIPTNAME} -- Running in a detached session
... STDOUT/STDERR will be in ${__STDOUT_FILE}" >&2
exec 3>&1
exec 4>&2
exec 1>>"${__STDOUT_FILE}" 2>&1
fi
else
__RUNNING_IN_TERMINAL_SESSION=${__TRUE}
fi
e.g.
# first execute the script in a session with tty
[xtrnaw7@t540p
/data/develop/scripts]$ ./scriptt.sh
[03.12.2017 11:47:10] scriptt.sh v1.0.0 started at Sun Dec 3
11:47:10 CET 2017.
[03.12.2017 11:47:10] Reading the config file
"/data/develop/scripts/scriptt.conf" ...
[03.12.2017 11:47:10] Using the log file "/var/tmp/scriptt.log"
[03.12.2017 11:47:10] This is only sample code!!!
[03.12.2017 11:47:10] __RUNNING_IN_TERMINAL_SESSION is 0
[03.12.2017 11:47:10] Executing an OS command that writes to STDERR ...
ls: cannot access '/dasdfsf': No such file or directory
This is a messageon STDOUT
This is a messageon STDERR
[03.12.2017 11:47:10] The log file used was "/var/tmp/scriptt.log"
[03.12.2017 11:47:10] scriptt.sh v1.0.0 started at Sun Dec 3
11:47:10 CET 2017 and ended at Sun Dec 3 11:47:10 CET 2017.
[03.12.2017 11:47:10] The time used for the script is 0 minutes and 0
seconds.
[03.12.2017 11:47:10] The RC is 0.
[xtrnaw7@t540p /data/develop/scripts]$
# and this is the same script executed in a session without tty
[xtrnaw7@t540p
/data/develop/scripts]$ ./scriptt.sh </dev/null
-- Running in a detached session ... STDOUT/STDERR will be in /var/tmp/scriptt.sh.STDOUT_STDERRR.7096
[xtrnaw7@t540p /data/develop/scripts]$ cat
/var/tmp/scriptt.sh.STDOUT_STDERRR.7096
[03.12.2017 11:47:31] scriptt.sh v1.0.0 started at Sun Dec 3
11:47:31 CET 2017.
[03.12.2017 11:47:31] Reading the config file
"/data/develop/scripts/scriptt.conf" ...
[03.12.2017 11:47:31] Using the log file "/var/tmp/scriptt.log"
[03.12.2017 11:47:31] This is only sample code!!!
[03.12.2017 11:47:31] __RUNNING_IN_TERMINAL_SESSION is 1
[03.12.2017 11:47:31] Executing an OS command that writes to STDERR ...
ls: cannot access '/dasdfsf': No such file or directory
This is a messageon STDOUT
This is a messageon STDERR
[03.12.2017 11:47:31] The log file used was "/var/tmp/scriptt.log"
[03.12.2017 11:47:31] scriptt.sh v1.0.0 started at Sun Dec 3
11:47:31 CET 2017 and ended at Sun Dec 3 11:47:31 CET 2017.
[03.12.2017 11:47:31] The time used for the script is 0 minutes and 0
seconds.
[03.12.2017 11:47:31] The RC is 0.
[xtrnaw7@t540p /data/develop/scripts]$
Another method to log all output of the script to STDOUT or STDERR is used if the parameter -T (or --tee) is used -- see the secion about the builtin tee functionality below
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".
Before calling the main code the script template initializes the
predefined variables and executes some common code:
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
Is another instance of the script already running?
Is the user executing the script root?
Is the user executing the script user xyz?
Is the Solaris version running Solarix x.y or newer (only tested in Solaris)?
Is the script running on the correct type of hardware (platform, class, cpu)?
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
compatible. 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.
The variable ${__TRACE_ACTIVE} is set to ${__TRUE} if the script is called with tracing enabled (ksh -x ...)
And the variable ${__SETOPTS} contains all current ksh 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 |
LANG=C will only be used to
initialize the runtime systems; before executing the main code the
variable LANG will be restored with the original value |
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".
To ensure that the script is only running in one session at a time set the variable ${__ONLY_ONCE} to ${__TRUE}.
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
}
RBAC is a Solaris 10 feature to configure the access rights of a user or program - in principle somehow like "sudo" but with a much better granuality.
To enable RBAC control for the script 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.
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 |
|
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
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 at runtime :
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.
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
One thing a lot of scripts are missing is the house keeping at script end,
e.g. delete temporary files, directories, etc.
Therefor house keeping is already implemented in the script template. For this purpose the script template discussed her defines these 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 |
use the format exitroutine:parameter1:parameter2[..:parameter#] to use parameter for an exit routine. The function can check the variable ${__INSIDE_EXIT_ROUTINE} to check if it's running as exit routine if [ ${__INSIDE_EXIT_ROUTINE} = ${__TRUE} ] ; then |
${__FINISHROUTINES} |
functions that should be called at script end after the mounts, directories, and files are removed | use the format finishroutine:parameter1:parameter2[..:parameter#] to use parameter for a finish routine. The function can check the variable ${__INSIDE_FINISH_ROUTINE} to check if it's running as finish routine if [ ${__INSIDE_FINISH_ROUTINE} = ${__TRUE} ] ; then |
${__PROCS_TO_KILL} | processes that should be killed at script end | the format for the entries in this list is: pid[:timeout] pid is the PID of the process to kill; timeout is the time in seconds to wait for the process to stop until a "kill -9" is issued. Use "pid:-1" to disable the "kill -9" for a process. The default value for timeout for all processes is ${__PROCS_KILL_TIMEOUT}. |
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:
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_KILL_PROCS} | kill processes | ${__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}
${__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 function.
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.
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 the
DebugShell.
The trap handler also supports user defined trap handler. The
variables must contain the function name for the 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 the DebugShell |
|
${__SIGNAL_SIGHUP_FUNCTION} | HUP |
toggle the verbose switch |
|
${__SIGNAL_SIGINT_FUNCTION} |
INT |
process user breaks (CTRL-C) |
in the default the user trap handler
for CTRL-C calls the function DebugShell if enabled |
${__SIGNAL_SIGQUIT_FUNCTION} |
QUIT |
||
${__SIGNAL_SIGTERM_FUNCTION} |
TERM |
To implement 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).
If you use one function for more then one trap handler you can check the
variable ${__TRAP_SIGNAL} to get the current trap.
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}
}
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:
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.$$
To make sure that all features of the script template also work for
sourced-in files you should always use the function includeScript
or tryIncludeScript to source-in another file. Therefore
use
includeScript scriptname
or
tryIncludeScript scriptname
instead of
. scriptname
to include another script file.
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"
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 |
-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 |
-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 be 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}.
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 a list of the currently implemented extended options use ./scriptt.sh
-D help, e.g.
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. |
tracemain | activate tracing for the main function only |
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" |
printargs | print the arguments of the script while processing them |
tracefunc=f1[...,f#] |
enable tracing (set -x) for one or
more functions |
debug[=cmd] |
exeucte the command "cmd" or start a
very simple debug shell |
DebugShell | call the DebugShell |
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=dirname |
enable environment dumps to the
target directory "dirname" |
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 "dryrun=<newcode>" |
nocleanup |
disable the house keeping at script
end |
cleanup[=type] |
disable or enable the house keeping
at script end; "type" can be all - enable all house keeping (this is the default) none - disable all house keeping nodelete - disable only the removing of temporary files and directories |
Environment
variable |
script 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} | ||
${_DEBUG_PREFIX} | ||
${__INFO_PREFIX} | ||
${__WARNING_PREFIX} | ||
${__ERROR_PREFIX} | ||
${__RUNTIME_INFO_PREFIX} | ||
${__NO_CLEANUP} | ||
${__NO_KILL_PROCS} | ||
${__PROCS_KILL_TIMEOUT} | ||
${__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) |
The function AskUser can be used to get some input from the user, the usage is
AskUser "message"
AskUser returns ${__TRUE} if the user input is yes and ${__FALSE} if the user input is no. The string entered by the user is available in the variable ${USER_INPUT}. The user input from the previous call to AskUser is available in the variable ${LAST_USER_INPUT}.
If the parameter -y was used AskUser will always return ${__TRUE} and never prompt for user input. If the parameter -n was used AskUser will always return ${__FALSE} and never prompts for user input.
There are some variables defined for the behaviour of AskUser:
Variable |
Description |
Comment |
${__DEBUG_SHELL_IN_ASKUSER} | if this variable is true the user can call the DebugShell in AskUser | |
${__NOECHO} | turn echo off while reading input from the user (e.g. to enter a password) | |
${__USE_TTY} | if this variable is true all output in AskUser goes to /dev/tty and all input is read from /dev/tty |
AskUser uses the standard read command so the input must be followed by <RETURN>. To only read one key from the user use the function GetKeystroke instead.
The function BackupFileIfNecessary can be used to create a backup of an existing file, the usage is
BackupFileIfNecessary [file1} ... {filen}
The format for the parameter is
filename[,no_of_backups]
BackupFileIfNecessary will create a backup of an existing file if the variable ${__OVERWRITE_MODE} (parameter -o/+o) is ${__FALSE}. The function keeps up to no_of_backups old versions of the file to backup, e.g.
This code
__VERBOSE_LEVEL=1
__VERBOSE_MODE=${__TRUE}
for i in 1 2 3 4 5 6 7 8 ; do
BackupFileIfNecessary "${TESTFILE}",5
echo "This is version $i of the
file">"${TESTFILE}"
done
__VERBOSE_MODE=${__FALSE}
produces this output :
[xtrnaw7@t540p /data/develop/scripts]$ ./scriptt.sh
[03.12.2017 14:08:24] scriptt.sh v1.0.0 started at Sun Dec 3
14:08:24 CET 2017.
[03.12.2017 14:08:24] Reading the config file
"/data/develop/scripts/scriptt.conf" ...
[03.12.2017 14:08:24] Using the log file "/var/tmp/scriptt.log"
[03.12.2017 14:08:24] This is only sample code!!!
[03.12.2017 14:08:24] RUNTIME INFO: Creating up to 5 backups of the file
"./testfile" ...
[03.12.2017 14:08:24] RUNTIME INFO: Copying "./testfile" to
"./testfile.0" ...
[03.12.2017 14:08:24] RUNTIME INFO: Creating up to 5 backups of the file
"./testfile" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.0"
to "./testfile.1" ...
[03.12.2017 14:08:24] RUNTIME INFO: Copying "./testfile" to
"./testfile.0" ...
[03.12.2017 14:08:24] RUNTIME INFO: Creating up to 5 backups of the file
"./testfile" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.1"
to "./testfile.2" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.0"
to "./testfile.1" ...
[03.12.2017 14:08:24] RUNTIME INFO: Copying "./testfile" to
"./testfile.0" ...
[03.12.2017 14:08:24] RUNTIME INFO: Creating up to 5 backups of the file
"./testfile" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.2"
to "./testfile.3" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.1"
to "./testfile.2" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.0"
to "./testfile.1" ...
[03.12.2017 14:08:24] RUNTIME INFO: Copying "./testfile" to
"./testfile.0" ...
[03.12.2017 14:08:24] RUNTIME INFO: Creating up to 5 backups of the file
"./testfile" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.3"
to "./testfile.4" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.2"
to "./testfile.3" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.1"
to "./testfile.2" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.0"
to "./testfile.1" ...
[03.12.2017 14:08:24] RUNTIME INFO: Copying "./testfile" to
"./testfile.0" ...
[03.12.2017 14:08:24] RUNTIME INFO: Creating up to 5 backups of the file
"./testfile" ...
[03.12.2017 14:08:24] RUNTIME INFO: Removing the old backup
file "./testfile.4"
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.3"
to "./testfile.4" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.2"
to "./testfile.3" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.1"
to "./testfile.2" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.0"
to "./testfile.1" ...
[03.12.2017 14:08:24] RUNTIME INFO: Copying "./testfile" to
"./testfile.0" ...
[03.12.2017 14:08:24] RUNTIME INFO: Creating up to 5 backups of the file
"./testfile" ...
[03.12.2017 14:08:24] RUNTIME INFO: Removing the old backup
file "./testfile.4"
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.3"
to "./testfile.4" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.2"
to "./testfile.3" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.1"
to "./testfile.2" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.0"
to "./testfile.1" ...
[03.12.2017 14:08:24] RUNTIME INFO: Copying "./testfile" to
"./testfile.0" ...
[03.12.2017 14:08:24] RUNTIME INFO: Creating up to 5 backups of the file
"./testfile" ...
[03.12.2017 14:08:24] RUNTIME INFO: Removing the old backup
file "./testfile.4"
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.3"
to "./testfile.4" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.2"
to "./testfile.3" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.1"
to "./testfile.2" ...
[03.12.2017 14:08:24] RUNTIME INFO: Renaming "./testfile.0"
to "./testfile.1" ...
[03.12.2017 14:08:24] RUNTIME INFO: Copying "./testfile" to
"./testfile.0" ...
[03.12.2017 14:08:24] The log file used was "/var/tmp/scriptt.log"
[03.12.2017 14:08:24] scriptt.sh v1.0.0 started at Sun Dec 3
14:08:24 CET 2017 and ended at Sun Dec 3 14:08:24 CET 2017.
[03.12.2017 14:08:24] The time used for the script is 0 minutes and 0
seconds.
[03.12.2017 14:08:24] The RC is 0.
And the files contain:
[xtrnaw7@t540p /data/develop/scripts]$ for i in 0 1 2 3 4 ; do echo
"" ; echo "File $i contains \"$( cat testfile.$i )\" " ; done
File 0 contains "This is version 7 of the file"
File 1 contains "This is version 6 of the file"
File 2 contains "This is version 5 of the file"
File 3 contains "This is version 4 of the file"
File 4 contains "This is version 3 of the file"
[xtrnaw7@t540p /data/develop/scripts]$
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:
substr sourceStr pos length [resultVariable]
replacestr sourceStr oldsubStr newsubStr [resultVariable]
pos searchstring sourcestring
lastpos searchstring sourcestring
toUppercase sourceString [resultVariable]
toLowercase sourceString [resultVariable]
Also missing in standard ksh scripts are functions for converting data.
The functions defined for this purpose in the script template are:
isNumer number
ConvertToHex value
ConvertToOctal value
ConvertToBinary value
Other often used functions are those to process UID and usernames:
UserIs userid
GetCurrentUID
GetUserName UID
GetUID userid
Note:
These functions may or may not work as expected on other operating systems -- please test them before using.
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:
push value1 [...] [value#]
pop variable1 [...] [variable#]
push_and_set variable new_value
FlushStack
NoOfStackElements
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
There are other functions defined in the script template that may or may
not be useful (Note that some of them are used by the runtime system -
so be carefull if removing unused functions). 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:
In ksh you can define a function if it's not already defined (either explicit or as builtin function). I use this technique to define the function whence it's not a builtin function in the used shell:
# new definition for whence:
#
whence whence 2>/dev/null 1>/dev/null || function whence {
typeset __FUNCTION="whence"; ${__FUNCTION_INIT} ; ${__DEBUG_CODE}
typeset THISRC=1
if typeset +f $1 1>/dev/null ; then
echo $1 ; THISRC=0
elif alias $1 2>/dev/null 1>/dev/null ; then
echo $1 ; THISRC=0
else
which $1 2>/dev/null ; THISRC=$?
fi
${__FUNCTION_EXIT}
return ${THISRC}
}
Note:
Another method to check if a function is defined is
if typeset +f >/dev/null ; then
(see the source code for the function IsFunctionDefined)
Some of the parameter for the script template must be evaluated before the general parameter processing is done (e.g the parameter -q or -T ) The code for this feature just checks the variable $0, e.g.
#
-----------------------------------------------------------------------------
# process the parameter -q or --quiet
#
if [[ \ $*\ == *\ -q* || \ $*\ == *\ --quiet\ * ]] ; then
__NO_HEADERS=${__TRUE}
__QUIET_MODE=${__TRUE}
fi
The code used in the script template for initializing variables that can also be set via environment variables before starting the script use some handy features of the ksh, e.g.:
#### __NO_FINISH_ROUTINES - do not execute the finish routeins at
script end if ${__TRUE}
####
: ${__NO_FINISH_ROUTINES:=${__FALSE}}
The nope statement ":" does nothing here but the variable ${NO_FINISH_ROUTES} will be set to ${__FALSE} if it's not already set.
The function DebugShell can be
used to debug a running script; just call the function DebugShell
in your code at any time.
In the default configuration DebugShell is also called
if CTRL-C is pressed and in the trap handler for the signal USR2. To
call the DebugShell you can also use the parameter -D DebugShell.
In principle DebugShell is only a loop that reads input
from the user and executes it. DebugShell knows some
aliase and interprets everything else as an OS command and executes it via
eval.
DebugShell only works if the script is called in an
interactive terminal session; all output is written to /dev/tty and all
input is only read from /dev/tty.
e.g.
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> functions
AskUser
BackupFileIfNecessary
CheckInputDevice
CheckYNParameter
ConvertDateToEpoc
ConvertMinutesToHours
ConvertToBinary
ConvertToHex
ConvertToOctal
...
toUppercase
tryIncludeScript
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>>
Note: This functionality is implemented using "typeset -f functionname" which is not supported by all ksh versions
e.g.
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> func pos
pos
function pos {
typeset __FUNCTION="pos"; ${__FUNCTION_INIT} ;
${__DEBUG_CODE}
typeset searchstring="$1"
typeset sourcestring="$2"
typeset THISRC=0
if [[ "${sourcestring}" == *${searchstring}* ]] ; then
typeset f="${sourcestring%%${searchstring}*}"
THISRC=$(( ${#f}+1 ))
fi
[ ${THISRC} -gt 255 ] && die 234 "The return value
${THISRC} is greater than 255 in function \"${__FUNCTION}\""
${__FUNCTION_EXIT}
return ${THISRC}
}
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>>
This alias adds the string "typeset _FUNCTION=<functioname>; ${__DEBUG_CODE}" as first statements to a function
Note: This functionality is implemented using "typeset -f functionname" which is not supported by all ksh versions
e.g.
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> view_debug
The current debug code for all functions (__DEBUG_CODE) is:
eval [ 0 = 1 -o "${__FUNCTION}"x = "pos"x ] &&
printf "
*** Enabling trace for the function ${__FUNCTION} ...
" >&2 && set -x
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>>
e.g.
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> set_debug pos substr
Enabling debug code for the functions "pos substr" now
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> view_debug
The current debug code for all functions (__DEBUG_CODE) is:
eval [ 0 = 1 -o "${__FUNCTION}"x = "pos"x -o
"${__FUNCTION}"x = "substr"x ] && printf "
*** Enabling trace for the function ${__FUNCTION} ...
" >&2 && set -x
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>>
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> pos aa bbbXXaaYY
*** Enabling trace for the function pos ...
+ searchstring=aa
+ typeset searchstring
+ sourcestring=bbbXXaaYY
+ typeset sourcestring
+ THISRC=0
+ typeset THISRC
+ [[ bbbXXaaYY == *aa* ]]
+ f=bbbXX
+ typeset f
+ THISRC=6
+ [ 6 -gt 255 ]
+ return 6
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>>
e.g.
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> clear_debug
Clearing the debug code now ...
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> view_debug
The current debug code for all functions (__DEBUG_CODE) is:
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>>
e.g.
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> vars help
Known variable lists for the alias "vars" are:
all
- print all variables used
application - print application
variables
used_env - print
environment variables used
log
- print variables for the logfile handling
defaults - print
variables with DEFAULT_* values
config
- print variables for the config file processing
house_keeping -
print variables for the housekeeping processing
signalhandler -
print variables for the signal handler
dump
- print variables for the dump processing
script
- print variables with the script name, directory, etc
debug
- print variables for the debug functions
parameter - print
variables for the parameter
requirements - print variables for the
script requirements
runtime -
print various runtime variables
os_env
- print variables for the OS environment
internal - print
all internal variables execept these variables
__LONG_USAGE_HELP __SHORT_USAGE_HELP
__OTHER_USAGE_EXAMPLES __CONFIG_PARAMETER
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>>
e.g
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> vars log
*** Logging variables:
__DEF_LOGFILE: "/var/tmp/scriptt.log"
__LOGFILE: "/tmp/scriptt.sh.16552.TEMP"
__DEBUG_PREFIX: "DEBUG: "
__INFO_PREFIX: "INFO:"
__WARNING_PREFIX: "WARNING: "
__ERROR_PREFIX: "ERROR: "
__RUNTIME_INFO_PREFIX: "RUNTIME INFO: "
__NO_TIME_STAMPS: "1"
__NO_HEADERS: "1"
__NOECHO: "1"
__USE_TTY: "1"
__NO_OF_WARNINGS: "0"
__LIST_OF_WARNINGS: ""
__NO_OF_ERRORS: "0"
__LIST_OF_ERRORS: ""
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>>
e.g.
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> verbose
Toggling the verbose mode now ...
The verbose mode is now 0
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> verbose
Toggling the verbose mode now ...
The verbose mode is now 1
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>>
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> break
Toggling the break mode now ...
The break mode is now 1
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>> break
Toggling the break mode now ...
The break mode is now 0
-------------------------------------------------------------------------------
scriptt.sh - debug shell - enter a command to execute ("exit" to
leave the shell)
defined aliase: functions = list all defined functions, vars
[help|var_list] = print global variables,
quit = exit the script, abort = abort the script with kill -9, use help
for short usage help
>>
This alias is only valid if DebugShell is called via CTRL-C.
For more indepth documentation for the function DebugShell see http://bnsmb.de/solaris/scriptt_mini.html.
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 -H
: http://bnsmb.de/files/public/solaris/templates/scriptt.txt
A mini version of scriptt.sh is here: http://bnsmb.de/solaris/scriptt_mini.html
These scripts are based on scriptt.sh (be aware that most of them are based on an older version of scriptt.sh):