I'm seeing some weird behavior with bash and trapping EXIT
inside subshells. I'd expect the four following lines to all output the same thing ("hi trapped"):
a=$(trap 'echo trapped' EXIT ; echo hi); echo $a
a=$(trap 'echo trapped' EXIT && echo hi); echo $a
a=$(trap 'echo trapped' EXIT ; /bin/echo hi); echo $a
a=$(trap 'echo trapped' EXIT && /bin/echo hi); echo $a
The first three do print "hi trapped", but not the last one. It just outputs "hi". The trap is not being called. You can verify this with set -x
:
set -x; a=$(trap 'echo trapped' EXIT ; echo hi); set +x; echo $a
set -x; a=$(trap 'echo trapped' EXIT && echo hi); set +x; echo $a
set -x; a=$(trap 'echo trapped' EXIT ; /bin/echo hi); set +x; echo $a
set -x; a=$(trap 'echo trapped' EXIT && /bin/echo hi); set +x; echo $a
Through some trial and error I've found that the EXIT
trap is not called under the following conditions:
- The entirety of the subshell program is a list of commands chained together with
&&
.- If you use
;
, or even||
at any point, the trap will execute.
- If you use
- All commands in the chain must execute.
- If any one of the commands (except the last) exits with a non-zero exit status such that the last command never executes, the trap will execute.
- The final command must be a program on the system, not a shell builtin and not a function.
- Non-final commands can be builtins or functions and the trap will not run as long as the final command is a program
Is this intentional? Is it documented?
For reference, I came across this because rvm overwrites cd
with its own function that ends up adding a trap on EXIT
which does (among other things) echo -n 'Saving session...'
. I was running a shell script that uses this bash idiom:
some_dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )
So some_dir
was getting 'Saving session...' appended to it. It was hard to debug, because subshells weren't always running the EXIT
trap rvm was adding.