String Tools
- signal_tools.bsh
Function to help handle signals and traps in bash
- set_bashpid
Sets the BASHPID
environment variable in bash versions older than 4, for compatibility in bash 3.2
- trap_chain
- Arguments:
$1
- Command to be read and executes when the shell received signal$2
$2
- Same sigspecs that thetrap
command accepts.
- Parameters:
TRAP_CHAIN_QUIET
- Set to1
to silence warnings, if you don’t want them. Default:0
- Outputs:
_TRAP_CHAIN_{signal name}_LAST_PID
- the PID last timetrap_chain
was called
Can be called multiple times to chain multiple signal handlers to one signal. Stores list of trap functions in _TRAP_CHAIN_{signal name}
.
Handlers are run in the opposite order they are assigned in (FILO).
Bugs
If trap_chain
is called inside a subshell on bash 4.2 or newer when the original trap was set using the trap
command instead of trap_chain
, the traps of the parent are auto inherited in the subshell. This is due to a change in how the trap
command works, and there is no known automatic work around. If this happens, you’ll need to clear the trap in or before the subshell to prevent this. A warning message will be printed out, unless disabled via TRAP_CHAIN_QUIET
.
Note
If you are using signal ERR
, you must have set -E
set, or else it will not inherit correctly. The same goes for DEBUG
and RETURN
with set -T
. This is part of normal bash
behavior, however the same bug mentioned above, can happened if you set +E
or set +T
before creating a subshell, and then re-enabling the same flag before calling trap_chain
. A different warning is printed out, unless disabled via TRAP_CHAIN_QUIET
.
- exit_chain
In case there are multiple functions being chained together for a trap, you might not want to exit until after all the traps have a chance to run. In order to accomplish this, use exit_chain
. If the trap is not part of a chain, exit
is called immediately. If the trap is part of a chain, exit
is called after the chain is complete
- Arguments:
[$1]
- The exit code used whenexit
is called
Note
This is not designed to work if the function is called in a subshell; this includes function name()( ... )
subshell functions.
- trap_chain_active
When a trap_chain
trap is triggered, a single trap will be called directly, while a chain of multiple traps will be using _trap_unchained
. Functions like exit_chain
will change how they work depending on whether _trap_unchained
is used or not. This variable stores that state.
- Value:
0
-_trap_unchained
is being used to call the chainUnset - The trap function is being called directly (by
trap
or other)
Example
The following example handles Ctrl+C, kills (SIGTERM) and normal exit cases. Writing exit handlers in bash is very tricky. Fortunately, little is needed to make it work with trap_chain
correctly.
The major differences in writing a trap for trap_chain
are:
trap
becomestrap_chain
. Use this to set a trap handler.exit
becomesexit_chain
. Use this to exit the script from within a trap handler.
The rest of this boiler plate example is handling all the intricacies of bash.
function my_trap()
{
# When exit (which exit_chain calls eventually) is called, the exit code
# will get stored in $?, but only for the first line of a function, so
# it should first be stored in rv
# This is also the case when an error occurs (with "set -e" is enabled)
# This will *not* capture an error code for unbound variables on bash 3.2
local rv="${?}"
# If INT or TERM were already triggered, and EXIT is being triggered after
# that, there is no need to run everything a second time, so return rv right
# away. Failure to specify rv will result in an exit code of 0
if [ "${my_trap_called-}" = "0" ]; then
return "${rv}"
fi
# This is where the trap handler code goes
# <- Do stuff.
# If the TERM signal is being handled, the program does not exit. Ironically,
# if the trap wasn't setup in the first place, it would have exited. So we have
# to manually call exit. To do this, use exit_chain so that all the TERM
# handlers in the chain have a chance to run.
if [ "${1-}" = "term" ]; then
my_trap_called=0
exit_chain 143
# The same caveats for TERM are true for the INT signal, generated by
# pressing Ctrl+C
elif [ "${1-}" = "int" ]; then
my_trap_called=0
exit_chain 130
fi
# If this is just a normal EXIT call, you'll get here, and you still
# need to pass the return value, or else the exit code will always be zero
return "${rv}"
}
# Here is where you connect "my_trap" to EXIT, INT, and TERM handles.
# We typically use all three to catch the different ways a program can
# end. Handling all three signals increases compatibility on all OSes
trap_chain "my_trap exit" EXIT
trap_chain "my_trap int" INT
trap_chain "my_trap term" TERM