diff --git a/seqTemplate.sh b/seqTemplate.sh new file mode 100755 index 0000000..b03bcdd --- /dev/null +++ b/seqTemplate.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +readonly toolName=mytool + +# Get script working directory +# (when called from a different directory and even when called via symlink) +readonly sq_dir="$(cd "$(dirname -- "$(realpath ${BASH_SOURCE[0]})")" >>/dev/null 2>&1 && pwd)" +readonly sq_scriptFile=$(basename -- $0) +readonly sq_scriptName=${sq_scriptFile%%.*} +readonly sq_configFileName="${sq_scriptName}.cfg" +readonly sq_configFileTemplate="$sq_dir/${sq_configFileName}.example" +sq_aptOpt= +sq_config=0 + +step_config() { + ## Called once before executing steps. + ## e.g. to source a config file manually: + #. "$sq_config_FILE" + + ## or to use sequencer api with profile config file support: + #initSeqConfig -p "$sq_scriptName" "$sq_configFileTemplate" + + ## or to use sequencer api with global config file: + #initSeqConfig "$sq_configFileName" "$sq_configFileTemplate" + #if [ $? -eq 0 ] ; then + # sq_config=1 + #else + # # End if no configuration file exists + # [ $DRY -eq 0 ] && return -1 + #fi + + ## Apt cmdline option to suppress user interaction + [ $QUIET -ne 0 ] && sq_aptOpt="-y" + + ## Return of non zero value will abort the sequence + return 0 +} + +step_1_info() { echoinfoArgs "[OPTIONS]"; echo "My custom step"; } +step_1_alias() { echo "begin"; } +step_1() { + echo "Doing something for step $1 ..." + echo "Command line arguments starting with argument 2: $@" + # Use exe for regular command + # Use exep "command" for commands containing pipes or redirects + exe ls + exep "dmesg | head" +} + +VERSION_SEQREV=16 +. /usr/local/bin/sequencer.sh diff --git a/sequencer.sh b/sequencer.sh index 99c151d..db0bd2f 100755 --- a/sequencer.sh +++ b/sequencer.sh @@ -12,7 +12,7 @@ set -o pipefail # Turn on traces, useful while debugging but commented out by default # set -o xtrace -## Globals +## Globals { readonly _sqr_version=16 readonly _sqr_versionMajor=0 @@ -44,9 +44,12 @@ set -o pipefail _sqr_debug=0 _sqr_dry=0 _sqr_verbose=0 + _sqr_errno=0 + + sqr_editor= # Colors - col_black= ; [ -t 1 ] && col_black='\033[0;30m' + col_black= ; [ -t 1 ] && col_black='\033[0;30m' col_darkgrey= ; [ -t 1 ] && col_darkgrey='\033[1;30m' col_red= ; [ -t 1 ] && col_red='\033[0;31m' col_lightred= ; [ -t 1 ] && col_lightred='\033[1;31m' @@ -95,7 +98,7 @@ set -o pipefail --) shift && break ;; -a) - appendText=1 + appendText=1 shift ;; -e) outp='/dev/stderr' @@ -119,7 +122,7 @@ set -o pipefail fi if (( ! appendText )) ; then - printf " %3s " "[${log_level}]" >${outp} + printf " %3s " "[${log_level}]" >${outp} printf "%s" "${log_line}" >${outp} else # +3 : "[] " @@ -137,9 +140,10 @@ set -o pipefail info () { [[ "${LOG_LEVEL:-0}" -ge 3 ]] && sqr::log "i" "" "${@}"; true; } debug () { [[ "${LOG_LEVEL:-0}" -ge 4 ]] && sqr::log "dbug" "${col_lightpurple}" "${@}"; true; } - # internal printf same loglevel as info + # internal print(s) same loglevel as error # shellcheck disable=SC2059 # don't use variables in format - sqr::print () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && printf "$@"; true; } + sqr::printf () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && printf "$@"; true; } + sqr::echo () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && echo "$@"; true; } sqr::debugPause() { if (( _sqr_debug )) ; then set +o xtrace; else true; fi @@ -173,7 +177,7 @@ set -o pipefail tput sgr0 return 0 ;; *) - tput setaf ${1:-} ;; + tput setaf "${1:-}" ;; esac case "${2:-}" in @@ -216,15 +220,18 @@ set -o pipefail # trap 'sqr::error_report "${FUNCNAME:-.}" ${LINENO}' ERR } +# check if run as root +root() { + [[ $(id -u) -eq 0 ]] +} # check if there is another PID other than this one running() { pidof -o %PPID -x "${0##*/}">>/dev/null } - # exists [-f] [--] [ELEMENT] # [ELEMENT] # : either a variable name or -# -f : function +# -f : a function exists() { sqr::debugPause local func= @@ -245,7 +252,6 @@ exists() { [[ -n "${!1:-}" ]] fi } - # interactive # Started with -q to use defaults for confirmations interactive() { @@ -275,7 +281,7 @@ verbose() { ### interactive # confirm [OPTIONS] [--] [QUESTION] # Default (empty character) = no -# +# # [OPTIONS] # -f : interactive even if quiet # -n : no input help @@ -308,7 +314,9 @@ confirm() { (( noHelp )) && inputHelp= if interactive || (( force )) ; then read -r -p "${1:-} ${inputHelp}" -n 1 - echo "" + # Needed when read stops after one character (-n 1) + # Add a newline only if input is not empty (just enter is pressed) + [[ -z "$REPLY" ]] || sqr::echo "" else REPLY='' fi @@ -345,7 +353,7 @@ ask() { done local answer= if [[ -n "${2:-}" ]] ; then - ! interactive && printf '%s\n' "${2}" && sqr::debugContinue && return 0 + ! interactive && printf '%s\n' "${2}" && sqr::debugContinue && return 0 read ${hidden?} -r -p "${1:-"User input"} ($2) " answer else read ${hidden?} -r -p "${1:-"User input"} " answer @@ -361,25 +369,111 @@ ask() { [[ -n "${answer}" ]] fi } +# Escaping non-printable characters with the proposed POSIX $'' syntax +escpath() { + printf "%q" "$*" +} +# saveReturn +# Function returns with in case step wants additional evaluation +saveReturn() { + if [[ "${1:-"0"}" -ne 0 ]] ; then + _sqr_errno=${1} + fi + return "${_sqr_errno}" +} + +# getReturn +# Returns latest saved $_sqr_errno +getReturn() { + return "${_sqr_errno}" +} + +# endReturn [-f] [-o ERRORCODE] [MESSAGE] +# -f : force exit with $_sqr_errno without user input +# -o : override and check given error code +# MESSAGE : Custom error message +# +endReturn() { + local arg + local forceExit=0 + local errorCode=${_sqr_errno} + local endMessage="" + + for arg in "$@" ; do + case "$1" in + -f) + forceExit=1 + shift + ;; + -o) + shift + local rex='^[-]*[0-9]+$' + # Check if string is a number or alias + if [[ "${1:?}" =~ ${rex} ]] ; then + errorCode="${1:?}" + else + warning " [W] Ignoring invalid error code: $1" + fi + shift + ;; + "") + break + ;; + *) + endMessage="$*" + break + ;; + esac + done + + if ( [[ ${errorCode} -ne 0 ]] && ! interactive ) \ + || [[ ${errorCode} -ne 0 && $forceExit -ne 0 ]] ; then + sqr::echo + if [[ -n "${endMessage}" ]] ; then + error -e "${endMessage}" + error -e -a "Sequence stopped" + else + error -e "Return value ${errorCode} detected." + error -e -a "Sequence stopped" + fi + exit "${errorCode}" + fi + if [[ "${errorCode}" -ne 0 ]] ; then + sqr::echo + if [ "${endMessage}" != "" ]; then + error -e "${endMessage}" + else + error -e "Return value ${errorCode} detected." + fi + if confirm -y "End sequence"; then + error -e "Sequence stopped" + exit "${errorCode}"; + else + # reset saved error code if user chooses to continue + _sqr_errno=0 + sqr::echo + warning "Continuing sequence..." + fi + fi +} # listSteps [FILTER STRING] # [FILTER STRING] # show only steps and aliases starting with [FILTER STRING] listSteps() { + local locAlias= local aList=() local aSearch="${1:-}" - local locAlias= - for ((i=1; i<=${_sqr_stepMax}; i++)); do + for ((i=1; i<=_sqr_stepMax; i++)); do # Display step reference in help if step function exists - exists -f step_${i} || continue + exists -f "step_${i}" || continue # Display alias if exists - if exists -f step_${i}_alias ; then - step_${i}_alias - locAlias=("$ALIAS") + if exists -f "step_${i}_alias" ; then + locAlias="$("step_${i}_alias")" else - locAlias=("$i") + locAlias="$i" fi if [ -z "$aSearch" ]; then @@ -389,7 +483,7 @@ listSteps() { fi done - [ ${#aList[@]} -ne 0 ] && printf '%s\n' "${aList[@]}" + [ ${#aList[@]} -ne 0 ] && printf '%s\n' "${aList[*]}" } # showVersion @@ -404,9 +498,9 @@ exe() { dry && printf -- '--' if dry || verbose ; then (set -x; : "$@") - fi + fi - if ! dry ; then + if ! dry ; then "$@" fi } @@ -420,11 +514,17 @@ exep() { printf '++ : %s\n' "$*" fi - if ! dry ; then + if ! dry ; then bash -c "$*" fi } +# Used as editor if no system editor could be found +sqr::noEditor() { + error "No editor found (\$EDITOR,\"/etc/alternatives\",nano,vi)" + error -a "Cannot open: $*" +} + sqr::main() { # options check for arg in "$@" ; do @@ -463,7 +563,17 @@ sqr::main() { trap 'sqr::error_report "${FUNCNAME:-.}" ${LINENO}' ERR fi - sqr::print 'Running...\n' + # Determine system default editor + # Change with update-alternatives --config editor + sqr_editor="$(realpath -eq "/etc/alternatives/editor")" + ## Various fallbacks + [ -z "${sqr_editor}" ] && sqr_editor="$EDITOR" + [ -z "${sqr_editor}" ] && sqr_editor="$(command -v nano)" + [ -z "${sqr_editor}" ] && sqr_editor="$(command -v vi)" + ## Fall back to error message + [ -z "${sqr_editor}" ] && sqr_editor="sqr::noEditor" + + sqr::printf 'Running...\n' seq_config 2>/dev/null || true step_1 "$@" }