Classic Shell Scripting - Arnold Robbins [106]
$ echo $?
1
$ pathfind NOSUCHPATH ls
Empty directory search path
Usage: pathfind [--all] [--?] [--help] [--version] envvar pattern(s)
$ echo $?
1
Then we supply some nonsense filenames:
$ pathfind -a PATH foobar
foobar: not found
$ echo $?
1
$ pathfind -a PATH "name with spaces"
name with spaces: not found
$ echo $?
1
The empty filename list test is next:
$ pathfind PATH
$ echo $?
0
Here's what happens when a quickly typed Ctrl-C interrupts the running program:
$ pathfind PATH foo
^C
$ echo $?
130
The exit code is 128 + 2, indicating that signal number 2 was caught and terminated the program. On this particular system, it is the INT signal, corresponding to interactive input of the keyboard interrupt character.
So far, error reporting is exactly as we intended. Now let's search for files that we know exist, and exercise the -a option:
$ pathfind PATH ls
/usr/local/bin/ls
$ echo $?
0
$ pathfind -a PATH ls
/usr/local/bin/ls
/bin/ls
$ echo $?
Next, we check the handling of a quoted wildcard pattern that must match files that we know exist:
$ pathfind -a PATH '?sh'
/usr/local/bin/ksh
/usr/local/bin/zsh
/bin/csh
/usr/bin/rsh
/usr/bin/ssh
Then we do the same for a pattern that should not match anything:
$ pathfind -a PATH '*junk*'
*junk*: not found
Now for a big test: find some C and C++ compilers on this system:
$ pathfind -a PATH c89 c99 cc c++ CC gcc g++ icc lcc pgcc pgCC
c89: not found
c99: not found
/usr/bin/cc
/usr/local/bin/c++
/usr/bin/c++
CC: not found
/usr/local/bin/gcc
/usr/bin/gcc
/usr/local/gnat/bin/gcc
/usr/local/bin/g++
/usr/bin/g++
/opt/intel_cc_80/bin/icc
/usr/local/sys/intel/compiler70/ia32/bin/icc
/usr/local/bin/lcc
/usr/local/sys/pgi/pgi/linux86/bin/pgcc
/usr/local/sys/pgi/pgi/linux86/bin/pgCC
$ echo $?
3
An awk one-liner lets us verify that the exit-code counter logic works as intended. We try 150 nonexistent files, but the exit code correctly caps at 125:
$ pathfind PATH $(awk 'BEGIN { while (n < 150) printf("x.%d ", ++n) }' )
x.1: not found
...
x.150: not found
$ echo $?
125
Our final test verifies that standard error and standard output are handled as promised, by capturing the two streams in separate files, and then showing their contents:
$ pathfind -a PATH c89 gcc g++ >foo.out 2>foo.err
$ echo $?
1
$ cat foo.out
/usr/local/bin/gcc
/usr/bin/gcc
/usr/local/gnat/bin/gcc
/usr/local/bin/g++
/usr/bin/g++
$ cat foo.err
c89: not found
At this point, we can probably declare our pathfind command a success, although some shell wizard might still be able to spot a hole[1] in it, and there is no substitute for extensive testing, particularly with unexpected input, such as from the fuzz tests cited in a footnote in Section B.3 in Appendix B. Ideally, testing should exercise every combination of legal, and at least one illegal, argument. Since we have three main option choices, each with several abbreviations, there are (6 + 1) (10 + 1) × (14 + 1) = 1155 option combinations, and each of these needs to be tested with zero, one, two, and at least three remaining arguments. We know from our implementation that the option abbreviations are handled the same way so that many fewer tests are necessary. However, when we put on our testing hat, we must first view the program as a black box whose contents are unknown, but which is documented to behave a certain way. Later, we should put on a different testing hat, sneak inside the program, and then knowing how it works, try hard to figure out how to break it. Also, test data needs to be devised that can be shown to exercise every single line of the program. Exhaustive testing is tedious!
Because undocumented software is likely to be unusable software, and because few books describe how to write manual pages, we develop a manual page for pathfind in Appendix A.
pathfind has proved a valuable exercise. Besides being a handy new tool that isn't available in the standard GNU, POSIX, and Unix toolboxes, it has all the major elements of most Unix