Summary: I don't see the best way to (in bash) trap/handle an ERR inside a group-command "{}" that is in a pipeline. I have a way that seems to work, see "(F)", but little confidence that it's the best way, and some suspicion that the overall effort is misguided.
Detail: I'd like to trap and handle trouble during a bash script. By trapping ERR, I am successful handling trouble -- when commands are simple and not part of a pipeline. (A) cmd1; cmd2; trouble; cmd4; cmd5 But I'd like to also filter the output of these commands... My trap scheme successfully handles (B) cmd1; { cmd2; trouble; cmd4; }; cmd5 but fails to handle (C) cmd1; { cmd2; trouble; cmd4; } | filter; cmd5 (I've also explored killing $$ (see (D)), which exits at the right place but doesn't return the proper status) Is this an expected limitation? Is there a workaround? Ahhh, further reading & research informs me of "pipefail", and leads me to set another trap inside the "{}". So, now (F) seems to work for me... Any comments will be appreciated -- Is there a better way? Or perhaps there are uncaught conditions and I've developed a false sense of security? See below for simple test script demonstrating implementations A - F, with cmd3 returning non-zero (or zero). My bash is version 3.2.51(24)-release (i686-pc-cygwin) Demonstrate as follows... 1st arg is the implementation variant, 2nd arg is the exit status of the middle command. # define simple wrapping function for "try" script $ mytry () { ./try $1 $2; echo dbg: script exit status: $?; } # (A) This works. The implementation of (A) is: # trap myERRhandler ERR # trap myEXIThandler EXIT # cmd1; cmd2; cmd3; cmd4; cmd5 $ mytry A 0 # third cmd in series returns 0 $ mytry A 6 # third cmd in series returns 6, so abend immediately # (B) This works. The implementation of (B) is: # trap myERRhandler ERR # trap myEXIThandler EXIT # cmd1; { cmd2; cmd3; cmd4; }; cmd5 $ mytry B 0 $ mytry B 6 # (C) This doesn't abend when it should. The implementation of (C) is: # trap myERRhandler ERR # trap myEXIThandler EXIT # cmd1; { cmd2; cmd3; cmd4; } | filter; cmd5 $ mytry C 0 $ mytry C 6 # (D) This abends when it should, but on trouble, script exits 0 # trap myERRhandler ERR # trap myEXIThandler EXIT # cmd1 # # trap myERRhandler HUP # { cmd2 && cmd3 && cmd4 || kill -HUP $$; } | filter # # cmd5 $ mytry D 0 $ mytry D 6 # (E) This abends when it should, but on trouble, runs myERRhandler twice. # trap myERRhandler ERR # trap myEXIThandler EXIT # set -o pipefail # cmd1; { trap myERRhandler ERR; cmd2; cmd3; cmd4; } | filter; cmd5 $ mytry E 0 $ mytry E 6 # (F) This seems to work... :) # trap myERRhandler ERR # trap myEXIThandler EXIT # set -o pipefail # cmd1 # { trap myOtherERRhandler ERR; cmd2; cmd3; cmd4; } | filter # cmd5 $ mytry F 0 $ mytry F 6 where try contains: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #! c:\cygwin\bin\bash # print opening html echo "<PRE>" # on error, print msg and exit $STATUS trap myERRhandler ERR myERRhandler () { STATUS=$? echo "dbg: entered myERRhandler() -- (on error, print msg and exit $STATUS)" echo "trouble..." exit $STATUS } # on exit, print closing html trap myEXIThandler EXIT myEXIThandler () { echo "dbg: entered myEXIThandler() -- (on exit, print closing html)" echo "</PRE>" } myreturn() { return $1; } echo cmd1 case $1 in A) # THIS WORKS echo cmd2 echo "cmd3 (with exit code $2)"; myreturn $2 echo cmd4 ;; B) # THIS WORKS { echo cmd2 echo "cmd3 (with exit code $2)"; myreturn $2 echo cmd4 } ;; C) # THIS DOESN'T ABEND WHEN IT SHOULD { echo cmd2 echo "cmd3 (with exit code $2)"; myreturn $2 echo cmd4 } | cat ;; D) # THIS ABENDS WHEN IT SHOULD, BUT ON TROUBLE, SCRIPT EXITS 0 trap myERRhandler HUP { echo cmd2 && echo "cmd3 (with exit code $2)" && myreturn $2 && echo cmd4 || kill -HUP $$ } | cat ;; E) # THIS ABENDS WHEN IT SHOULD, BUT RUNS myERRhandler() twice # 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 successfully. set -o pipefail { trap myERRhandler ERR echo cmd2 echo "cmd3 (with exit code $2)"; myreturn $2 echo cmd4 } | cat ;; F) # THIS ABENDS WHEN IT SHOULD, BUT RUNS myERRhandler() twice # 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 successfully. myInGroupERRhandler () { STATUS=$? echo "dbg: entered myInGroupERRhandler()" exit $STATUS } set -o pipefail { trap myInGroupERRhandler ERR echo cmd2 echo "cmd3 (with exit code $2)"; myreturn $2 echo cmd4 } | cat ;; *) echo NOT IMPLEMENTED ;; esac echo cmd5