Any code that is going to be released in the wild should have the following short header:
# Script to turn lead into gold
# Copyright (C) 2009 Ima Hacker (i.m.hacker@foo.org)
# Permission to copy and modify is granted under the foo license
# Last revised 1/1/2009
Keeping a change log going in code headers is a throwback from when version control systems were terribly inconvenient. A last modified date shows someone how old the script is.
If you are going to be relying on bashisms, use #!/bin/bash , not /bin/sh, as sh is the POSIX invocation of any shell. Even if /bin/sh points to bash, many features will be turned off if you run it via /bin/sh. Most Linux distributions will not take scripts that rely on bashisms, try to be portable.
To me, comments in shell scripts are sort of silly unless they read something like:
# I am not crazy, this really is the only way to do this
Shell scripting is so simple that (unless you're writing a demonstration to teach someone how to do it) the code nearly always explains itself.
Some shells don't like to be fed typed 'local' variables. I believe to this day Busybox (a common rescue shell) is one of them. Make GLOBALS_OBVIOUS instead, it's much easier to read, especially when debugging via /bin/sh -x ./script.sh.
My personal preference is to let logic speak for itself and minimize work for the parser. For instance, many people might write:
if [ $i = 1 ]; then
... some code
fi
Where I'd just:
[ $i = 1 ] && {
... some code
}
Likewise, someone might write:
if [ $i -ne 1 ]; then
... some code
fi
... where I'd:
[ $i = 1 ] || {
... some code
}
The only time I use conventional if / then / else is if there's an else-if to throw in the mix.
A horribly insane example of very good portable shell code can be studied by just viewing the 'configure' script in most free software packages that use autoconf. I say insane because its 6300 lines of code that caters to every system known to humankind that has a UNIX like shell. You don't want that kind of bloat, but it is interesting to study some of the various portability hacks within.. such as being nice to those who might point /bin/sh to zsh :)
The only other advice I can give is watch your expansion in here-docs, i.e.
cat << EOF > foo.sh
printf "%s was here" "$name"
EOF
... is going to expand $name, when you probably want to leave the variable in place. Solve this via:
printf "%s was here" "\$name"
which will leave $name as a variable, instead of expanding it.
I also highly recommend learning how to use trap to catch signals .. and make use of those handlers as boilerplate code. Telling a running script to slow down with a simple SIGUSR1 is quite handy :)
Most new programs that I write (which are tool / command line oriented) start out as shell scripts, it's a great way to prototype UNIX tools.
You might also like the SHC shell script compiler, check it out here.