Traps (signal handlers) are useful for cleaning up resources, but have some unexpected quirks in bash.
Multiple EXIT Traps
You would hope that the following would call
f2 on exit:
... but only
f2 is called, since a new EXIT trap replaces an existing one.
This is a particular problem when two EXIT traps are widely separated; for example, if one trap is inside a script sourced by another script.
EXIT Trap in a function
An EXIT trap defined in a function is called on exit from the script using that function. For instance:
This trap will also be called if the function exits explicitly via
exit statement or after receiving a signal such as INT or TERM. If the function is called as
bash -c f, however, the order of messages is reversed:
In particular, this happens when a function is invoked under
Traps can catch signals such as
SIGTERM (from a
kill command) or
SIGINT (sent by Ctrl+C).
But be aware that EXIT traps are still called when a signal is received, so
bye will be called twice in the following script when Ctrl+C is pressed:
The EXIT trap was called a second time because of the
exit command inside
bye(). The solution is not to just remove the
exit, since then
bye() won't terminate the script on SIGINT.
It is safer just to just trap EXIT (and also ignore signals inside the trap function):
In this case
bye() doesn't need to contain an
- If a script runs to completion, the exit status is 0.
- If it is terminated by
exit n, it exits with status
nafter calling the trap function.
- If it is terminated by a signal, the exit status is 128 plus the signal code, even if there is an
exitcommand inside the trap function. (Use
trap -lto see a list of signal codes).
RETURN traps are not called if the script terminates (via a signal or
exit command), so they are best avoided in general.
An alternative method of cleaning up on function return is to call the function inside a subshell with an EXIT trap. This has the normal limitations of subshells, however (e.g. a variable set in the subshell won't be set in the parent).
- There is an extra gotcha here: using the normal subshell bracket syntax
( ... )doesn't seem to call the EXIT trap. Instead, create the subshell using
comma/bash/process/comma-process-utilfor an example.
If you do end up using a RETURN trap, there is one last gotcha: the trap needs to be unset (using
trap - RETURN), otherwise it can remain active. (This behaviour is inconsistent in bash: sometimes it happens and sometimes it doesn't).
DEBUG traps are called for every command in a script:
The main gotcha is that DEBUG traps are not inherited by functions unless they have the "trace" attribute (