When discovering Bash one of the missing features is not try catch block, but try finally block.
See also with section of documentation.
It has some function build on top of L_finally.
The issues with raw trap:¶
- Not easy to append actions to trap.
- (This is something I crudely wanted to solve with
L_trap_pushandL_trap_popwith unmanageable results)
- (This is something I crudely wanted to solve with
- On which signals you want to execute? EXIT? INT? TERM? HUP?
- EXIT trap signal is not executed always and is not executed in command substitutions. Or it is? It depends.
- If you register action on EXIT and INT trap, it might execute twice, or not.
- Trapped SIGINT does not exit. Calling
exitinside SIGINT trap changes exit status.
- Trapped SIGINT does not exit. Calling
- The signal exit status is hard to preserve.
- The proper way is to
trap - SIGINT; kill -SIGINT $BASHPIDto have the Bash process properly exit with the signal cause. - The trap and kill is different between signals.
- The proper way is to
- There is one RETURN trap shared across all functions. Inner functions overwrite outer functions RETURN trap.
Features:¶
- Execute something when Bash exits. Always.
L_finally something
- Execute something when a function returns or Bash exits, whichever comes first.
func() { L_finally -r something; }
- Execute something on signal and continue execution after it.
L_finally; trap 'something' USR1
- If 2 signals are received during trap handler execution, terminate execution with a friendly message.
- Registered actions execute in reverse order.
- Remove the last registered action without execution.
L_finally_pop -n
- Remove and execute the last registered action.
L_finally_pop
- Critical section delays the execution of signal handlers.
L_finally_critical_section func
- The signal exit status as reported by
WIFSIGNALEDshould be preserved.
How L_finally works?¶
- Registers trap on all signals that result in process termination.
- Keeps a list of actions to execute on exit.
- When receiving a signal, all exit actions are executed.
- When receiving a RETURN trap:
- Execute the RETURN traps for the function that is returning.
- The traps are removed the exit traps.
Usage notes:¶
- Inside the
L_finallyhandler:- Variable
L_SIGNALis set to the received signal name. - Variable
L_SIGNUMis set to the received signal number. - Variable
L_SIGRETis set to the value of$?when trap is expanded. - It is not allowed to call
returnon top level. This would just return from theL_finallyhandler.
- Variable
- If you want to provide your own signal handlers, it is important to first call
L_finallywithout or with arguments. The first call toL_finallywill register the signal handlers for this BASHPID! They will not be re-registered later, so they allow to overwrite the handler. - Consider using
L_evalto properly escape arguments when evaluating them.
Examples¶
tmpf=$(mktemp)
L_finally rm -rf "$tmpf"
: do something to tmpf >"$tmpf"
if [[ -n "$option" ]]; then
tmpf2=$(mktemp)
L_finally rm -rf "$tmpf2"
: we need another tmpf2 >"$tmpf2"
: it is ok, we can remove it now
L_finally_pop
if
# tmp will be removed on the end of the script.
Function return example:
option_func() {
local tmpf=$(mktemp)
L_finally -r rm -rf "$tmpf"
echo Do something with temporary file >"$tmpf"
# exit 1 # this will remove the tempfile
# return 1 # this will also remove the tempfile
# kill $BASHPID # this will also remove the tempfile
# the tempfile is automatically removed on the end of function
}
main() {
tmpf=$(mktemp)
L_finally -r rm -rf "$tmpf"
if [[ -n "$option" ]]; then
option_func
fi
}
Custom action on signal:
increase_volume() { : your function to increase volume; }
L_finally # with no arguments, just registers the action on all traps for this BASHPID.
# kill -USR1 $BASHPID # would terminate the process executing L_finally_run
trap 'increase_volume' USR1
kill -USR1 $BASHPID # will increase volume and continue execution
Implementation notes¶
There have been multiple iterations of the design with multiple arrays. Bottom line, the idea was that "choosing" which traps to execute should be as fast as possible.
The simplest design with just two array of commands to execute turned out to be most effective and efficient and easy. Each element has an index, can be easily removed and navigate.
The only downside is that during L_finally_pop the code needs to find the index of RETURN array element connected to the EXIT array element. This is a simple loop. It is still faster than any ${//} ${##} parsing I have come up with before this design.
Users do not need an array on custom signals. I decided there is little need for "expanding" the features of this library into a ultra-signal-manager. 99.9% of the time I require a simple "try finally" block, nothing more, nothing less, and this covers most usages.
Generated documentation from source:¶
finally
¶
Properly preserve exit status for parent processes. https://www.cons.org/cracauer/sigint.html Re-signaling does not work properly on specific signals. Re-signaling does not work properly in subshells and subshell exits with 0.
Array of commands to execute on EXIT.
_L_finally_arr=()
Array of commands to execute on function returns.
When a function returns at stack depth given by ${#BASH_LINENO[@]} the _L_finally_return[${#BASH_LINENO[@]}] should be executed. _L_finally_return=()
BASHPID that registered traps.
_L_finally_pid=""
Holds signal received inside a critical section.
[0] - signal name [1] - signal number _L_finally_pending=()
Currently handled signal name.
Special values: RETURN EXIT POP NONE POP - when calling from L_finally_pop NONE - used inside critical section. L_SIGNAL=""
Currently handled signal number.
Unset when handling RETURN or POP. 0 for EXIT trap. L_SIGNUM=""
The value of $? as expanded by trap.
L_SIGRET=""
L_finally_handle_return
¶
L_finally RETURN handler.
Arguments:
-
$1The value of $?. -
$2The value of $BASH_COMMAND.
L_finally_handle_exit
¶
L_finally EXIT handler.
L_finally_handle_signal
¶
L_finally signal handler.
Arguments:
-
$1The trap signal name to handle. -
$2The trap signal number to handle.
L_finally_list
¶
List elements registered by L_finally.
L_finally
¶
Register an action to be executed upon termination.
The action will be executed only exactly once, even if the signal is received multiple times.
The signal exit code of the program or subshell is preserved.
The variable $L_SIGNAL is set to the currently handled signal name and available in action.
Warning
The function assumes full ownership of all trap values.
This is needed to properly set traps accross PIDs and subshells and functions and on Bash below 5.2.
Bash below 5.2 does not execute EXIT trap in subshells after receiving a signal,
so L_finally trap handler is registered on all possible signals.
The signal exit status is preserved.
Example
tmpf=$(mktemp)
L_finally rm "$tmpf"
calculate_something() {
local tmpf
tmpf=$(mktemp)
L_finally -r rm "$tmpf"
echo use tmpf >"$tmpf"
# tmpf automatically cleaned up once on RETURN or EXIT or signal, whichever comes first.
}
Options:
-
-rSet trace attribute on the function and add RETURN trap to register the function on.
This does not work correctly with source and instead source RETURN trap will execute parent scope actions. To mitigate this, wrap source in a function, for example use L_source.
-
-s <int>Increment the stack offset for the RETURN trap by this number.
The RETURN trap handler will execute the action only if called from the nth position in the stack relative to the current position. Default: 0
-
-lAdd action to be executed last, not first of the stack.
Calling L_finally_pop after registering such action is undefined.
-
-RForce reregister all the traps. Unless this option, traps are only registered on the first call of a BASHPID. -
-v <var>Store the action index in the variable.
This index can be used with
L_finally_pop -ito remove the action. -
-hPrint this help and return 0.
Argument:
$@
Action to execute. When action is missing, then only traps are registered.
The command may not call eval 'return'. It would just return from the handler function.
Shellcheck disable= SC2089 SC2090
See: L_finally_pop
L_finally_pop
¶
Execute and unregister the last action registered with L_finally.
Options:
-
-nDo not execute the action, only remove. -
-i <index>Remove action of index. -
-hPrint this help and return 0.
Return:
1 if nothing was popped, 2 on invalid usage,
otherwise return the exit status of the executed action.
See: L_finally
L_finally_critical_section
¶
Execute a command inside a critical section.
The signals will be raised after the command is finished. Prerequisite: L_finally has registered signal handlers.
Argument:
$@
Command to execute.