Shell programming style -- a plea for better shell scripts

Guido van Rossum guido at mcvax.UUCP
Thu Feb 9 07:50:57 AEST 1984


Here are some rules for writing shell scripts in such a way that
they are more readable, robust and still not too slow.

1. If at all possible, use the Bourne shell (/bin/sh), not the C shell
   (/bin/csh), even if your login shell is the latter.  The Bourne shell
   has a better way of treating multi-line control structures (if, for,
   case etc.) and better substitution rules.  Bourne shell scripts
   are also easier to port to other sites than C shell scripts are.
   (Exception: then C shell is superiour if lots of arithmetic must be done.) 
 
2. Use blank lines, comments and indentation like you would in C or
   Pascal programs.

3. Whenever parameter substitution (e.g., $1) or variable substitution
   (such as $HOME) are used, decide whether there should be double quotes
   ("") around it.  If it is expected that there may be embedded blanks
   in the actual value (theoretically this can even occur in filenames) and 
   it is passed as a single argument to another program, quote it!
   Also note the very useful difference between "$*" and "$@", which both
   expand to the concatenation of all arguments.  When passed to another
   program, "$*" is always one argument; "$@" is as many arguments as
   there were originally, if there was at least one.  $* (without quotes)
   omits empty or blank arguments (created by passing, e.g., "" as argument)
   and splits arguments in thwo when they contain blanks.  
   (Note that "" passed as a file name means the current directory, which
   is almost certainly not what was meant!)
   E.g., to print all arguments, by default the standard input:

      case $# in
         0) print;;
         *) print "$@";;
      esac
  
4. Use "case" for string comparisons rather than "if".  That is, to see
   e.g. whether $1 equals "-a", use:

        case "$1" in
           -a) then-part;;
           *) else-part;; 
        esac

   rather than

        if [ "$1" == "-a" ]; then
           <then-part>
        else
           <else-part>
        fi 

   The reason is mainly that "[" "]" executes as a separate process,
   while the case is executed by the shell.  (I know that some shell
   derivates avoid the extra process in this case; but the vanilla
   V7 Bourne shell is the subject of this article.)

5. Avoid the commands "true" and "false".  They are implemented through
   separate processes.  The next paragraph shows an alternative for
   "while true":
 
6. Be careful to design a parameter convention which mimics that of other
   well-known Un*x programs; e.g. command [-flag] ... [file] ... .
   If files can be given as arguments, the script should read its
   standard in put instead.  Example of how to program this:

        while :             # ":" is a built-in do-nothing command
        do 
           case "$1" in
              -a) <process-a-flag>; shift;;
              -b) <process-b-flag>; shift;;
              -*) echo "Usage: $0 [-a] [-b] [file] ..." 1>&2; exit 1;;
              *) break;;  # breaks out of loop
           esac
        done  

   And then proceed with the strategy pointed out in paragraph 3.


I could go on indefinitely with this, but try to stop here.
Any comments?  Contrary visions?  Other hints?  (Flames?, I would add...)

		Guido van Rossum
		Centrum voor Wiskunde en Informatica, Amsterdam
		...!{decvax,philabs}!mcvax!guido



More information about the Comp.unix mailing list