============ String Tools ============ .. default-domain:: bash .. file:: signal_tools.bsh Function to help handle signals and traps in bash .. function:: set_bashpid Sets the ``BASHPID`` environment variable in bash versions older than 4, for compatibility in bash 3.2 .. function:: 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 :func:`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). .. rubric:: Bugs If :func:`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 :func:`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 :func:`trap_chain`. A different warning is printed out, unless disabled via ``TRAP_CHAIN_QUIET``. .. function:: 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 :func:`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. .. var:: trap_chain_active When a :func:`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 :func:`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) .. rubric:: 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 :func:`trap_chain` correctly. The major differences in writing a trap for :func:`trap_chain` are: * ``trap`` becomes :func:`trap_chain`. Use this to set a trap handler. * ``exit`` becomes :func:`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. .. code-block:: 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