Last active
February 20, 2022 01:47
-
-
Save Mythra/cf08290d103cc42d9caec1846364c716 to your computer and use it in GitHub Desktop.
Defer but in bash
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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