tags:

views:

253

answers:

1

I currently have a script that does something like

./a | ./b | ./c

I want to modify it so that if any of a, b or c exit with an error code I print an error message and stop instead of piping bad output forward.

What would be the simplest/cleanest way to do so?

+4  A: 

If you really don't want the second command to proceed until the first is known to be successful, then you probably need to use temporary files. The simple version of that is:

tmp=${TMPDIR:-/tmp}/mine.$$
if ./a > $tmp.1
then
    if ./b <$tmp.1 >$tmp.2
    then
        if ./c <$tmp.2
        then : OK
        else echo "./c failed" 1>&2
        fi
    else echo "./b failed" 1>&2
    fi
else echo "./a failed" 1>&2
fi
rm -f $tmp.[12]

The '1>&2' redirection can also be abbreviated '>&2'; however, an old version of the MKS shell mishandled the error redirection without the preceding '1' so I've used that unambiguous notation for reliability for ages.

This leaks files if you interrupt something. Bomb-proof (more or less) shell programming uses:

tmp=${TMPDIR:-/tmp}/mine.$$
trap 'rm -f $tmp.[12]; exit 1' 0 1 2 3 13 15
...if statement as before...
rm -f $tmp.[12]
trap 0 1 2 3 13 15

The first trap line says 'run the commands 'rm -f $tmp.[12]; exit 1' when any of the signals 1 SIGHUP, 2 SIGINT, 3 SIGQUIT, 13 SIGPIPE, or 15 SIGTERM occur, or 0 (when the shell exits for any reason). If you're writing a shell script, the final trap only needs to remove the trap on 0, which is the shell exit trap (you can leave the other signals in place since the process is about to terminate anyway).

In the original pipeline, it is feasible for 'c' to be reading data from 'b' before 'a' has finished - this is usually desirable (it gives multiple cores work to do, for example). If 'b' is a 'sort' phase, then this won't apply - 'b' has to see all its input before it can generate any of its output.

If you want to detect which command(s) fail, you can use:

(./a || echo "./a exited with $?" 1>&2) |
(./b || echo "./b exited with $?" 1>&2) |
(./c || echo "./c exited with $?" 1>&2)

This is simple and symmetric - it is trivial to extend to a 4-part or N-part pipeline.

Simple experimentation with 'set -e' didn't help.

Jonathan Leffler
I would recommend using `mktemp` or `tempfile`.
Dennis Williamson
@Dennis: yes, I suppose I should get used to commands such as mktemp or tmpfile; they didn't exist at the shell level when I learned it, oh so many years ago. Let's do a quick check. I find mktemp on MacOS X; I have mktemp on Solaris but only because I've installed GNU tools; it seems that mktemp is present on antique HP-UX. I'm not sure whether there's a common invocation of mktemp that works across platforms. POSIX standardizes neither mktemp nor tmpfile. I didn't find tmpfile on the platforms I have access to. Consequently, I won't be able to use the commands in portable shell scripts.
Jonathan Leffler