Shell script templates [closed]
I'd extend Norman's answer to 6 lines, and the last of those is blank:
#!/bin/ksh## @(#)$Id$## Purpose
The third line is a version control identification string - it is actually a hybrid with an SCCS marker '@(#)
' that can be identified by the (SCCS) program what
and an RCS version string which is expanded when the file is put under RCS, the default VCS I use for my private use. The RCS program ident
picks up the expanded form of $Id$
, which might look like $Id: mkscript.sh,v 2.3 2005/05/20 21:06:35 jleffler Exp $
. The fifth line reminds me that the script should have a description of its purpose at the top; I replace the word with an actual description of the script (which is why there's no colon after it, for example).
After that, there is essentially nothing standard for a shell script. There are standard fragments that appear, but no standard fragment that appears in every script. (My discussion assumes that scripts are written in Bourne, Korn, or POSIX (Bash) shell notations. There's a whole separate discussion on why anyone putting a C Shell derivative after the #!
sigil is living in sin.)
For example, this code appears in some shape or form whenever a script creates intermediate (temporary) files:
tmp=${TMPDIR:-/tmp}/prog.$$trap "rm -f $tmp.?; exit 1" 0 1 2 3 13 15...real work that creates temp files $tmp.1, $tmp.2, ...rm -f $tmp.?trap 0exit 0
The first line chooses a temporary directory, defaulting to /tmp if the user did not specify an alternative ($TMPDIR is very widely recognized and is standardized by POSIX). It then creates a file name prefix including the process ID. This is not a security measure; it is a simple concurrency measure, preventing multiple instances of the script from trampling on each other's data. (For security, use non-predictable file names in a non-public directory.) The second line ensures that the 'rm
' and 'exit
' commands are executed if the shell receives any of the signals SIGHUP (1), SIGINT (2), SIGQUIT (3), SIGPIPE (13) or SIGTERM (15). The 'rm
' command removes any intermediate files that match the template; the exit
command ensures that the status is non-zero, indicating some sort of error. The 'trap
' of 0 means that the code is also executed if the shell exits for any reason - it covers carelessness in the section marked 'real work'. The code at the end then removes any surviving temporary files, before lifting the trap on exit, and finally exits with a zero (success) status. Clearly, if you want to exit with another status, you may - just make sure you set it in a variable before running the rm
and trap
lines, and then use exit $exitval
.
I usually use the following to remove the path and suffix from the script, so I can use $arg0
when reporting errors:
arg0=$(basename $0 .sh)
I often use a shell function to report errors:
error(){ echo "$arg0: $*" 1>&2 exit 1}
If there's only one or maybe two error exits, I don't bother with the function; if there are any more, I do because it simplifies the coding. I also create more or less elaborate functions called usage
to give the summary of how to use the command - again, only if there's more than one place where it would be used.
Another fairly standard fragment is an option parsing loop, using the getopts
shell built-in:
vflag=0out=file=Dflag=while getopts hvVf:o:D: flagdo case "$flag" in (h) help; exit 0;; (V) echo "$arg0: version $Revision$ ($Date$)"; exit 0;; (v) vflag=1;; (f) file="$OPTARG";; (o) out="$OPTARG";; (D) Dflag="$Dflag $OPTARG";; (*) usage;; esacdoneshift $(expr $OPTIND - 1)
or:
shift $(($OPTIND - 1))
The quotes around "$OPTARG" handle spaces in arguments. The Dflag is cumulative, but the notation used here loses track of spaces in arguments. There are (non-standard) ways to work around that problem, too.
The first shift notation works with any shell (or would do if I used back-ticks instead of '$(...)
'. The second works in modern shells; there might even be an alternative with square brackets instead of parentheses, but this works so I've not bothered to work out what that is.
One final trick for now is that I often have both the GNU and a non-GNU version of programs around, and I want to be able to choose which I use. Many of my scripts, therefore, use variables such as:
: ${PERL:=perl}: ${SED:=sed}
And then, when I need to invoke Perl or sed
, the script uses $PERL
or $SED
. This helps me when something behaves differently - I can choose the operational version - or while developing the script (I can add extra debug-only options to the command without modifying the script). (See Shell parameter expansion for information on the ${VAR:=value}
and related notations.)
I use the first set of ## lines for the usage documentation. I can't remember now where I first saw this.
#!/bin/sh## Usage: myscript [options] ARG1#### Options:## -h, --help Display this message.## -n Dry-run; only show what would be done.##usage() { [ "$*" ] && echo "$0: $*" sed -n '/^##/,/^$/s/^## \{0,1\}//p' "$0" exit 2} 2>/dev/nullmain() { while [ $# -gt 0 ]; do case $1 in (-n) DRY_RUN=1;; (-h|--help) usage 2>&1;; (--) shift; break;; (-*) usage "$1: unknown option";; (*) break;; esac done : do stuff.}
This is the header I use for my script shell (bash or ksh).It is a man
look alike and it is used to display usage() as well.
#!/bin/ksh#================================================================# HEADER#================================================================#% SYNOPSIS#+ ${SCRIPT_NAME} [-hv] [-o[file]] args ...#%#% DESCRIPTION#% This is a script template#% to start any good shell script.#%#% OPTIONS#% -o [file], --output=[file] Set log file (default=/dev/null)#% use DEFAULT keyword to autoname file#% The default value is /dev/null.#% -t, --timelog Add timestamp to log ("+%y/%m/%d@%H:%M:%S")#% -x, --ignorelock Ignore if lock file exists#% -h, --help Print this help#% -v, --version Print script information#%#% EXAMPLES#% ${SCRIPT_NAME} -o DEFAULT arg1 arg2#%#================================================================#- IMPLEMENTATION#- version ${SCRIPT_NAME} (www.uxora.com) 0.0.4#- author Michel VONGVILAY#- copyright Copyright (c) http://www.uxora.com#- license GNU General Public License#- script_id 12345#-#================================================================# HISTORY# 2015/03/01 : mvongvilay : Script creation# 2015/04/01 : mvongvilay : Add long options and improvements# #================================================================# DEBUG OPTION# set -n # Uncomment to check your syntax, without execution.# set -x # Uncomment to debug this shell script##================================================================# END_OF_HEADER#================================================================
And here is the usage functions to go with:
#== needed variables ==#SCRIPT_HEADSIZE=$(head -200 ${0} |grep -n "^# END_OF_HEADER" | cut -f1 -d:)SCRIPT_NAME="$(basename ${0})" #== usage functions ==#usage() { printf "Usage: "; head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#+" | sed -e "s/^#+[ ]*//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; }usagefull() { head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#[%+-]" | sed -e "s/^#[%+-]//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g" ; }scriptinfo() { head -${SCRIPT_HEADSIZE:-99} ${0} | grep -e "^#-" | sed -e "s/^#-//g" -e "s/\${SCRIPT_NAME}/${SCRIPT_NAME}/g"; }
Here is what you should obtain:
# Display help$ ./template.sh --help SYNOPSIS template.sh [-hv] [-o[file]] args ... DESCRIPTION This is a script template to start any good shell script. OPTIONS -o [file], --output=[file] Set log file (default=/dev/null) use DEFAULT keyword to autoname file The default value is /dev/null. -t, --timelog Add timestamp to log ("+%y/%m/%d@%H:%M:%S") -x, --ignorelock Ignore if lock file exists -h, --help Print this help -v, --version Print script information EXAMPLES template.sh -o DEFAULT arg1 arg2 IMPLEMENTATION version template.sh (www.uxora.com) 0.0.4 author Michel VONGVILAY copyright Copyright (c) http://www.uxora.com license GNU General Public License script_id 12345# Display version info$ ./template.sh -v IMPLEMENTATION version template.sh (www.uxora.com) 0.0.4 author Michel VONGVILAY copyright Copyright (c) http://www.uxora.com license GNU General Public License script_id 12345
You can get the full script template here: http://www.uxora.com/unix/shell-script/18-shell-script-template