Skip to content

Instantly share code, notes, and snippets.

@Mythra
Last active February 20, 2022 01:47
Show Gist options
  • Save Mythra/cf08290d103cc42d9caec1846364c716 to your computer and use it in GitHub Desktop.
Save Mythra/cf08290d103cc42d9caec1846364c716 to your computer and use it in GitHub Desktop.
Defer but in bash
#!/usr/bin/env bash
set -e
source "defer.sh"
deferBashCommand() {
defer "ls -lart -hu $(pwd)"
echo "hihi!"
}
deferCleanup() {
defer_ignore_errors
defer "rm -f /tmp/my-test" # script will not exit because ignore errors is on
defer "echo 'who is there?'"
echo "knock knock!"
}
deferFailure() {
defer_guard_errors # techincally isn't needed since we have set script on -e, but why not!
defer "false" # this will cause script to exit!
echo "bye!"
}
deferBashCommand
deferCleanup
deferFailure
; bash test.sh
hihi!
total 16K
drwxrwxrwt 20 root root 4.0K Nov 19 06:08 ..
-rw-rw-r-- 1 cynthia cynthia 512 Nov 19 06:13 test.sh
-rw-rw-r-- 1 cynthia cynthia 3.8K Nov 19 06:15 defer.sh
drwxrwxr-x 2 cynthia cynthia 4.0K Nov 19 06:15 .
knock knock!
'who is there?'
bye!
; echo $?
1
#!/usr/bin/env bash
# Ensure defer , and it's global state only get used once.
if ! declare -f defer >/dev/null 2>&1 ; then
# Use case statement so we don't have to compile a regex for a single
# character.
case "$-" in
*"e"*)
__base_error_mode=1
;;
*)
__base_error_mode=0
;;
esac
# Error Hashmap `<FunctionName, IsErrorModeOn>`
#
# split into arrays for bashv3 support.
__error_stack_key=()
__error_stack_value=()
# Defer Hashmap `<FunctionName, CodeToCall>`
#
# split into arrays for bashv3 support.
__defer_stack_key=()
__defer_stack_value=()
# __trap_read(signal: String)
#
# Modifies Variables: None
#
# parses trap output to get you the command currently set for a signal.
__trap_read() {
printf '%s' "$(trap -p "$1" | sed "s/trap -- '//g; s/' $1$//g; s/\\\''//g")"
}
# __trap_write(command: String, signal: String)
#
# Modifies Variables: None
#
# Wrapper around the trap command, properly respecting empty strings.
__trap_write() {
if [ -z "$1" ]; then
trap - "$2"
else
trap "$1" "$2"
fi
}
# __push_error_stack(func_name: String, err_mode: (0 || 1))
#
# Modifies Variables:
# * __error_stack_key
# * __error_stack_value
# Modifies State:
# * Error Mode.
#
# Pushes onto the error stack.
__push_error_stack() {
__error_stack_key=("${__error_stack_key[@]}" "$1")
__error_stack_value=("${__error_stack_value[@]}" "$2")
if [ "$2" -eq "1" ]; then
set -e
else
set +e
fi
}
# __pop_err_stack()
#
# Modifies Variables:
# * __error_stack_key
# * __error_stack_value
# Modifies State:
# * Error Mode
#
# Should be called when a function exits. Checks to see
# if the function exiting had an artificial error_state, and unsets it.
__pop_err_stack() {
local -r func_name="${FUNCNAME[1]}"
local -r stack_size="${#__error_stack_key[@]}"
if [ "$stack_size" -eq "0" ]; then
return 0
fi
local -r stack_le_index=$(( stack_size - 1 ))
local -r stack_last_element="${__error_stack_key[$stack_le_index]}"
if [ "$stack_last_element" = "$func_name" ]; then
if [ "$stack_size" -eq "1" ]; then
if [ "$__base_error_mode" -eq "1" ]; then
set -e
else
set +e
fi
__error_stack_key=()
__error_stack_value=()
return 0
fi
if [ "${__error_stack_value[$stack_le_index]}" -eq "1" ]; then
set +e
else
set -e
fi
unset '__error_stack_key[-1]'
unset '__error_stack_value[-1]'
else
if [ "${__error_stack_value[$stack_le_index]}" -eq "1" ]; then
set -e
else
set +e
fi
fi
return 0
}
# __defer_invoke()
#
# Modifies Variables:
# * __defer_stack_key
# * __defer_stack_value
# Modifies State:
# * Potentially (user defined)
#
# Should be called when a function exits. Checks to see
# if the function exiting had any actions to run, and cleans them up.
__defer_invoke() {
local -r func_name="${FUNCNAME[1]}"
local stack_size="${#__defer_stack_key[@]}"
if [ "$stack_size" -eq 0 ]; then
return 0
fi
local stack_le_index=$(( stack_size - 1 ))
local stack_last_element="${__defer_stack_key[$stack_le_index]}"
while [ "$stack_last_element" = "$func_name" ]; do
if [ "$stack_size" -eq 1 ]; then
${__defer_stack_value[0]}
__defer_stack_key=()
__defer_stack_value=()
return 0
fi
${__defer_stack_value[$stack_le_index]}
unset '__defer_stack_key[-1]'
unset '__defer_stack_value[-1]'
stack_le_index=$(( stack_le_index - 1 ))
stack_size=$(( stack_size - 1 ))
stack_last_element="${__defer_stack_key[$stack_le_index]}"
done
return 0
}
set -o functrace
__trapped_return_pre=$(__trap_read "RETURN")
if [ -z "$__trapped_return_pre" ]; then
__trap_write "__defer_invoke ; __pop_err_stack" "RETURN"
else
if [[ "$__trapped_return_pre" == '*;' ]]; then
__trap_write "${__trapped_return_pre} __defer_invoke ; __pop_err_stack" "RETURN"
else
__trap_write "${__trapped_return_pre} ; __defer_invoke ; __pop_err_stack" "RETURN"
fi
fi
# defer_ignore_errors()
#
# Modifies Variables:
# * __error_stack_key
# * __error_stack_value
# Modifies State:
# * Error Mode.
#
# sets the error mode to be off for the runtime of this function.
defer_ignore_errors() {
__push_error_stack "${FUNCNAME[1]}" "0"
}
# defer_guard_errors()
#
# Modifies Variables:
# * __error_stack_key
# * __error_stack_value
# Modifies State:
# * Error Mode.
#
# sets the error mode to be on for the runtime of this function.
defer_guard_errors() {
__push_error_stack "${FUNCNAME[1]}" "1"
}
# defer(command: String)
#
# Modifies Variables:
# * __defer_stack_key
# * __defer_stack_value
#
# defer a statement to be run at the end of the current function, can be called
# multiple times in the same function.
defer() {
__defer_stack_key=("${__defer_stack_key[@]}" "${FUNCNAME[1]}")
__defer_stack_value=("${__defer_stack_value[@]}" "$@")
}
else
# Already loaded, do nothing.
true ;
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment