Classic Shell Scripting - Arnold Robbins [104]
dirpath=`eval echo '${'"$envvar"'}' 2>/dev/null | tr : ' ' `
It took a long paragraph to explain the single short statement that sets dirpath, so you can see that it is tricky. Clearly, eval adds significant power to the language.
After eval, understanding the rest of the program is pretty easy. First there are some sanity checks to handle any unusual conditions that would cause problems later on: every good program should make such checks, to avoid the infamous garbage-in, garbage-out syndrome. Notice that the last sanity check, for an empty file list, does not cause an error report. The reason is that any program that processes a list should always handle an empty list gracefully: if there is nothing to do, there is nothing to report but success:
# sanity checks for error conditions
if test -z "$envvar"
then
error Environment variable missing or empty
elif test "x$dirpath" = "x$envvar"
then
error "Broken sh on this platform: cannot expand $envvar"
elif test -z "$dirpath"
then
error Empty directory search path
elif test $# -eq 0
then
exit 0
fi
We then have three nested loops: the outer one over the argument files or patterns, the middle one over the directories in the search path, and the inner one over matching files in a single directory. We want the loops in that order so that each file is dealt with completely before moving on to the next one. The reverse loop order would just prove confusing to the user, since the file reports would be mixed up. Before starting the middle loop, we set result to the empty string, since we use that later to determine whether anything was found:
for pattern in "$@"
do
result=
for dir in $dirpath
do
for file in $dir/$pattern
do
In the body of the innermost loop, test -f tells us whether $file exists and is a regular file. (It is also true if it is a symbolic link that ultimately points to a regular file.) If it does, we record it in result, report it on standard output with an echo command, and if the default of reporting only the first one applies, we break out of the innermost and middle loops. Otherwise, the loop continues over the remaining matching files, possibly producing more reports:
if test -f "$file"
then
result="$file"
echo $result
test "$all" = "no" && break 2
fi
done
done
In this program, there is no need in the middle loop to test whether $dir itself exists as a valid directory because that is subsumed by the existence check in the innermost loop for $file. However, with a more complex loop body, such a test would be desirable, and can be easily done with a single statement: test -d $dir || continue.
At the completion of the middle loop, we have searched all of the directories in the search path for $pattern, and result either holds the name of the last match found or is still empty, if no matches were found.
We test whether the expansion $result is empty, and if so, we report the missing file on standard error, increment the error count in EXITCODE (inside the warning function), and then continue the outer loop with the next file: