diff --git a/README.md b/README.md index 147a7fd..b957c0e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,23 @@ -# bash-trap-stack -command stack for bash traps +# Bash Trap Stack +## Purpose +The traps.sh shell script allows you to create a stack of commands to execute for trap. +## Commands +* traps_start - registers the exit trap +* traps_push $@ - pushes $@ as one entry to the stack +* traps_pop - pops one entry from the stack and execute +* traps_drop - pops one entry from the stack and do nothing +* traps_isEmpty - returns 0 if empty, returns 1 if not empty +* traps_stop - unregisters the exit trap if there is nothing on the stack +* traps_cancel - unregisters the exit trap and remove all entries from the stack +* traps_exit - unregisters the exit map and pops all entries from the stack +* traps_diag - print the stack on pipe 1 +## User Variables +* TRAPS_DEBUG=0 - provides more verbose output on pipe 2 +* TRAPS_SIGNAL=EXIT - controls the trap signal +## Typical Sequence +* traps_start +* traps_push echo "test" +* traps_pop +* traps_stop +## Notes +You can use *set -eE* along with *TRAPS_SIGNAL=ERR* before you start the trap stack to get the function and line number that triggered the trap. \ No newline at end of file diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..cab88bd --- /dev/null +++ b/test.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# SPDX-License-Identifier: MIT +# Copyright (C) 2022 Da Xue + +. traps.sh + +TRAPS_DEBUG=1 + +function traps_test { + traps_start + traps_push "echo" "traps 1" "test 1" "drop" + traps_push "echo" "traps 2" "test 2" "pop" + traps_push "echo" "traps 3" "test 3" "drop" + traps_push "echo" "traps 2" "test 2" "pop" + traps_diag + echo + traps_isEmpty && echo "Empty" || echo "Not Empty" + echo + traps_drop + traps_diag + echo + traps_pop + traps_diag + echo + traps_drop + traps_diag + echo + traps_pop + traps_diag + echo + traps_stop + echo + traps_start + traps_cancel + echo + traps_pop && echo "FAILED" || true + traps_diag + echo + traps_cancel && echo "FAILED" || true + traps_diag + echo + set -eE + TRAPS_SIGNAL=ERR + traps_start + traps_push echo "test" + false +} + +if [ "${BASH_SOURCE[0]##*/}" = "${0##*/}" ]; then + traps_test +fi \ No newline at end of file diff --git a/traps.sh b/traps.sh new file mode 100755 index 0000000..8ef9d40 --- /dev/null +++ b/traps.sh @@ -0,0 +1,149 @@ +#!/bin/bash + +# SPDX-License-Identifier: MIT +# Copyright (C) 2022 Da Xue + +TRAPS_DEBUG=0 +TRAPS_SIGNAL=EXIT + +declare -a TRAPS_PARAMS +declare -a TRAPS_LENGTHS +TRAPS_LENGTH= + +function traps_start { + if [ -z "$TRAPS_LENGTH" ]; then + TRAPS_LENGTH=0 + if [ "$TRAPS_SIGNAL" = "ERR" ]; then + trap 'traps_exit "$FUNCNAME" "$LINENO"' $TRAPS_SIGNAL + else + trap traps_exit $TRAPS_SIGNAL + fi + else + if [ "$TRAPS_DEBUG" -eq 1 ]; then + echo "$FUNCNAME: duplicate call" >&2 + fi + return 1 + fi +} + +function traps_isEmpty { + if [ -z "$TRAPS_LENGTH" ]; then + if [ "$TRAPS_DEBUG" -eq 1 ]; then + echo "$FUNCNAME: traps not started" >&2 + fi + return 2 + fi + if [ $TRAPS_LENGTH -gt 0 ]; then + return 1 + fi + return 0 +} + +function traps_push { + if [ -z "$TRAPS_LENGTH" ]; then + if [ "$TRAPS_DEBUG" -eq 1 ]; then + echo "$FUNCNAME: traps not started" >&2 + fi + return 1 + fi + TRAPS_PARAMS+=("$@") + TRAPS_LENGTHS+=($#) + ((TRAPS_LENGTH=TRAPS_LENGTH+1)) +} + +function traps_pop { + if [ -z "$TRAPS_LENGTH" ]; then + if [ "$TRAPS_DEBUG" -eq 1 ]; then + echo "$FUNCNAME: traps not started" >&2 + fi + return 1 + elif [ $TRAPS_LENGTH -gt 0 ]; then + local trap_length=${TRAPS_LENGTHS[@]: -1} + local trap_cmd=(${TRAPS_PARAMS[@]: -$trap_length}) + local traps_params_end=$((${#TRAPS_PARAMS[@]}-trap_length)) + TRAPS_PARAMS=("${TRAPS_PARAMS[@]:0:$traps_params_end}") + TRAPS_LENGTH=$((TRAPS_LENGTH-1)) + TRAPS_LENGTHS=(${TRAPS_LENGTHS[@]:0:$TRAPS_LENGTH}) + if [ "$TRAPS_DEBUG" -eq 1 ]; then + echo "$FUNCNAME: ${trap_cmd[@]}" >&2 + fi + "${trap_cmd[@]}" + else + if [ "$TRAPS_DEBUG" -eq 1 ]; then + echo "$FUNCNAME: traps empty" >&2 + fi + return 1 + fi +} + +function traps_drop { + if [ -z "$TRAPS_LENGTH" ]; then + if [ "$TRAPS_DEBUG" -eq 1 ]; then + echo "$FUNCNAME: traps not started" >&2 + fi + return 1 + elif [ $TRAPS_LENGTH -gt 0 ]; then + local trap_length=${TRAPS_LENGTHS[@]: -1} + local traps_params_end=$((${#TRAPS_PARAMS[@]}-trap_length)) + TRAPS_PARAMS=("${TRAPS_PARAMS[@]:0:$traps_params_end}") + TRAPS_LENGTH=$((TRAPS_LENGTH-1)) + TRAPS_LENGTHS=(${TRAPS_LENGTHS[@]:0:$TRAPS_LENGTH}) + else + if [ "$TRAPS_DEBUG" -eq 1 ]; then + echo "$FUNCNAME: traps empty" >&2 + fi + return 1 + fi +} + + +function traps_stop { + if [ "$TRAPS_LENGTH" = "0" ]; then + TRAPS_LENGTH= + trap - $TRAPS_SIGNAL + else + if [ "$TRAPS_DEBUG" -eq 1 ]; then + echo "$FUNCNAME: registered traps" >&2 + traps_diag >&2 + fi + return 1 + fi +} + +function traps_cancel { + if [ -z "$TRAPS_LENGTH" ]; then + if [ "$TRAPS_DEBUG" -eq 1 ]; then + echo "$FUNCNAME: traps not started" >&2 + fi + return 1 + fi + TRAPS_PARAMS=() + TRAPS_LENGTHS=() + TRAPS_LENGTH= + trap - $TRAPS_SIGNAL +} + +function traps_exit { + if [ "$TRAPS_DEBUG" -eq 1 ]; then + if [ "$TRAPS_SIGNAL" = "ERR" ]; then + echo "$FUNCNAME: $TRAPS_SIGNAL IN $1 LINE $2" >&2 + fi + fi + trap - $TRAPS_SIGNAL + if [ -z "$TRAPS_LENGTH" ]; then + if [ "$TRAPS_DEBUG" -eq 1 ]; then + echo "$FUNCNAME: traps not initiated" >&2 + fi + return 1 + fi + while [ "$TRAPS_LENGTH" -gt 0 ]; do + traps_pop + done + return 1 +} + +function traps_diag { + echo "$FUNCNAME params: ${TRAPS_PARAMS[@]}" + echo "$FUNCNAME lengths: ${TRAPS_LENGTHS[@]}" + echo "$FUNCNAME length: $TRAPS_LENGTH" +}