I hate it when I have a complex bash script that I need to run and it has lots of steps in it, any one of which can fail. However, NOT re-running all the previously succeeded steps seems efficient. And in some circumstances, running any previous steps prove catastrophic.
I could just run the script and hope it works, and when it fails, fix the failing bits, while removing the already run code, but keeping the variable declarations.
Or I could source this bash script. ;)
#!/bin/bash
set -o functrace
set -o history
set +o noclobber
shopt -s extdebug
[[ ${BASH_VERSINFO[0]} -lt 4 ]] && {
echo "BASH Version 4 or higher required by idempotency.sh"
exit 1
}
idem_debug() {
[[ "${DEBUG}" != "" ]] && echo "$@"
}
idem_is_command() {
local cmd="$( echo "$1" | awk '{print $2}' )"
command -v "$cmd" >/dev/null 2>&1 && return 0
[[ -z "$( which "$cmd" 2>/dev/null)" ]] && return 1
return 0
}
idem_debug "idempotency using source file '${0}'"
idem_dir="${0%\/*}"
[[ "${idem_dir}" == "" ]] || [[ "${idem_dir}" == "$0" ]] && {
idem_dir="."
}
idem_debug "idempotency using source dir '${idem_dir}'"
idem_file="${idem_dir}/.${0/#*\/}.idempotent"
idem_debug "idempotency using history file '${idem_file}'"
idem_trap() {
local cmd=$(history 1)
idem_debug "idempotency checking command '${cmd}' subpart '${BASH_COMMAND}'"
[[ "${idem_last}" == "${cmd}" ]] && {
idem_debug "idempotency not re-checking subshell/pipe breakdown for '${BASH_COMMAND}'"
return 0 # return ok
}
! idem_is_command "${cmd}" && {
idem_debug "idempotency running non-command '${cmd}'"
return 0
}
export idem_last="${cmd}"
[[ -f "${idem_file}" ]] && grep -qF "${cmd}" ${idem_file} && {
idem_debug "idempotency not running previously run command '${cmd}'"
return 1 # don't run the command again
}
echo "${cmd}" >> ${idem_file}
idem_debug "idempotency running command '${cmd}'"
return 0
}
trap idem_trap DEBUG
It uses the preexec hack idea from glyph, and I need to write a bunch of tests for it.