Classic Shell Scripting - Arnold Robbins [112]
$ dirname /usr/local/bin/nawk
Report the directory path/usr/local/bin
If the argument does not contain a slash, dirname produces a dot representing the current directory:
$ dirname whimsical-name
Report the directory path.
Like basename, dirname treats its argument as a simple text string, without checking for its existence in the filesystem.
If the argument is omitted, dirname's behavior is implementation-defined.
* * *
We tell the user where the logs are created, and record that location in email as well, because the user might forget the log location before the build of a large package completes:
msg="Check build logs for $p in `hostname`:$LOGDIR"
echo "$msg"
echo "$msg" | $MAIL -s "$msg" $USER 2> /dev/null
The final step in the main loop is a nested loop over the remote hosts to start building the current package on each of them in parallel. Once again, most of the work is relegated to a function. This also ends the outer loop:
for u in $userhosts
do
build_one $u
done
done
The build_one invocations are done sequentially so we can more easily identify communications problems. However, the work that they start on the remote build hosts runs in the background on those systems, so build_one actually completes comparatively quickly.
At this point, the program has done its work. The last statements cap the cumulative status code at the limit of 125 and return the status code to the caller:
test $EXITCODE -gt 125 && EXITCODE=125
exit $EXITCODE
We have left several build processes running in the background, with their output accumulating in associated log files. We chose to exit anyway so that build-all runs quickly.
Some people might prefer an alternate design that does not return until all of the background processes have completed. The change is simple: immediately before the final exit statement, insert this statement:
wait
We don't find that approach useful because it either ties up a terminal window until all builds are complete, or if build-all is run in the background, its completion notice is likely to be mixed in with other output, and thus missed, when it appears much later.
Now that we have a general picture of how the program works, it is time to examine the details that are hidden inside the functions. We present them in order of use.
usage is a simple function: it prints a short help message on standard output, using a here document instead of a series of echo statements:
usage( )
{
cat < $PROGRAM [ --? ] [ --all "..." ] [ --cd "..." ] [ --check "..." ] [ --configure "..." ] [ --environment "..." ] [ --help ] [ --logdirectory dir ] [ --on "[user@]host[:dir][,envfile] ..." ] [ --source "dir ..." ] [ --userhosts "file(s)" ] [ --version ] package(s) EOF } usage_and_exit calls usage, and then exits with the status code supplied as its argument: usage_and_exit( ) { usage exit $1 } version displays the version number on standard output: version( ) { echo "$PROGRAM version $VERSION" } error displays its arguments on standard error, follows them with the usage message, and then terminates the program with a failure status code: error( ) { echo "$@" 1>&2 usage_and_exit 1 } warning displays its arguments on standard error, increments the warning count in EXITCODE, and returns: warning( ) { echo "$@" 1>&2 EXITCODE=`expr $EXITCODE + 1` } The outer loop in the main body began with a call to find_package. That function loops over the source directories, looking for the package, and handles details that we haven't discussed yet: find_package( ) { # Usage: find_package package-x.y.z base=`echo "$1" | sed -e 's/[-_][.]*[0-9].*$//'` PAR= PARFILE= for srcdir in $SRCDIRS do test "$srcdir" = "." && srcdir="`pwd`" for subdir in "$base" "" do # NB: update package setting in build_one( ) if this list changes find_file $srcdir/$subdir/$1.tar.gz "tar xfz" && return find_file $srcdir/$subdir/$1.tar.Z "tar xfz" && return find_file $srcdir/$subdir/$1.tar "tar xf" && return find_file