Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shell script templates [closed]

What would be your suggestions for a good bash/ksh script template to use as a standard for all newly created scripts?

I usually start (after the #! line) with a commented-out header with a filename, synopsis, usage, return values, author(s), changelog and would fit into 80-char lines.

All documentation lines I start with double-hash symbols ## so I can grep for them easily and local var names are prepended with "__".

Any other best practices? Tips? Naming conventions? What about return codes?

Comments on version control : we use SVN all right, but another dept in the enterprise has a separate repo and this is their script. How do I know who to contact with Q's if there is no @author info? Using entries similar to javadocs has some merit even in the shell context, IMHO, but I might be wrong.

like image 964
webwesen Avatar asked Jan 09 '09 23:01

webwesen


People also ask

What does $1 do in Bash?

$1 - The first argument sent to the script. $2 - The second argument sent to the script. $3 - The third argument... and so forth. $# - The number of arguments provided.

What is cat << EOF?

This operator stands for the end of the file. This means that wherever a compiler or an interpreter encounters this operator, it will receive an indication that the file it was reading has ended.

How does HereDoc work in Bash?

A HereDoc is a multiline string or a file literal for sending input streams to other commands and programs. HereDocs are especially useful when redirecting multiple commands at once, which helps make Bash scripts neater and easier to understand.


2 Answers

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 0 exit 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=0 out= file= Dflag= while getopts hvVf:o:D: flag do     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;;     esac done shift $(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.)

like image 185
Jonathan Leffler Avatar answered Sep 17 '22 17:09

Jonathan Leffler


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/null  main() {   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. } 
like image 31
Mark Edgar Avatar answered Sep 18 '22 17:09

Mark Edgar