12

Possible Duplicate:
Get exit code of process that's piped to another

I am using the following command line (in a makefile) to pipe the verbose error messages from my compiler through a perl script that simplifies them into something human-readable:

g++ -c source.cpp -o source.o 2>&1 | perl /bin/gSTLFilt.pl

Unfortunately, this approach "masks" the error value returned by the g++ command. make has no idea that the g++ command has failed, because all it gets back is the error result from the perl command.

Is there a way to pipe the output, and still retain the original error condition?

In case it makes a difference: I am using GNU Make 3.81 and g++ (GCC) 3.4.5 (mingw-vista special r3) in an MSYS console running GNU bash, version 2.04.0(1)-release (i686-pc-msys) on Windows XP.

1
  • Maybe this is off-topic for Unix SE, since it doesn't deal with a Unix environment?
    – Chris Down
    Commented Dec 3, 2011 at 16:06

3 Answers 3

14

I am not sure what shell sh.exe provides (since there are multiple shells that use that name for their Windows executables), but if it is bash or similar, you can use the $PIPESTATUS array. For your example, you would do:

g++ -c source.cpp -o source.o 2>&1 | perl /bin/gSTLFilt.pl
echo "${PIPESTATUS[0]}"
5
  • 1
    That might be working, but wouldn't it just echo the result? I would need it to be the "return value" from that command line for make to recognize it. (sorry if I am not using the correct terminology here).
    – e.James
    Commented Dec 1, 2011 at 23:21
  • @e.James if (( ${PIPESTATUS[0]} )); then ... command failed ...; else ... command succeeded ...; fi
    – Chris Down
    Commented Dec 1, 2011 at 23:23
  • Ah. I should have figured that one out on my own. Thank you :)
    – e.James
    Commented Dec 1, 2011 at 23:25
  • you could use true and false in the if, but I'm not sure this will work because every line is executed in its own subshell (link in my post).
    – Kevin
    Commented Dec 1, 2011 at 23:26
  • That seems to do the trick. For the record, I used: if (( ${PIPESTATUS[0]} )); then true; else false; fi, and it successfully stops the makefile when an error has occurred.
    – e.James
    Commented Dec 1, 2011 at 23:38
9

Bash has an option pipefail:

The return status of a pipeline is the exit status of the last command,
unless  the  pipefail  option  is enabled.  If pipefail is enabled, the
pipeline's return status is the value of the last  (rightmost)  command
to  exit  with a non-zero status, or zero if all commands exit success-
fully.

So:

set -o pipefail && $GCC_COMMAND | $PERL_COMMAND

Make executes every line in a subshell for each line, so you need to add it to the beginning of your gcc line. There may be a way to get make to execute just that one command with pipefail set already but I don't know it.

Try adding SHELL=/bin/bash in the Makefile (Make should use this)

Or try:

bash -o pipefail -c "$GCC_COMMAND | $PERL_COMMAND"
6
  • I get: sh: set: pipefail: unknown option name, so it seems that my shell does not support it :(
    – e.James
    Commented Dec 1, 2011 at 23:23
  • Too bad, what shell are you using?
    – Kevin
    Commented Dec 1, 2011 at 23:24
  • GNU bash, version 2.04.0(1)-release (i686-pc-msys)
    – e.James
    Commented Dec 1, 2011 at 23:28
  • Updated with a couple more suggestions.
    – Kevin
    Commented Dec 1, 2011 at 23:32
  • I tried set -o pipefail on a command line by itself, and that still gives me the unknown option error. bash is also not available. I may try to install it, but that sounds like a bit more work than I was hoping for :)
    – e.James
    Commented Dec 1, 2011 at 23:35
6

In traditional shells, the status of the first command in a pipeline is not reported at all to the script. Only the status of the last command is available, in $?.

In bash ≥3.0, when you want to do is stop if an error occurs anywhere in the pipeline, use the pipefail option.

g++ -c source.cpp -o source.o 2>&1 | perl /bin/gSTLFilt.pl

More generally, in bash, the PIPESTATUS array generalizes $? to cover all the commands in the last pipeline.

$ (exit 1) | (exit 2) | (exit 3); echo ${PIPESTATUS[@]}
1 2 3

Zsh has the same feature, only the array is called pipestatus.

% zsh -c '(exit 1) | (exit 2) | (exit 3); echo $pipestatus'    
1 2 3

If you're willing to assume bash (which IIRC is the shell provided by msys as sh), then you can use PIPESTATUS. If you aren't, you can arrange to pass the exit status through to the toplevel shell via the pipe, and make your filter program exit with the status it reads on the last line of input instead of using it as normal input. It's clumsy, but it can be useful.

In makefiles, it's relatively common to use temporary files, here treating the compiler messages as one more intermediate file.

%.otmp %.g++-log: %.cpp
        g++ -c $< -o $@tmp 2>&1 >$*.g++-log
%.o: %.otmp %.g++-log
        perl /bin/gSTLFilt.pl <$*.g++-log
        mv $*.otmp $@
1
  • Depends what you mean by traditional. (t)csh do report the status of the rightmost failing command in the pipeline, like (k|ba|z)sh -o pipefail (in tcsh, you can disable that with unset anyerror). Commented Feb 26, 2015 at 12:24

Not the answer you're looking for? Browse other questions tagged .