Does the trap catch as expected during the pipeline?

Below is the minimum code to demonstrate the problem: http://pastebin.com/5TXDpSh5

#!/bin/bash set -e set -o pipefail function echoTraps() { echo "= on start:" trap -p trap -- 'echo func-EXIT' EXIT echo "= after set new:" trap -p # we can ensure after script done - file '/tmp/tmp.txt' was not created trap -- 'echo SIG 1>/tmp/tmp.txt' SIGPIPE SIGHUP SIGINT SIGQUIT SIGTERM } trap -- 'echo main-EXIT1' EXIT echo "===== subshell trap" ( echoTraps; ) echo "===== pipe trap" echoTraps | cat echo "===== done everything" 

Output

 ===== subshell trap = on start: = after set new: trap -- 'echo func-EXIT' EXIT func-EXIT ===== pipe trap = on start: = after set new: trap -- 'echo func-EXIT' EXIT ===== done everything main-EXIT1 

expected output

 ===== subshell trap = on start: = after set new: trap -- 'echo func-EXIT' EXIT func-EXIT ===== pipe trap = on start: = after set new: trap -- 'echo func-EXIT' EXIT func-EXIT <---- here is the expected difference ===== done everything main-EXIT1 

NB: I tested for OSX 10.9.2 bash (3.2.51) - other versions of bash have the same difference between the actual expected output and are described below

+6
source share
2 answers

The only way to find out if this behavior is expected or not is to ask Chet Ramey (GNU bash supporter). Please send an email with your bug report - bash @ gnu.org

You can see that the current behavior seems correct, given that it explicitly handles the case of the subshell: http://git.savannah.gnu.org/cgit/bash.git/tree/execute_cmd.c#n621

 /* We want to run the exit trap for forced {} subshells, and we want to note this before execute_in_subshell modifies the COMMAND struct. Need to keep in mind that execute_in_subshell runs the exit trap for () subshells itself. */ /* This handles { command; } & */ s = user_subshell == 0 && command->type == cm_group && pipe_in == NO_PIPE && pipe_out == NO_PIPE && asynchronous; /* run exit trap for : | { ...; } and { ...; } | : */ /* run exit trap for : | ( ...; ) and ( ...; ) | : */ s += user_subshell == 0 && command->type == cm_group && (pipe_in != NO_PIPE || pipe_out != NO_PIPE) && asynchronous == 0; last_command_exit_value = execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close); if (s) subshell_exit (last_command_exit_value); else sh_exit (last_command_exit_value); 

As you can see, the explicit case of a subshell is treated as a special case (and this happens with a grouping of commands). This behavior has evolved historically, as Adrian found out, due to numerous bug reports.

This is the list of changes for this particular function (running the EXIT trap on subshells):


Commit: http://git.savannah.gnu.org/cgit/bash.git/commit/?id=a37d979e7b706ce9babf1306c6b370c327038eb9

 +execute_cmd.c + - execute_command_internal: make sure to run the EXIT trap for group + commands anywhere in pipelines, not just at the end. From a point + raised by Andreas Schwab < schwab@linux-m68k.org > 

Report: https://lists.gnu.org/archive/html/bug-bash/2013-04/msg00126.html (Re: trap EXIT in a subtitle that does not start while waiting)


Commit: http://git.savannah.gnu.org/cgit/bash.git/commit/?id=1a81420a36fafc5217e770e042fd39a1353a41f9

 +execute_cmd.c + - execute_command_internal: make sure any subshell forked to run a + group command or user subshell at the end of a pipeline runs any + EXIT trap it sets. Fixes debian bash bug 698411 + http://bugs.debian.org/cgi-big/bugreport.cgi?bug=698411 

Report: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=698411 (EXIT trap, pipeline and subshell)


Commit: http://git.savannah.gnu.org/cgit/bash.git/commit/?id=fd58d46e0d058aa983eea532bfd7d4c597adef54

 +execute_cmd.c + - execute_command_internal: make sure to call subshell_exit for + {} group commands executed asynchronously (&). Part of fix for + EXIT trap bug reported by Maarten Billemont < lhunath@lyndir.com > 

Report: http://lists.gnu.org/archive/html/bug-bash/2012-07/msg00084.html (EXIT traps in interactive shells)


There is also a recent bug report regarding an EXIT trap that does not execute in some expected contexts: http://lists.gnu.org/archive/html/bug-bash/2016-11/msg00054.html

+1
source

Here are some more test cases for your entertainment:

 $ cat traps.sh #!/bin/bash echoTraps() { echo "entering echoTraps()" printf " traps: %s\n" "$(trap -p)" echo " setting trap" trap -- 'echo "func-exit()"' EXIT printf " traps: %s\n" "$(trap -p)" echo "exiting echoTraps()" } trap -- 'echo "main-exit()"' EXIT echo "===== calling '( echoTraps; )'" ( echoTraps; ) echo echo "===== calling 'echoTraps | cat'" echoTraps | cat echo echo "===== calling '( echoTraps; ) | cat'" ( echoTraps; ) | cat echo echo "===== calling '{ echoTraps; } | cat'" { echoTraps; } | cat echo 

bash -4.2.25 (1)

 $ ./traps.sh ===== calling '( echoTraps; )' entering echoTraps() traps: setting trap traps: trap -- 'echo "func-exit()"' EXIT exiting echoTraps() func-exit() ===== calling 'echoTraps | cat' entering echoTraps() traps: trap -- 'echo "main-exit()"' EXIT setting trap traps: trap -- 'echo "func-exit()"' EXIT exiting echoTraps() ===== calling '( echoTraps; ) | cat' entering echoTraps() traps: setting trap traps: trap -- 'echo "func-exit()"' EXIT exiting echoTraps() func-exit() ===== calling '{ echoTraps; } | cat' entering echoTraps() traps: trap -- 'echo "main-exit()"' EXIT setting trap traps: trap -- 'echo "func-exit()"' EXIT exiting echoTraps() main-exit() 

bash -4.3.0 (1)

 $ bash-static-4.3.2/bin/bash-static traps.sh ===== calling '( echoTraps; )' entering echoTraps() traps: setting trap traps: trap -- 'echo "func-exit()"' EXIT exiting echoTraps() func-exit() ===== calling 'echoTraps | cat' entering echoTraps() traps: trap -- 'echo "main-exit()"' EXIT setting trap traps: trap -- 'echo "func-exit()"' EXIT exiting echoTraps() ===== calling '( echoTraps; ) | cat' entering echoTraps() traps: setting trap traps: trap -- 'echo "func-exit()"' EXIT exiting echoTraps() func-exit() ===== calling '{ echoTraps; } | cat' entering echoTraps() traps: trap -- 'echo "main-exit()"' EXIT setting trap traps: trap -- 'echo "func-exit()"' EXIT exiting echoTraps() func-exit() main-exit() 

Bottom line: Do not rely on such edges. I remember that I studied other inconsistencies (not traps) regarding subshells and pipes and tried to wrap my head around the bash source code, and you donโ€™t want to go this route and try to understand why it behaves the way it does in some situations (the code is really terrible, by the way). As you can see, some things seem to have been โ€œfixedโ€ and /, but both of my examples are already behaving differently than yours.

0
source

All Articles