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 the trap command accepts.

Parameters:
  • TRAP_CHAIN_QUIET - Set to 1 to silence warnings, if you don’t want them. Default: 0

Outputs:

_TRAP_CHAIN_{signal name}_LAST_PID - the PID last time trap_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 when exit 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 chain

  • Unset - 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 becomes trap_chain. Use this to set a trap handler.

  • exit becomes exit_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