Classic Shell Scripting - Arnold Robbins [103]
However, it is easier to verify that all cases are properly terminated when the semicolons appear on their own lines, and it also facilitates adding extra statements to the block. Careful use of indentation helps to clarify and emphasize logical structure in almost any programming language.
The GNU convention is to respond to a —help request with a brief summary on standard output of how to use the program, and then to exit immediately with a success status code (0 in POSIX and Unix). For larger programs, that summary would include a short description of each of the options, but ours is simple enough that extra explanation is not necessary. Since the question mark, ?, is a shell wildcard character, we must quote its use in the case selector:
--help | --hel | --he | --h | '--?' | -help | -hel | -he | -h | '-?' )
usage_and_exit 0
;;
Similarly, the GNU convention is that a —version option should produce a (usually) one-line report on standard output and an immediate successful exit. The same applies to other kinds of status-request options that might be offered by a larger program: —author, —bug-reports, —copyright, —license, —where-from, and so on:
--version | --versio | --versi | --vers | --ver | --ve | --v | \
-version | -versio | -versi | -vers | -ver | -ve | -v )
version
exit 0
;;
The case selector -*) matches any other option: we report the illegal option on standard error, call the usage( ) function to remind the user what was expected, and exit immediately with a failure status code (1):
-*)
error "Unrecognized option: $1"
;;
The distinction between standard error and standard output is not always obeyed by software, and when commands are used interactively, the user won't see a difference, since both streams go to the same display device. If the program is a filter, then both errors and status reports, like the output from the —help and —version options, should go to standard error so as not to contaminate a pipeline; otherwise, status reports can go to standard output. Since status reports are a relatively recent contribution from the GNU world, programming practice is still evolving, and standards have yet to be issued. Neither POSIX nor traditional Unix documentation seems to address this issue.
The final case selector *) matches everything else. It is analogous to the default selector in the switch statement of the C, C++, and Java languages, and it is always a good idea to include it, even if its body is empty, to demonstrate to the reader that all alternatives have been considered. Here, a match indicates that we have processed all of the options, so we can exit the loop. Since we have now handled all of the possible cases, we end the case statement with the terminating keyword:
*)
break
;;
esac
We are now at the end of the option loop. Just before its final statement, we use shift to discard the first argument, which has now been processed, and to move the remaining ones down in the argument list. This guarantees eventual loop termination when the argument count, $#, reaches zero:
shift
done
On exit from the loop, all options have been handled, and what remain in the argument list are the environment variable name and the files to find. We save the variable name in envvar and if at least one argument remains, we discard the first argument:
envvar="$1"
test $# -gt 0 && shift
The remaining arguments are available as "$@". We avoid storing them in a variable, such as with files="$@", because filenames with spaces would be handled incorrectly: the embedded spaces would become argument separators.
Since it is possible that the user-supplied environment variable is PATH, which we reset on entry for security reasons, we check for this, and update envvar accordingly:
test "x$envvar" = "xPATH" && envvar=OLDPATH
The leading x is commonly seen: here, it prevents the expansion of the variable from being confused with a test option, if that expansion starts with a hyphen.
All arguments have now been handled, and we come to the tricky part: the use of the shell