I really missed the "finally" functionality in Bash, so I decided to implement.
Bottom line, there is one single trap shared by the process. If you set trap something RETURN in one function, another function will also execute something. The traps are process wide, global, for all functions.
Thus, we have to implement it ourselves to call proper callback depending on which function has just returned. We know which function returned by inspecting BASH_SOURCE, BASH_LINENO or FUNCNAME. Thus in my L_lib library I implemented L_finally function.
Below you might find a stripped down implementation. The elements to execute are just stored in a global array.
finally_pid=""
finally_on_return=()
finally_on_exit=()
finally_handle_return() {
local i IFS=' '
for i in ${finally_on_return[${#BASH_SOURCE[*]}]}; do
eval "${finally_on_exit[i]}"
unset -v 'finally_on_exit[$i]'
done
unset -v 'finally_on_return[${#BASH_SOURCE[*]}]'
}
finally_handle_exit() {
# execute on_exit traps in reverse order
for ((i=${#finally_on_exit[*]}-1; i>=0; --i)); do
eval "${finally_on_exit[i]}"
done
# Even if EXIT trap is called after INT trap, it will not re-execute.
finally_on_exit=()
}
finally() {
local cmd
# Reset arrays in subshells, so that they are not inherited
if ((finally_pid != BASHPID)); then
finally_pid=$BASHPID
finally_on_return=()
finally_on_exit=()
# register the traps
trap finally_handle_exit EXIT INT TERM
trap finally_handle_return RETURN
fi
# Properly quote command for eval.
printf -v cmd "%q " "$@"
# Add the return trap. Store the index of on_exit trap to execute.
# The RETURN trap executes at specific "depth" of RETURN,
# simply indexed by the number of elements in BASH_SOURCE.
finally_on_return[${#BASH_SOURCE[*]}-1]+=" ${#finally_on_exit[@]}"
# Add to the exit trap.
finally_on_exit+=("$cmd;")
# Attach trace attribute to function so it executes RETURN handler when functrace is not enabled.
declare -f -t "${FUNCNAME[1]}"
}
############
fooclear() { echo fooclear; }
barclear() { echo barclear; }
bar() {
finally barclear
echo bar
}
foo() {
finally fooclear
echo foo
bar
}
foo
The code outputs:
foo
bar
barclear
fooclear
The "barclear" and "fooclear" are executes on RETURN from foo and bar functions. You might want to insert exit at various points in the code and see how EXIT trap will execute.
The code above might be improved in several ways - introducing critical sections with delayed signals, attaching RETURN trap to function higher in the stack, properly preserving exit status when receiving a signal, handling set -e, popping last registered action or popping action with specific index.