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.

e.James
  • 223
  • 2
  • 7

3 Answers3

13

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]}"
Chris Down
  • 122,090
  • 24
  • 265
  • 262
  • 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 Dec 01 '11 at 23:21
  • @e.James `if (( ${PIPESTATUS[0]} )); then ... command failed ...; else ... command succeeded ...; fi` – Chris Down Dec 01 '11 at 23:23
  • Ah. I should have figured that one out on my own. Thank you `:)` – e.James Dec 01 '11 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 Dec 01 '11 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 Dec 01 '11 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"
Kevin
  • 40,087
  • 16
  • 88
  • 112
  • I get: `sh: set: pipefail: unknown option name`, so it seems that my shell does not support it `:(` – e.James Dec 01 '11 at 23:23
  • Too bad, what shell are you using? – Kevin Dec 01 '11 at 23:24
  • GNU bash, version 2.04.0(1)-release (i686-pc-msys) – e.James Dec 01 '11 at 23:28
  • Updated with a couple more suggestions. – Kevin Dec 01 '11 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 Dec 01 '11 at 23:35
  • +1 and thank you for your help. Chris Down's solution ended up working for me, so I have marked his as accepted. – e.James Dec 01 '11 at 23:41
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 $@
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • 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`). – Stéphane Chazelas Feb 26 '15 at 12:24