Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Posting-Version: version B 2.10.1 6/24/83 (MC840302); site mcvax.UUCP Path: utzoo!watmath!clyde!floyd!cmcl2!philabs!mcvax!guido From: guido@mcvax.UUCP (Guido van Rossum) Newsgroups: net.unix Subject: Shell programming style -- a plea for better shell scripts Message-ID: <5684@mcvax.UUCP> Date: Wed, 8-Feb-84 16:50:57 EST Article-I.D.: mcvax.5684 Posted: Wed Feb 8 16:50:57 1984 Date-Received: Sat, 11-Feb-84 07:19:15 EST Organization: CWI, Amsterdam Lines: 82 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" ]; thenelse 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) ; shift;; -b) ; 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