How can I pipe output to another process, but retain the error state of the first process?

7,029

Solution 1

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]}"

Solution 2

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"

Solution 3

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 $@
Share:
7,029

Related videos on Youtube

e.James
Author by

e.James

Updated on September 18, 2022

Comments

  • e.James
    e.James almost 2 years

    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.

    • Admin
      Admin over 12 years
      Maybe this is off-topic for Unix SE, since it doesn't deal with a Unix environment?
  • e.James
    e.James over 12 years
    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
    e.James over 12 years
    I get: sh: set: pipefail: unknown option name, so it seems that my shell does not support it :(
  • clerksx
    clerksx over 12 years
    @e.James if (( ${PIPESTATUS[0]} )); then ... command failed ...; else ... command succeeded ...; fi
  • Kevin
    Kevin over 12 years
    Too bad, what shell are you using?
  • e.James
    e.James over 12 years
    Ah. I should have figured that one out on my own. Thank you :)
  • e.James
    e.James over 12 years
    GNU bash, version 2.04.0(1)-release (i686-pc-msys)
  • Kevin
    Kevin over 12 years
    Updated with a couple more suggestions.
  • e.James
    e.James over 12 years
    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
    e.James over 12 years
    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
    e.James over 12 years
    +1 and thank you for your help. Chris Down's solution ended up working for me, so I have marked his as accepted.
  • Stéphane Chazelas
    Stéphane Chazelas over 9 years
    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).