Squashed commit of the following:

commit 61bf734812cb62ba6e0ec224bc15f7928705a8a2
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Thu Oct 21 15:44:21 2021 -0400

    Major overhaul continued

     - rename templates
     - add checks utilities
     - add new array utilities
     - rename files
     - add assorted utilities
     - improve documentation

commit 546178fff3b526f492eb0eeffc63f79537e75de3
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Wed Oct 20 16:31:14 2021 -0400

    Update conventions

commit f6d0642f85518efda9c5d8472b99d1c14163e381
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Wed Oct 20 09:47:09 2021 -0400

    minor formatting changes

commit 2217612b55e3f9faf803a2d0c937ea2261206505
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Tue Oct 19 17:59:09 2021 -0400

    add new functions

commit 347ba7aa738dcd6a5ad9d70886b38da3a17dc89e
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Tue Oct 19 12:06:44 2021 -0400

    major overhaul

    - Add standaloneTemplate.sh
    - Rework README
    - Refactor inline documentation
    - Enforce coding standards
    - Remove CSV utilities
    - Add new array utilities
    - add _useGNUutils_
    - more ...

commit cd8e0d49aef25eeaf6b3e71a3c9e1f29ab9b06f5
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Sun Oct 17 09:56:08 2021 -0400

    Add debug functions

commit f7c5c0a3d19815dcc6ba80b5f5a2ebb77ef88b07
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Sat Oct 16 21:10:01 2021 -0400

    add new array functions

    _joinArray_, _isEmptyArray_, _sortArray_, _reverseSortArray_, and _mergearrays_

commit d8bc3d8cabdbcee3c479f97b43a45bdfe3bdafe0
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Fri Oct 15 17:27:12 2021 -0400

    add _columnize_

commit 2fd2ae9435f476bc3968c3eb0d793db4bf1d9eaf
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Mon Oct 11 22:17:45 2021 -0400

    _progressBar_: Fix unbound variable

commit e8933d15fc955a1acc665e9a081f131e681855d5
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Sun Oct 10 11:50:42 2021 -0400

    _alert_: header now underlined

commit c9ce894361dec7d3513c038794a155519baf26bc
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Tue Oct 5 09:49:42 2021 -0400

    _alert_: line numbers to gray

commit 4aaddd336ce613f629a7e6a62ef3b27ffc24d22d
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Fri Oct 8 15:05:20 2021 -0400

    _usage_ to stdout

commit e2372fc3122ec1f20acc27f04d29b3785f014e25
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Tue Oct 5 09:38:26 2021 -0400

    _setPATH_: remove unneeded logic

commit e60c75b6c954ac4bd146e2758252168027b9a43d
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Tue Oct 5 09:25:38 2021 -0400

    _findSource_: bugfix

commit 0e84912e1ccd7203e5beff9f8737f8374f4aa5d8
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Thu Sep 30 16:29:25 2021 -0400

    add requirements to documentation

commit 2c24843e3ada591e1868a94416e40b5ac0aa4994
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Thu Sep 30 15:34:10 2021 -0400

    _uniqueFilename_: improve extension handling

commit 08bc2dfdcc8632efee9179e9c960a574fc17cf0c
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Mon Sep 27 15:13:53 2021 -0400

    improve hooks script

commit 641918f1559d3b3aa38a9bbdf418938b2b81c176
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Fri Sep 24 08:16:52 2021 -0400

    _inArry_: case insensitivity

commit eae10f170680540fdb4a1222add7e54f8785ea63
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Mon Sep 20 18:31:44 2021 -0400

    clean up alerting

commit 700acd56f57fd57db84ef0e232ef41cdd7aee43c
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Mon Sep 20 18:22:11 2021 -0400

    refactor _execute_

commit d893f86900a9fed9d91a0c9cc06c13b6b34d9926
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Mon Sep 20 18:19:18 2021 -0400

    'fatal' replaces 'die'

commit 3326857bf127bef36cd9982246aa5b826d796d0a
Author: Nathaniel Landau <nate@natelandau.com>
Date:   Fri Sep 17 08:29:50 2021 -0400

    _execute_: ensure quiet and verbose work together
This commit is contained in:
Nathaniel Landau
2021-10-21 16:03:27 -04:00
parent 1e2d2ff287
commit 8bb89541e8
35 changed files with 5769 additions and 2791 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
scratch
tmp
*.DS_Store
.cache

View File

@@ -1,8 +1,8 @@
This folder contains git hook scripts which will be executed on the client side before/after certain git actions are completed.
This folder contains a git pre-commit script which will be executed on the client side before files are committed to the repository. This script provides automated linting and testing of multiple file types.
## Usage
To install these hooks, create a symlink to `/path/to/repo/.git/hooks` directory.
To install the hook, create a symlink into the `/path/to/repo/.git/hooks` directory.
```bash
ln -s "$(git rev-parse --show-toplevel)/.hooks/pre-commit.sh" "$(git rev-parse --show-toplevel)/.git/hooks/pre-commit"

View File

@@ -17,7 +17,7 @@ _mainScript_() {
STOP_WORD_FILE="${HOME}/.git_stop_words"
GIT_DIFF_TEMP="${TMP_DIR}/diff.txt"
if cat "${STOP_WORD_FILE}" | grep . | grep -v '# ' >"${TMP_DIR}/pattern_file.txt"; then
if [ -f "${STOP_WORD_FILE}" ]; then
if [[ $(basename "${STOP_WORD_FILE}") == "$(basename "${1}")" ]]; then
debug "Don't check stop words file for stop words. Skipping $(basename "${1}")"
@@ -25,16 +25,18 @@ _mainScript_() {
fi
debug "Checking for stop words"
# remove blank lines and comments from stopwords file
# remove blank lines from stopwords file
cat "${STOP_WORD_FILE}" | sed '/^$/d' >"${TMP_DIR}/pattern_file.txt"
# Add diff to a temporary file
git diff --cached -- "${1}" | grep '^+' >"${GIT_DIFF_TEMP}"
if grep --file="${TMP_DIR}/pattern_file.txt" "${GIT_DIFF_TEMP}"; then
error "Found git stop word in '$(basename "${1}")'"
_safeExit_ 1
fi
else
debug "Could not find git stopwords file expected at '${STOP_WORD_FILE}'. Or it was empty. Continuing..."
debug "Could not find git stopwords file expected at '${STOP_WORD_FILE}'. Continuing..."
fi
}
@@ -91,22 +93,31 @@ _mainScript_() {
fi
elif command -v yamllint >/dev/null; then
debug "Linting YAML File"
if ! yamllint "${1}"; then
error "Error in ${1}"
if [ -f "$(git rev-parse --show-toplevel)/.yamllint.yml" ]; then
if ! yamllint -c "$(git rev-parse --show-toplevel)/.yamllint.yml" "${1}"; then
error "YAML Error in ${1}"
_safeExit_ 1
else
success "yamllint passed: '${1}'"
fi
else
notice "No YAML linter installed. Continuing..."
if ! yamllint "${1}"; then
error "YAML Error in ${1}"
_safeExit_ 1
else
success "yamllint passed: '${1}'"
fi
fi
else
notice "No YAML linter installed. Continuiing..."
fi
}
_lintShellscripts_() {
if command -v shellcheck >/dev/null; then
debug "Linting shellscript: ${1}"
if ! shellcheck --exclude=2016,2059,2001,2002,2148,1090,2162,2005,2034,2154,2086,2155,2181,2164,2120,2119,1083,1117,2207 "${1}"; then
error "Error in ${file}"
if ! shellcheck --exclude=2016,2059,2001,2002,2148,1090,2162,2005,2034,2154,2086,2155,2181,2164,2120,2119,1083,1117,2207,1091 "${1}"; then
error "Error in ${1}"
_safeExit_ 1
else
success "shellcheck passed: '${1}'"
@@ -133,7 +144,47 @@ _mainScript_() {
unset filename
}
_lintAnsible_() {
if ! command -v ansible-lint >/dev/null; then
notice "Found Ansible files but ansible-lint is not available. Continuing..."
return 0
elif [[ "$(basename ${1})" =~ (^\.|^requirements|j2|vault\.yml|variables|meta|defaults?|inventory) ]]; then
# Don't lint files that are not Ansible playbooks
debug "won't ansible lint: ${1}"
return 0
elif [[ ${1} =~ /(handlers|vars/|defaults/|meta/|molecule/|templates/|files/)/ ]]; then
# Don't lint in directory names that are not likely to contain Ansible playbooks
debug "Won't ansible lint: ${1}"
return 0
fi
ANSIBLE_COMMAND="ansible-lint -vv --parseable-severity"
if [ -f "$(git rev-parse --show-toplevel)/.ansible-lint.yml" ]; then
ANSIBLE_COMMAND="ansible-lint -p -c $(git rev-parse --show-toplevel)/.ansible-lint.yml"
fi
debug "Linting ansible file: ${1}"
if ! ${ANSIBLE_COMMAND} "${1}"; then
error "Ansible-lint error"
_safeExit_ 1
else
success "ansible-lint passed: ${1}"
fi
}
# RUN SCRIPT LOGIC
# Attempt to discern if we are working on an repo that contains ansible files
IS_ANSIBLE_REPO=false
if find "$(git rev-parse --show-toplevel)" -type f -mindepth 1 -maxdepth 1 \
-name "inventory.yml" \
-o -name "ansible.cfg" \
-o -name ".ansible-lint.yml" &>/dev/null; then
IS_ANSIBLE_REPO=true
fi
_ignoreSymlinks_
while read -r STAGED_FILE; do
@@ -142,17 +193,18 @@ _mainScript_() {
_gitStopWords_ "${STAGED_FILE}"
if [[ "${STAGED_FILE}" =~ \.(yaml|yml)$ ]]; then
if [[ ${STAGED_FILE} =~ \.(yaml|yml)$ ]]; then
_lintYAML_ "${STAGED_FILE}"
if [ "${IS_ANSIBLE_REPO}" = true ]; then
_lintAnsible_ "${STAGED_FILE}"
fi
if [[ "${STAGED_FILE}" =~ \.(bash|sh)$ ]]; then
fi
if [[ ${STAGED_FILE} =~ \.(bash|sh)$ || "$(head -n 1 "${STAGED_FILE}")" =~ ^#!.*bash$ ]]; then
_lintShellscripts_ "${STAGED_FILE}"
fi
if [[ "${STAGED_FILE}" =~ \.(sh|bash|bats|zsh)$ ]]; then
if [[ ${STAGED_FILE} =~ \.(sh|bash|bats|zsh)$ || "$(head -n 1 "${STAGED_FILE}")" =~ ^#!.*bash$ ]]; then
_BATS_ "${STAGED_FILE}"
fi
else
fatal "${STAGED_FILE} does not exist"
fi
done < <(git diff --cached --name-only --line-prefix="$(git rev-parse --show-toplevel)/")
@@ -160,22 +212,22 @@ _mainScript_() {
} # end _mainScript_
# ################################## Flags and defaults
# Script specific
# Script specific
# Common
LOGFILE="${HOME}/logs/$(basename "$0").log"
QUIET=false
LOGLEVEL=ERROR
VERBOSE=false
FORCE=false
DRYRUN=false
declare -a ARGS=()
NOW=$(LC_ALL=C date +"%m-%d-%Y %r") # Returns: 06-14-2015 10:34:40 PM
DATESTAMP=$(LC_ALL=C date +%Y-%m-%d) # Returns: 2015-06-14
HOURSTAMP=$(LC_ALL=C date +%r) # Returns: 10:34:40 PM
TIMESTAMP=$(LC_ALL=C date +%Y%m%d_%H%M%S) # Returns: 20150614_223440
LONGDATE=$(LC_ALL=C date +"%a, %d %b %Y %H:%M:%S %z") # Returns: Sun, 10 Jan 2016 20:47:53 -0500
GMTDATE=$(LC_ALL=C date -u -R | sed 's/\+0000/GMT/') # Returns: Wed, 13 Jan 2016 15:55:29 GMT
# Common
LOGFILE="${HOME}/logs/$(basename "$0").log"
QUIET=false
LOGLEVEL=ERROR
VERBOSE=false
FORCE=false
DRYRUN=false
declare -a ARGS=()
NOW=$(LC_ALL=C date +"%m-%d-%Y %r") # Returns: 06-14-2015 10:34:40 PM
DATESTAMP=$(LC_ALL=C date +%Y-%m-%d) # Returns: 2015-06-14
HOURSTAMP=$(LC_ALL=C date +%r) # Returns: 10:34:40 PM
TIMESTAMP=$(LC_ALL=C date +%Y%m%d_%H%M%S) # Returns: 20150614_223440
LONGDATE=$(LC_ALL=C date +"%a, %d %b %Y %H:%M:%S %z") # Returns: Sun, 10 Jan 2016 20:47:53 -0500
GMTDATE=$(LC_ALL=C date -u -R | sed 's/\+0000/GMT/') # Returns: Wed, 13 Jan 2016 15:55:29 GMT
# ################################## Custom utility functions
_setPATH_() {
@@ -190,37 +242,65 @@ _setPATH_() {
done
for NEWPATH in "${NEWPATHS[@]}"; do
if ! echo "$PATH" | grep -Eq "(^|:)${NEWPATH}($|:)"; then
if [ -d "${NEWPATH}" ]; then
if ! echo "${PATH}" | grep -Eq "(^|:)${NEWPATH}($|:)"; then
PATH="${NEWPATH}:${PATH}"
debug "Added '${tan}${NEWPATH}${purple}' to PATH"
debug "Added '${NEWPATH}' to PATH"
else
debug "_setPATH_: '${NEWPATH}' already exists in PATH"
fi
else
debug "_setPATH_: can not find: ${NEWPATH}"
fi
done
}
# ################################## Common Functions for script template
# Colors
_setColors_() {
# DESC: Sets colors use for alerts.
# ARGS: None
# OUTS: None
# USAGE: echo "${blue}Some text${reset}"
if tput setaf 1 &>/dev/null; then
bold=$(tput bold)
white=$(tput setaf 7)
underline=$(tput smul)
reverse=$(tput rev)
reset=$(tput sgr0)
purple=$(tput setaf 171)
red=$(tput setaf 1)
green=$(tput setaf 76)
tan=$(tput setaf 3)
yellow=$(tput setaf 3)
if [[ $(tput colors) -ge 256 ]] 2>/dev/null; then
white=$(tput setaf 231)
blue=$(tput setaf 38)
underline=$(tput sgr 0 1)
else
yellow=$(tput setaf 11)
tan=$(tput setaf 3)
green=$(tput setaf 82)
red=$(tput setaf 1)
purple=$(tput setaf 171)
gray=$(tput setaf 250)
else
white=$(tput setaf 7)
blue=$(tput setaf 38)
yellow=$(tput setaf 3)
tan=$(tput setaf 3)
green=$(tput setaf 2)
red=$(tput setaf 1)
purple=$(tput setaf 13)
gray=$(tput setaf 7)
fi
else
bold="\033[4;37m"
white="\033[0;37m"
reset="\033[0m"
purple="\033[0;35m"
red="\033[0;31m"
green="\033[1;32m"
tan="\033[0;33m"
yellow="\033[0;33m"
blue="\033[0;34m"
underline="\033[4;37m"
fi
reverse=""
white="\033[0;37m"
blue="\033[0;34m"
yellow="\033[0;33m"
tan="\033[0;33m"
green="\033[1;32m"
red="\033[0;31m"
purple="\033[0;35m"
gray="\033[0;37m"
fi
}
_alert_() {
# DESC: Controls all printing of messages to log files and stdout.
@@ -239,26 +319,30 @@ _alert_() {
local message="${2}"
local line="${3:-}" # Optional line number
if [[ -n "${line}" && "${alertType}" =~ ^(fatal|error) && "${FUNCNAME[2]}" != "_trapCleanup_" ]]; then
if [[ -n ${line} && ${alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
message="${message} (line: ${line}) $(_functionStack_)"
elif [[ -n "${line}" && "${FUNCNAME[2]}" != "_trapCleanup_" ]]; then
elif [[ -n ${line} && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
message="${message} (line: ${line})"
elif [[ -z "${line}" && "${alertType}" =~ ^(fatal|error) && "${FUNCNAME[2]}" != "_trapCleanup_" ]]; then
elif [[ -z ${line} && ${alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
message="${message} $(_functionStack_)"
fi
if [[ "${alertType}" =~ ^(error|fatal) ]]; then
if [[ ${alertType} =~ ^(error|fatal) ]]; then
color="${bold}${red}"
elif [ "${alertType}" = "warning" ]; then
elif [ "${alertType}" == "info" ]; then
color="${gray}"
elif [ "${alertType}" == "warning" ]; then
color="${red}"
elif [ "${alertType}" = "success" ]; then
elif [ "${alertType}" == "success" ]; then
color="${green}"
elif [ "${alertType}" = "debug" ]; then
elif [ "${alertType}" == "debug" ]; then
color="${purple}"
elif [ "${alertType}" = "header" ]; then
elif [ "${alertType}" == "header" ]; then
color="${bold}${tan}"
elif [[ "${alertType}" =~ ^(input|notice) ]]; then
elif [ ${alertType} == "notice" ]; then
color="${bold}"
elif [ ${alertType} == "input" ]; then
color="${bold}${underline}"
elif [ "${alertType}" = "dryrun" ]; then
color="${blue}"
else
@@ -268,7 +352,7 @@ _alert_() {
_writeToScreen_() {
("${QUIET}") && return 0 # Print to console when script is not 'quiet'
[[ ${VERBOSE} == false && "${alertType}" =~ ^(debug|verbose) ]] && return 0
[[ ${VERBOSE} == false && ${alertType} =~ ^(debug|verbose) ]] && return 0
if ! [[ -t 1 ]]; then # Don't use colors on non-recognized terminals
color=""
@@ -280,11 +364,13 @@ _alert_() {
_writeToScreen_
_writeToLog_() {
[[ "${alertType}" == "input" ]] && return 0
[[ "${LOGLEVEL}" =~ (off|OFF|Off) ]] && return 0
[ -z "${LOGFILE:-}" ] && LOGFILE="$(pwd)/$(basename "$0").log"
[ ! -d "$(dirname "${LOGFILE}")" ] && command mkdir -p "$(dirname "${LOGFILE}")"
[[ ! -f "${LOGFILE}" ]] && touch "${LOGFILE}"
[[ ${alertType} == "input" ]] && return 0
[[ ${LOGLEVEL} =~ (off|OFF|Off) ]] && return 0
if [ -z "${LOGFILE:-}" ]; then
LOGFILE="$(pwd)/$(basename "$0").log"
fi
[ ! -d "$(dirname "${LOGFILE}")" ] && mkdir -p "$(dirname "${LOGFILE}")"
[[ ! -f ${LOGFILE} ]] && touch "${LOGFILE}"
# Don't use colors in logs
if command -v gsed &>/dev/null; then
@@ -304,22 +390,27 @@ _alert_() {
_writeToLog_
;;
INFO | info | Info)
if [[ "${alertType}" =~ ^(die|error|fatal|warning|info|notice|success) ]]; then
if [[ ${alertType} =~ ^(error|fatal|warning|info|notice|success) ]]; then
_writeToLog_
fi
;;
NOTICE | notice | Notice)
if [[ ${alertType} =~ ^(error|fatal|warning|notice|success) ]]; then
_writeToLog_
fi
;;
WARN | warn | Warn)
if [[ "${alertType}" =~ ^(die|error|fatal|warning) ]]; then
if [[ ${alertType} =~ ^(error|fatal|warning) ]]; then
_writeToLog_
fi
;;
ERROR | error | Error)
if [[ "${alertType}" =~ ^(die|error|fatal) ]]; then
if [[ ${alertType} =~ ^(error|fatal) ]]; then
_writeToLog_
fi
;;
FATAL | fatal | Fatal)
if [[ "${alertType}" =~ ^(die|fatal) ]]; then
if [[ ${alertType} =~ ^fatal ]]; then
_writeToLog_
fi
;;
@@ -327,7 +418,7 @@ _alert_() {
return 0
;;
*)
if [[ "${alertType}" =~ ^(die|error|fatal) ]]; then
if [[ ${alertType} =~ ^(error|fatal) ]]; then
_writeToLog_
fi
;;
@@ -343,23 +434,35 @@ success() { _alert_ success "${1}" "${2:-}"; }
dryrun() { _alert_ dryrun "${1}" "${2:-}"; }
input() { _alert_ input "${1}" "${2:-}"; }
header() { _alert_ header "== ${1} ==" "${2:-}"; }
die() {
_alert_ fatal "${1}" "${2:-}"
_safeExit_ "1"
}
debug() { _alert_ debug "${1}" "${2:-}"; }
fatal() {
_alert_ fatal "${1}" "${2:-}"
_safeExit_ "1"
}
debug() { _alert_ debug "${1}" "${2:-}"; }
verbose() { _alert_ debug "${1}" "${2:-}"; }
_functionStack_() {
# DESC: Prints the function stack in use
# ARGS: None
# OUTS: Prints [function]:[file]:[line]
# NOTE: Does not print functions from the alert class
local _i
funcStackResponse=()
for ((_i = 1; _i < ${#BASH_SOURCE[@]}; _i++)); do
case "${FUNCNAME[$_i]}" in "_alert_" | "_trapCleanup_" | fatal | error | warning | notice | info | debug | dryrun | header | success) continue ;; esac
funcStackResponse+=("${FUNCNAME[$_i]}:$(basename ${BASH_SOURCE[$_i]}):${BASH_LINENO[_i - 1]}")
done
printf "( "
printf %s "${funcStackResponse[0]}"
printf ' < %s' "${funcStackResponse[@]:1}"
printf ' )\n'
}
_safeExit_() {
# DESC: Cleanup and exit from a script
# ARGS: $1 (optional) - Exit code (defaults to 0)
# OUTS: None
if [[ -d "${SCRIPT_LOCK:-}" ]]; then
if [[ -d ${SCRIPT_LOCK:-} ]]; then
if command rm -rf "${SCRIPT_LOCK}"; then
debug "Removing script lock"
else
@@ -367,7 +470,7 @@ _safeExit_() {
fi
fi
if [[ -n "${TMP_DIR:-}" && -d "${TMP_DIR:-}" ]]; then
if [[ -n ${TMP_DIR:-} && -d ${TMP_DIR:-} ]]; then
if [[ ${1:-} == 1 && -n "$(ls "${TMP_DIR}")" ]]; then
# Do something here to save TMP_DIR on a non-zero script exit for debugging
command rm -r "${TMP_DIR}"
@@ -401,7 +504,7 @@ _trapCleanup_() {
funcstack="'$(echo "$funcstack" | sed -E 's/ / < /g')'"
if [[ "${script##*/}" == "${sourced##*/}" ]]; then
if [[ ${script##*/} == "${sourced##*/}" ]]; then
fatal "${7:-} command: '${command}' (line: ${line}) [func: $(_functionStack_)]"
else
fatal "${7:-} command: '${command}' (func: ${funcstack} called at line ${linecallfunc} of '${script##*/}') (line: $line of '${sourced##*/}') "
@@ -454,23 +557,6 @@ _acquireScriptLock_() {
fi
}
_functionStack_() {
# DESC: Prints the function stack in use
# ARGS: None
# OUTS: Prints [function]:[file]:[line]
# NOTE: Does not print functions from the alert class
local _i
funcStackResponse=()
for ((_i = 1; _i < ${#BASH_SOURCE[@]}; _i++)); do
case "${FUNCNAME[$_i]}" in "_alert_" | "_trapCleanup_" | fatal | error | warning | notice | info | verbose | debug | dryrun | header | success | die) continue ;; esac
funcStackResponse+=("${FUNCNAME[$_i]}:$(basename ${BASH_SOURCE[$_i]}):${BASH_LINENO[$_i - 1]}")
done
printf "( "
printf %s "${funcStackResponse[0]}"
printf ' < %s' "${funcStackResponse[@]:1}"
printf ' )\n'
}
_parseOptions_() {
# Iterate over options
# breaking -ab into -a -b when needed and --foo=bar into --foo bar
@@ -547,7 +633,8 @@ _usage_() {
${bold}Options:${reset}
-h, --help Display this help and exit
--loglevel [LEVEL] One of: FATAL, ERROR, WARN, INFO, DEBUG, ALL, OFF (Default is 'ERROR')
--loglevel [LEVEL] One of: FATAL, ERROR, WARN, INFO, NOTICE, DEBUG, ALL, OFF
(Default is 'ERROR')
--logfile [FILE] Full PATH to logfile. (Default is '${HOME}/logs/$(basename "$0").log')
-n, --dryrun Non-destructive. Makes no permanent changes.
-q, --quiet Quiet (no output)
@@ -563,18 +650,46 @@ EOF
# ################################## INITIALIZE AND RUN THE SCRIPT
# (Comment or uncomment the lines below to customize script behavior)
trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' \
EXIT INT TERM SIGINT SIGQUIT
set -o errtrace # Trap errors in subshells and functions
set -o errexit # Exit on error. Append '||true' if you expect an error
set -o pipefail # Use last non-zero exit code in a pipeline
# shopt -s nullglob globstar # Make `for f in *.txt` work when `*.txt` matches zero files
IFS=$' \n\t' # Set IFS to preferred implementation
# set -o xtrace # Run in debug mode
set -o nounset # Disallow expansion of unset variables
# [[ $# -eq 0 ]] && _parseOptions_ "-h" # Force arguments when invoking the script
_parseOptions_ "$@" # Parse arguments passed to script
_makeTempDir_ "$(basename "$0")" # Create a temp directory '$TMP_DIR'
# _acquireScriptLock_ # Acquire script lock
_mainScript_ # Run the main logic script
_safeExit_ # Exit cleanly
trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT
# Trap errors in subshells and functions
set -o errtrace
# Exit on error. Append '||true' if you expect an error
set -o errexit
# Use last non-zero exit code in a pipeline
set -o pipefail
# Make `for f in *.txt` work when `*.txt` matches zero files
# shopt -s nullglob globstar
# Set IFS to preferred implementation
IFS=$' \n\t'
# Run in debug mode
# set -o xtrace
# Initialize color constants
_setColors_
# Disallow expansion of unset variables
set -o nounset
# Force arguments when invoking the script
# [[ $# -eq 0 ]] && _parseOptions_ "-h"
# Parse arguments passed to script
_parseOptions_ "$@"
# Create a temp directory '$TMP_DIR'
_makeTempDir_ "$(basename "$0")"
# Acquire script lock
# _acquireScriptLock_
# Run the main logic script
_mainScript_
# Exit cleanly
_safeExit_

View File

@@ -19,11 +19,18 @@
"scope": "shellscript",
"prefix": "_c",
"body": [
"# DESC:$1",
"# ARGS:\t\t${2:None}",
"# OUTS:\t\t${3:None}",
"# USAGE:$4",
"# NOTE:$0"
"# DESC:",
"#\t\t\t\t\t$1",
"# ARGS:",
"#\t\t\t\t\t$2",
"# OUTS:",
"#\t\t\t\t\t0 - Success",
"#\t\t\t\t\t1 - Failure",
"#\t\t\t\t\tstdout: $4",
"# USAGE:",
"#\t\t\t\t\t$4",
"# NOTES:",
"#\t\t\t\t\t$5"
],
"description": "Comment block for a function"
},
@@ -44,14 +51,21 @@
"prefix": "_f",
"body": [
"_${1:name}_() {",
"\t\t# DESC:$2",
"\t\t# ARGS:\t\t${3:None}",
"\t\t# OUTS:\t\t${4:None}",
"\t\t# USAGE:$5",
"\t\t# NOTE:$6",
"\t\t# DESC:",
"\t\t#\t\t\t\t\t$2",
"\t\t# ARGS:",
"\t\t#\t\t\t\t\t$3",
"\t\t# OUTS:",
"\t\t#\t\t\t\t\t0 - Success",
"\t\t#\t\t\t\t\t1 - Failure",
"\t\t#\t\t\t\t\tstdout: $4",
"\t\t# USAGE:",
"\t\t#\t\t\t\t\t_${1:name}_ ",
"\t\t# NOTES:",
"\t\t#\t\t\t\t\t$5",
"\t\t",
"\t\t echo \"Hello world\"",
"\t\t$0",
"\t\t [[ $# == 0 ]] && fatal \"Missing required argument to ${FUNCNAME[0]}\"",
"\t\t$6",
"}"
],
"description": "Add a new function"

395
README.md
View File

@@ -1,108 +1,135 @@
# Shell Scripting Templates and Utilities
A collection of shell scripting utilities and templates used to ease the creation of BASH scripts. [BATS](https://github.com/bats-core/bats-core) provides unit testing capabilities. All tests are in the `tests/` repo.
A collection of BASH utility functions and script templates used to ease the creation of portable and hardened BASH scripts with sane defaults.
## Bash Script Template Usage
## Usage
To create a new script, copy `scriptTemplate.sh` to a new file and make it executable `chmod 755 [newscript].sh`. Place your custom script logic within the `_mainScript_` function at the top of the script.
There are **two Script Templates** located in the root level of this repository. The usage of these templates is described in detail below.
### Script Template Usage
BASH **Utility functions** are located within the `utilities/` folder.
Default flags included in the base template are:
Complex `sed` find/replace operations are supported with the files located in `sedfiles/`. Read [the usage instructions](sedfiles/README.md).
- `-h`: Prints the contents of the `_usage_` function. Edit the text in that function to provide help
- `-l [level]`: Log level of the script. One of: `FATAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`, `ALL`, `OFF` (Default is '`ERROR`')
- `-n`: Dryrun, sets `$DRYRUN` to `true` allowing you to write functions that will work non-destructively using the `_execute_` function
- `-v`: Sets `$VERBOSE` to `true` and prints all debug messages to stdout
- `-q`: Runs in quiet mode, suppressing all output to stdout. Will still write to log files
- `--force`: If using the `_seekConfirmation_` utility function, this skips all user interaction. Implied `Yes` to all confirmations.
**Automated testing** is provided using [BATS](https://github.com/bats-core/bats-core). All tests are in the `tests/` repo. A git pre-commit hook provides automated testing is located in the `.hooks/` directory. Read about [how to install the hook](.hooks/README.md).
## Bash Script Templates Usage
To create a new script, copy one of the script templates to a new file and make it executable `chmod 755 [newscript].sh`. Place your custom script logic within the `_mainScript_` function at the top of the script.
### Script Templates
There are two templates located at the root level of this repository.
- **`template_source_utils.sh`** - A lean template which attempts to source all the utility functions from this repository. You will need to update the path to the utilities folder sent to `_sourceUtilities_` at the bottom of the script. This template will not function correctly if the utilities are not found.
- **`template_standalone.sh`** - For portability, the standalone template does not assume that this repository is available. Copy and paste the individual utility functions under the `### Custom utility functions` line.
### Code Organization
The script templates are roughly split into three sections:
- TOP: Write the main logic of your script within the `_mainScript_` function. It is placed at the top of the file for easy access and editing. However, it is invoked at the end of the script after options are parsed and functions are sourced.
- MIDDLE: Functions and default variable settings are located just below `_mainScript_`.
- BOTTOM: Script initialization (BASH options, traps, call to `_mainScript_`, etc.) is at the bottom of the template
### Default Options
These default options and global variables are included in the templates and used throughout the utility functions. CLI flags to set/unset them are:
- **`-h, --help`**: Prints the contents of the `_usage_` function. Edit the text in that function to provide help
- **`--logfile [FILE]`** Full PATH to logfile. (Default is `${HOME}/logs/$(basename "$0").log`)
- **`loglevel [LEVEL]`**: Log level of the script. One of: `FATAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`, `ALL`, `OFF` (Default is '`ERROR`')
- **`-n, --dryrun`**: Dryrun, sets `$DRYRUN` to `true` allowing you to write functions that will work non-destructively using the `_execute_` function
- **`-q, --quiet`**: Runs in quiet mode, suppressing all output to stdout. Will still write to log files
- **`-v, --verbose`**: Sets `$VERBOSE` to `true` and prints all debug messages to stdout
- **`--force`**: If using the `_seekConfirmation_` utility function, this skips all user interaction. Implied `Yes` to all confirmations.
You can add custom script options and flags to the `_parseOptions_` function.
### Script Template Functions
scriptTemplate.sh includes some helper functions to perform common tasks.
- `_alert_` Provides alerting and logging functionality. See notes below.
- `_trapCleanup_` Cleans up files on error
- `_makeTempDir_` Creates a temp directory to house temporary files
- `_acquireScriptLock_` Acquires script lock to avoid race conditions on files
- `_functionStack_` Prints the function stack in use to aid debugging
- `_parseOptions_` Parse options and take user input (`-a`, `--some-flag`, and `--some-file [filename]` supported)
- `_usage_` Prints help text when `-h` passed
- `_safeExit_` Used to exit gracefully, cleaning up all temporary files etc.
### Script Initialization
The bottom of the script template file contains a block which initializes the script. Comment, uncomment, or change the settings here for your needs
```bash
trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' \
EXIT INT TERM SIGINT SIGQUIT
set -o errtrace # Trap errors in subshells and functions
set -o errexit # Exit on error. Append '||true' if you expect an error
set -o pipefail # Use last non-zero exit code in a pipeline
# shopt -s nullglob globstar # Make `for f in *.txt` work when `*.txt` matches zero files
IFS=$' \n\t' # Set IFS to preferred implementation
# set -o xtrace # Run in debug mode
set -o nounset # Disallow expansion of unset variables
# [[ $# -eq 0 ]] && _parseOptions_ "-h" # Force arguments when invoking the script
_parseOptions_ "$@" # Parse arguments passed to script
# _makeTempDir_ "$(basename "$0")" # Create a temp directory '$tmpDir'
# _acquireScriptLock_ # Acquire script lock
_mainScript_ # Run the main logic script
_safeExit_ # Exit cleanly
trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT SIGTERM ERR
# Trap errors in subshells and functions
set -o errtrace
# Exit on error. Append '||true' if you expect an error
set -o errexit
# Use last non-zero exit code in a pipeline
set -o pipefail
# Confirm we have BASH greater than v4
[ "${BASH_VERSINFO:-0}" -ge 4 ] || {
printf "%s\n" "ERROR: BASH_VERSINFO is '${BASH_VERSINFO:-0}'. This script requires BASH v4 or greater."
exit 1
}
# Make `for f in *.txt` work when `*.txt` matches zero files
shopt -s nullglob globstar
# Set IFS to preferred implementation
IFS=$' \n\t'
# Run in debug mode
# set -o xtrace
# Source utility functions
_sourceUtilities_ "${HOME}/repos/shell-scripting-templates/utilities"
# Initialize color constants
_setColors_
# Disallow expansion of unset variables
set -o nounset
# Force arguments when invoking the script
# [[ $# -eq 0 ]] && _parseOptions_ "-h"
# Parse arguments passed to script
_parseOptions_ "$@"
# Create a temp directory '$TMP_DIR'
# _makeTempDir_ "$(basename "$0")"
# Acquire script lock
# _acquireScriptLock_
# Source GNU utilities for use on MacOS
# _useGNUUtils_
# Run the main logic script
_mainScript_
# Exit cleanly
_safeExit_
```
# Utility Files
# Utility Functions
The files within `utilities/` contain BASH functions which can be used in your scripts. Each included function includes detailed usage information. Read the code for instructions.
The files within `utilities/` contain BASH functions which can be used in your scripts. Each included function includes detailed usage information. Read the inline comments within the code for detailed usage instructions.
## Including Utility Functions
Within the `utilities` folder are many BASH functions meant to ease development of more complicated scripts. These can be included in the template in two ways.
#### 1. Copy and Paste
#### 1. Copy and paste into standaloneTemplate.sh
You can copy any complete function from the Utilities and place it into your script. Copy it beneath the end of `_mainscript_()`
You can copy any complete function from the Utilities and place it into your script. Copy it beneath the `### Custom utility functions` line.
#### 2. Source entire utility files
#### 2. Source all the utility files by using scriptTemplate.sh
You can source entire utility files by pasting the following snippet into your script beneath `_mainScript_()`. Be sure to replace `[PATH_TO]` with the full path to this repository.
```bash
_sourceHelperFiles_() {
# DESC: Sources script helper files.
local filesToSource
local sourceFile
filesToSource=(
"[PATH_TO]/shell-scripting-templates/utilities/alerts.bash"
"[PATH_TO]/shell-scripting-templates/utilities/baseHelpers.bash"
"[PATH_TO]/shell-scripting-templates/utilities/arrays.bash"
"[PATH_TO]/shell-scripting-templates/utilities/files.bash"
"[PATH_TO]/shell-scripting-templates/utilities/macOS.bash"
"[PATH_TO]/shell-scripting-templates/utilities/numbers.bash"
"[PATH_TO]/shell-scripting-templates/utilities/services.bash"
"[PATH_TO]/shell-scripting-templates/utilities/textProcessing.bash"
"[PATH_TO]/shell-scripting-templates/utilities/dates.bash"
)
for sourceFile in "${filesToSource[@]}"; do
[ ! -f "${sourceFile}" ] \
&& {
echo "error: Can not find sourcefile '${sourceFile}'."
echo "exiting..."
exit 1
}
source "${sourceFile}"
done
}
_sourceHelperFiles_
```
`scriptTemplate.sh` contains a function to source all the utility files into the script. Beware, this will require a full path to the location of this repository and will result in a script that will not be portable to other systems.
## alerts.bash
- `_setColors_` Sets color constants for alerting (**Note:** Colors default to a dark theme.)
- `_alert_` Performs alerting functions including writing to a log file and printing to screen
- - **`_columnizeOutput_`** Creates a column output for key/value pairs with line wrapping for the right column (value)
- -**`_printFuncStack_`** Prints the function stack in use. Used for debugging, and error reporting
- **`_alert_`** Performs alerting functions including writing to a log file and printing to screen
- **`_centerOutput_`** Prints text in the center of the terminal window
- **`_setColors_`** Sets color constants for alerting (**Note:** Colors default to a dark theme.)
Basic alerting, logging, and setting color functions (included in `scriptTemplate.sh` by default). Print messages to stdout and to a user specified logfile using the following functions.
@@ -111,121 +138,183 @@ debug "some text" # Printed only when in verbose (-v) mode
info "some text" # Basic informational messages
notice "some text" # Messages which should be read. Brighter than 'info'
warning "some text" # Non-critical warnings
error "some text" # Error state warnings. (Does not stop the script)
error "some text" # Prints errors and the function stack but does not stop the script.
fatal "some text" # Fatal errors. Exits the script
success "some text" # Prints a success message
header "some text" # Prints a header element
dryrun "some text" # Prints commands that would be run if not in dry run (-n) mode
```
Set the following variables for the alert functions to work.
The following global variables must be set for the alert functions to work
- `$LOGFILE` - Location of a log file
- `$LOGLEVEL` - One of: FATAL, ERROR, WARN, INFO, DEBUG, ALL, OFF (Default is 'ERROR')
- `$QUIET` - If `true`, nothing will print to STDOUT (Logs files will still be populated)
- `$DEBUG` - If `true`, prints `debug` and `verbose` level alerts to stdout
- **`$DEBUG`** - If `true`, prints `debug` level alerts to stdout. (Default: `false`)
- **`$DRYRUN`** - If `true` does not eval commands passed to `_execute_` function. (Default: `false`)
- **`$LOGFILE`** - Path to a log file
- **`$LOGLEVEL`** - One of: FATAL, ERROR, WARN, INFO, DEBUG, ALL, OFF (Default: `ERROR`)
- **`$QUIET`** - If `true`, prints to log file but not stdout. (Default: `false`)
## arrays.bash
Common functions for working with BASH arrays.
Utility functions for working with arrays.
- `_inArray_` Determine if a value is in an array
- `_join_` Joins items together with a user specified separator
- `_setdiff_` Return items that exist in ARRAY1 that are do not exist in ARRAY2
- `_removeDupes_` Removes duplicate array elements
- `_randomArrayElement_` Selects a random item from an array
- **`_dedupeArray_`** Removes duplicate array elements
- **`_forEachDo_`** Iterates over elements and passes each to a function
- **`_forEachFilter_`** Iterates over elements, returning only those that are validated by a function
- **`_forEachFind_`** Iterates over elements, returning the first value that is validated by a function
- **`_forEachReject_`** Iterates over elements, returning only those that are NOT validated by a function
- **`_forEachValidate_`** Iterates over elements and passes each to a function for validation
- **`_inArray_`** Determine if a value is in an array
- **`_isEmptyArray_`** Checks if an array is empty
- **`_joinArray_`** Joins items together with a user specified separator
- **`_mergeArrays_`** Merges the values of two arrays together
- **`_randomArrayElement_`** Selects a random item from an array
- **`_reverseSortArray_`** Sorts an array from highest to lowest
- **`_setdiff_`** Return items that exist in ARRAY1 that are do not exist in ARRAY2
- **`_sortArray_`** Sorts an array from lowest to highest
## baseHelpers.bash
## checks.bash
Commonly used functions in many scripts
Functions for validating common use-cases
- `_execute_` Executes commands with safety and logging options. Respects `DRYRUN` and `VERBOSE` flags.
- `_findBaseDir_` Locates the real directory of the script being run. Similar to GNU readlink -n
- `_checkBinary_` Check if a binary exists in the search PATH
- `_haveFunction_` Tests if a function exists
- `_pauseScript_` Pause a script at any point and continue after user input
- `_progressBar_` Prints a progress bar within a for/while loop
- `_rootAvailable_` Validate we have superuser access as root (via sudo if requested)
- `_runAsRoot_` Run the requested command as root (via sudo if requested)
- `_safeExit_` Cleans up temporary files before exiting a script
- `_seekConfirmation_` Seek user input for yes/no question
- `_setPATH_` Add directories to $PATH so script can find executables
## csv.bash
Functions to write to a CSV file.
- `_makeCSV_` Creates a new CSV file if one does not already exist
- `_writeCSV_` Takes passed arguments and writes them as a comma separated line
- **`_binaryExists_`** Check if a binary exists in the PATH
- **`_functionExists_`** Tests if a function is available in current scope
- **`_isInternetAvailable_`** Checks if Internet connections are possible
- **`_isAlpha_`** Validate that a given variable contains only alphabetic characters
- **`_isAlphaDash_`** Validate that a given variable contains only alpha-numeric characters, as well as dashes and underscores
- **`_isAlphaNum_`** Validate that a given variable contains only alpha-numeric characters
- **`_isDir_`** Validate that a given input points to a valid directory
- **`_isEmail_`** Validates that an input is a valid email address
- **`_isFile_`** Validate that a given input points to a valid file
- **`_isIPv4_`** Validates that an input is a valid IPv4 address
- **`_isIPv6_`** Validates that an input is a valid IPv6 address
- **`_isNum_`** Validate that a given variable contains only numeric characters
- **`_isTerminal_`** Checks if script is run in an interactive terminal
- **`_rootAvailable_`** Validate we have superuser access as root (via sudo if requested)
- **`_varIsEmpty_`** Checks if a given variable is empty or null
- **`_varIsFalse_`** Checks if a given variable is false
- **`_varIsTrue_`** Checks if a given variable is true
## dates.bash
Common utilities for working with dates in BASH scripts.
Functions for working with dates and time.
- `_monthToNumber_` Convert a month name to a number
- `_numberToMonth_` Convert a month number to its name
- `_parseDate_` Takes a string as input and attempts to find a date within it to parse into component parts (day, month, year)
- `_formatDate_` Reformats dates into user specified formats
- **`_convertToUnixTimestamp_`** Converts a date to unix timestamp
- **`_countdown_`** Sleep for a specified amount of time
- **`_dateUnixTimestamp_`** Current time in unix timestamp
- **`_formatDate_`** Reformats dates into user specified formats
- **`_fromSeconds_`** Convert seconds to HH:MM:SS
- **`_monthToNumber_`** Convert a month name to a number
- **`_numberToMonth_`** Convert a month number to its name
- **`_parseDate_`** Takes a string as input and attempts to find a date within it to parse into component parts (day, month, year)
- **`_readableUnixTimestamp_`** Format unix timestamp to human readable format
- **`_toSeconds_`** Converts HH:MM:SS to seconds
## debug.bash
Functions to aid in debugging BASH scripts
- **`_pauseScript_`** Pause a script at any point and continue after user input
- **`_printAnsi_`** Helps debug ansi escape sequence in text by displaying the escape codes
- **`_printArray_`** Prints the content of array as key value pairs for easier debugging
## files.bash
Common utilities for working with files.
Functions for working with files.
- `_listFiles_` Find files in a directory. Use either glob or regex.
- `_backupFile_` Creates a backup of a specified file with .bak extension or optionally to a specified directory.
- `_parseFilename_` Break a filename into its component parts which and place them into prefixed variables for use in your script (dir, basename, extension, path, etc.)
- `_decryptFile_` Decrypts a file with `openssl`
- `_encryptFile_` Encrypts a file with `openssl`
- `_extract_` Extract a compressed file
- `_json2yaml_` Convert JSON to YAML uses python
- `_makeSymlink_` Creates a symlink and backs up a file which may be overwritten by the new symlink. If the exact same symlink already exists, nothing is done.
- `_parseYAML_` Convert a YAML file into BASH variables for use in a shell script
- `_readFile_` Prints each line of a file
- `_sourceFile_` Source a file into a script
- `_uniqueFileName_` Ensure a file to be created has a unique filename to avoid overwriting other files
- `_yaml2json_` Convert a YAML file to JSON with python
- **`_backupFile_`** Creates a backup of a specified file with .bak extension or optionally to a specified directory.
- **`_decryptFile_`** Decrypts a file with `openssl`
- **`_encryptFile_`** Encrypts a file with `openssl`
- **`_extractArchive_`** Extract a compressed file
- **`_fileAbsPath_`** Finds the absolute path to a relative file or directory
- **`_fileBasename_`** Gets the basename of a file from a file name
- **`_fileContains_`** Tests whether a file contains a given pattern
- **`_fileDirectory_`** Finds the directory name from a file path
- **`_fileExtension_`** Gets the extension of a file
- **`_fileName_`** Prints a filename from a path
- **`_json2yaml_`** Convert JSON to YAML uses python
- **`_listFiles_`** Find files in a directory. Use either glob or regex.
- **`_makeSymlink_`** Creates a symlink and backs up a file which may be overwritten by the new symlink. If the exact same symlink already exists, nothing is done.
- **`_parseFilename_`** Break a filename into its component parts which and place them into global variables for use in your script (dir, basename, extension, path, etc.)
- **`_parseYAML_`** Convert a YAML file into BASH variables for use in a shell script
- **`_readFile_`** Prints each line of a file
- **`_sourceFile_`** Source a file into a script
- **`_createUniqueFilename_`** Ensure a file to be created has a unique filename to avoid overwriting other files
- **`_yaml2json_`** Convert a YAML file to JSON with python
## macOS.bash
Functions useful when writing scripts to be run on macOS
- `_haveScriptableFinder_` Determine whether we can script the Finder or not
- `_guiInput_` Ask for user input using a Mac dialog box
- **`_guiInput_`** Ask for user input using a Mac dialog box
- **`_haveScriptableFinder_`** Determine whether we can script the Finder or not
- **`_useGNUUtils_`** Add GNU utilities to PATH to allow consistent use of sed/grep/tar/etc. on MacOS
## numbers.bash
## misc.bash
Helpers to work with numbers
Miscellaneous functions
- `_fromSeconds_` Convert seconds to HH:MM:SS
- `_toSeconds_` Converts HH:MM:SS to seconds
- `_countdown_` Sleep for a specified amount of time
- **`_acquireScriptLock_`** Acquire script lock to prevent running the same script a second time before the first instance exits
- **`_detectLinuxDistro_`** Detects the host computer's distribution of Linux
- **`_detectMacOSVersion_`** Detects the host computer's version of macOS
- **`_detectOS_`** Detect the the host computer's operating system
- **`_execute_`** Executes commands with safety and logging options. Respects `DRYRUN` and `VERBOSE` flags.
- **`_findBaseDir_`** Locates the real directory of the script being run. Similar to GNU readlink -n
- **`_generateUUID_`** Generates a unique UUID
- **`_makeProgressBar_`** Prints a progress bar within a for/while loop
- **`_runAsRoot_`** Run the requested command as root (via sudo if requested)
- **`_seekConfirmation_`** Seek user input for yes/no question
- **`_trapCleanup_`** Cleans up after a trapped error.
## services.bash
Functions to work with external services
- `_haveInternet_` Tests to see if there is an active Internet connection
- `_httpStatus_` Report the HTTP status of a specified URL
- `_pushover_` Sends a notification via Pushover (Requires API keys)
- **`_haveInternet_`** Tests to see if there is an active Internet connection
- **`_httpStatus_`** Report the HTTP status of a specified URL
- **`_pushover_`** Sends a notification via Pushover (Requires API keys)
## testProcessing.bash
## strings.bash
Work with strings in your script
Functions for string manipulation
- `_cleanString_` Cleans a string of text
- `_stopWords_` Removes common stopwords from a string. Requires a sed stopwords file. Customize to your needs.
- `_escape_` Escapes a string by adding `\` before special chars
- `_htmlDecode_` Decode HTML characters with sed. (Requires sed file)
- `_htmlEncode_` Encode HTML characters with sed (Requires sed file)
- `_lower_` Convert a string to lowercase
- `_upper_` Convert a string to uppercase
- `_ltrim_` Removes all leading whitespace (from the left)
- `_regex_` Use regex to validate and parse strings
- `_rtrim_` Removes all leading whitespace (from the right)
- `_trim_` Removes all leading/trailing whitespace
- `_urlEncode_` URL encode a string
- `_urlDecode_` Decode a URL encoded string
- **`_cleanString_`** Cleans a string of text
- **`_decodeHTML_`** Decode HTML characters with sed. (Requires sed file)
- **`_decodeURL_`** Decode a URL encoded string
- **`_encodeHTML_`** Encode HTML characters with sed (Requires sed file)
- **`_encodeURL_`** URL encode a string
- **`_escapeString_`** Escapes a string by adding `\` before special chars
- **`_lower_`** Convert a string to lowercase
- **`_ltrim_`** Removes all leading whitespace (from the left)
- **`_regexCapture_`** Use regex to validate and parse strings
- **`_rtrim_`** Removes all leading whitespace (from the right)
- **`_splitString_`** Split a string based on a given delimeter
- **`_stringContains_`** Tests whether a string matches a substring
- **`_stringRegex_`** Tests whether a string matches a regex pattern
- **`_stripANSI_`** Strips ANSI escape sequences from text
- **`_stripStopwords_`** Removes common stopwords from a string using a list of sed replacements located in an external file.
- **`_trim_`** Removes all leading/trailing whitespace
- **`_upper_`** Convert a string to uppercase
## A Note on Code Reuse
## template_utils.bash
Functions required to allow the script template and alert functions to be used
- **`_makeTempDir_`** Creates a temp directory to house temporary files
- **`_safeExit_`** Cleans up temporary files before exiting a script
- **`_setPATH_`** Add directories to $PATH so script can find executables
# Coding conventions
Where possible, I follow [defensive BASH programming](https://kfirlavi.herokuapp.com/blog/2012/11/14/defensive-bash-programming/) principles.
- Function names use camel case surrounded by underscores: `_nameOfFunction_`
- Local variable names use camel case with a starting underscore: `_localVariable`
- Global variables are in ALL_CAPS with underscores seperating words
- Exceptions to the variable an function naming rules are made for alerting functions and colors to ease my speed of programming. (Breaking years of habits is hard...) I.e. `notice "Some log item: ${blue}blue text${reset}` Where `notice` is a function and `$blue` and `$reset` are global variables but are lowercase.
- Variables are always surrounded by quotes and brackets `"${1}"` (It's verbose, but a safe practice)
- Formatting is provided by [shfmt](https://github.com/mvdan/sh) using 4 spaces for indentation
## A Note on Code Reuse and Prior Art
I compiled these scripting utilities over many years without having an intention to make them public. As a novice programmer, I have Googled, GitHubbed, and StackExchanged a path to solve my own scripting needs. I often lift a function whole-cloth from a GitHub repo don't keep track of its original location. I have done my best within these files to recreate my footsteps and give credit to the original creators of the code when possible. Unfortunately, I fear that I missed as many as I found. My goal in making this repository public is not to take credit for the code written by others. If you recognize something that I didn't credit, please let me know.

View File

@@ -1,480 +0,0 @@
#!/usr/bin/env bash
_mainScript_() {
# Replace everything in _mainScript_() with your script's code
header "Showing alert colors"
debug "This is debug text"
info "This is info text"
notice "This is notice text"
dryrun "This is dryrun text"
warning "This is warning text"
error "This is error text"
success "This is success text"
input "This is input text"
} # end _mainScript_
# ################################## Flags and defaults
# Script specific
# Common
LOGFILE="${HOME}/logs/$(basename "$0").log"
QUIET=false
LOGLEVEL=ERROR
VERBOSE=false
FORCE=false
DRYRUN=false
declare -a ARGS=()
NOW=$(LC_ALL=C date +"%m-%d-%Y %r") # Returns: 06-14-2015 10:34:40 PM
DATESTAMP=$(LC_ALL=C date +%Y-%m-%d) # Returns: 2015-06-14
HOURSTAMP=$(LC_ALL=C date +%r) # Returns: 10:34:40 PM
TIMESTAMP=$(LC_ALL=C date +%Y%m%d_%H%M%S) # Returns: 20150614_223440
LONGDATE=$(LC_ALL=C date +"%a, %d %b %Y %H:%M:%S %z") # Returns: Sun, 10 Jan 2016 20:47:53 -0500
GMTDATE=$(LC_ALL=C date -u -R | sed 's/\+0000/GMT/') # Returns: Wed, 13 Jan 2016 15:55:29 GMT
# ################################## Custom utility functions
# ################################## Common Functions for script template
_setColors_() {
# DESC: Sets colors use for alerts.
# ARGS: None
# OUTS: None
# USAGE: echo "${blue}Some text${reset}"
if tput setaf 1 &>/dev/null; then
bold=$(tput bold)
underline=$(tput smul)
reverse=$(tput rev)
reset=$(tput sgr0)
if [[ $(tput colors) -ge 256 ]] 2>/dev/null; then
white=$(tput setaf 231)
blue=$(tput setaf 38)
yellow=$(tput setaf 11)
tan=$(tput setaf 3)
green=$(tput setaf 82)
red=$(tput setaf 1)
purple=$(tput setaf 171)
gray=$(tput setaf 250)
else
white=$(tput setaf 7)
blue=$(tput setaf 38)
yellow=$(tput setaf 3)
tan=$(tput setaf 3)
green=$(tput setaf 2)
red=$(tput setaf 1)
purple=$(tput setaf 13)
gray=$(tput setaf 7)
fi
else
bold="\033[4;37m"
reset="\033[0m"
underline="\033[4;37m"
reverse=""
white="\033[0;37m"
blue="\033[0;34m"
yellow="\033[0;33m"
tan="\033[0;33m"
green="\033[1;32m"
red="\033[0;31m"
purple="\033[0;35m"
gray="\033[0;37m"
fi
}
_alert_() {
# DESC: Controls all printing of messages to log files and stdout.
# ARGS: $1 (required) - The type of alert to print
# (success, header, notice, dryrun, debug, warning, error,
# fatal, info, input)
# $2 (required) - The message to be printed to stdout and/or a log file
# $3 (optional) - Pass '${LINENO}' to print the line number where the _alert_ was triggered
# OUTS: None
# USAGE: [ALERTTYPE] "[MESSAGE]" "${LINENO}"
# NOTES: The colors of each alert type are set in this function
# For specified alert types, the funcstac will be printed
local function_name color
local alertType="${1}"
local message="${2}"
local line="${3:-}" # Optional line number
if [[ -n ${line} && ${alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
message="${message} (line: ${line}) $(_functionStack_)"
elif [[ -n ${line} && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
message="${message} (line: ${line})"
elif [[ -z ${line} && ${alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
message="${message} $(_functionStack_)"
fi
if [[ ${alertType} =~ ^(error|fatal) ]]; then
color="${bold}${red}"
elif [ "${alertType}" == "info" ]; then
color="${gray}"
elif [ "${alertType}" == "warning" ]; then
color="${red}"
elif [ "${alertType}" == "success" ]; then
color="${green}"
elif [ "${alertType}" == "debug" ]; then
color="${purple}"
elif [ "${alertType}" == "header" ]; then
color="${bold}${tan}"
elif [ ${alertType} == "notice" ]; then
color="${bold}"
elif [ ${alertType} == "input" ]; then
color="${bold}${underline}"
elif [ "${alertType}" = "dryrun" ]; then
color="${blue}"
else
color=""
fi
_writeToScreen_() {
("${QUIET}") && return 0 # Print to console when script is not 'quiet'
[[ ${VERBOSE} == false && ${alertType} =~ ^(debug|verbose) ]] && return 0
if ! [[ -t 1 ]]; then # Don't use colors on non-recognized terminals
color=""
reset=""
fi
echo -e "$(date +"%r") ${color}$(printf "[%7s]" "${alertType}") ${message}${reset}"
}
_writeToScreen_
_writeToLog_() {
[[ ${alertType} == "input" ]] && return 0
[[ ${LOGLEVEL} =~ (off|OFF|Off) ]] && return 0
if [ -z "${LOGFILE:-}" ]; then
LOGFILE="$(pwd)/$(basename "$0").log"
fi
[ ! -d "$(dirname "${LOGFILE}")" ] && mkdir -p "$(dirname "${LOGFILE}")"
[[ ! -f ${LOGFILE} ]] && touch "${LOGFILE}"
# Don't use colors in logs
if command -v gsed &>/dev/null; then
local cleanmessage="$(echo "${message}" | gsed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
else
local cleanmessage="$(echo "${message}" | sed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
fi
echo -e "$(date +"%b %d %R:%S") $(printf "[%7s]" "${alertType}") [$(/bin/hostname)] ${cleanmessage}" >>"${LOGFILE}"
}
# Write specified log level data to logfile
case "${LOGLEVEL:-ERROR}" in
ALL | all | All)
_writeToLog_
;;
DEBUG | debug | Debug)
_writeToLog_
;;
INFO | info | Info)
if [[ ${alertType} =~ ^(die|error|fatal|warning|info|notice|success) ]]; then
_writeToLog_
fi
;;
NOTICE | notice | Notice)
if [[ ${alertType} =~ ^(die|error|fatal|warning|notice|success) ]]; then
_writeToLog_
fi
;;
WARN | warn | Warn)
if [[ ${alertType} =~ ^(die|error|fatal|warning) ]]; then
_writeToLog_
fi
;;
ERROR | error | Error)
if [[ ${alertType} =~ ^(die|error|fatal) ]]; then
_writeToLog_
fi
;;
FATAL | fatal | Fatal)
if [[ ${alertType} =~ ^(die|fatal) ]]; then
_writeToLog_
fi
;;
OFF | off)
return 0
;;
*)
if [[ ${alertType} =~ ^(die|error|fatal) ]]; then
_writeToLog_
fi
;;
esac
} # /_alert_
error() { _alert_ error "${1}" "${2:-}"; }
warning() { _alert_ warning "${1}" "${2:-}"; }
notice() { _alert_ notice "${1}" "${2:-}"; }
info() { _alert_ info "${1}" "${2:-}"; }
success() { _alert_ success "${1}" "${2:-}"; }
dryrun() { _alert_ dryrun "${1}" "${2:-}"; }
input() { _alert_ input "${1}" "${2:-}"; }
header() { _alert_ header "== ${1} ==" "${2:-}"; }
debug() { _alert_ debug "${1}" "${2:-}"; }
die() {
_alert_ fatal "${1}" "${2:-}"
_safeExit_ "1"
}
fatal() {
_alert_ fatal "${1}" "${2:-}"
_safeExit_ "1"
}
_safeExit_() {
# DESC: Cleanup and exit from a script
# ARGS: $1 (optional) - Exit code (defaults to 0)
# OUTS: None
if [[ -d ${SCRIPT_LOCK:-} ]]; then
if command rm -rf "${SCRIPT_LOCK}"; then
debug "Removing script lock"
else
warning "Script lock could not be removed. Try manually deleting ${tan}'${LOCK_DIR}'${red}"
fi
fi
if [[ -n ${TMP_DIR:-} && -d ${TMP_DIR:-} ]]; then
if [[ ${1:-} == 1 && -n "$(ls "${TMP_DIR}")" ]]; then
# Do something here to save TMP_DIR on a non-zero script exit for debugging
command rm -r "${TMP_DIR}"
debug "Removing temp directory"
else
command rm -r "${TMP_DIR}"
debug "Removing temp directory"
fi
fi
trap - INT TERM EXIT
exit ${1:-0}
}
_trapCleanup_() {
# DESC: Log errors and cleanup from script when an error is trapped
# ARGS: $1 - Line number where error was trapped
# $2 - Line number in function
# $3 - Command executing at the time of the trap
# $4 - Names of all shell functions currently in the execution call stack
# $5 - Scriptname
# $6 - $BASH_SOURCE
# OUTS: None
local line=${1:-} # LINENO
local linecallfunc=${2:-}
local command="${3:-}"
local funcstack="${4:-}"
local script="${5:-}"
local sourced="${6:-}"
funcstack="'$(echo "$funcstack" | sed -E 's/ / < /g')'"
if [[ ${script##*/} == "${sourced##*/}" ]]; then
fatal "${7:-} command: '${command}' (line: ${line}) [func: $(_functionStack_)]"
else
fatal "${7:-} command: '${command}' (func: ${funcstack} called at line ${linecallfunc} of '${script##*/}') (line: $line of '${sourced##*/}') "
fi
_safeExit_ "1"
}
_makeTempDir_() {
# DESC: Creates a temp directory to house temporary files
# ARGS: $1 (Optional) - First characters/word of directory name
# OUTS: $TMP_DIR - Temporary directory
# USAGE: _makeTempDir_ "$(basename "$0")"
[ -d "${TMP_DIR:-}" ] && return 0
if [ -n "${1:-}" ]; then
TMP_DIR="${TMPDIR:-/tmp/}${1}.$RANDOM.$RANDOM.$$"
else
TMP_DIR="${TMPDIR:-/tmp/}$(basename "$0").$RANDOM.$RANDOM.$RANDOM.$$"
fi
(umask 077 && mkdir "${TMP_DIR}") || {
fatal "Could not create temporary directory! Exiting."
}
debug "\$TMP_DIR=${TMP_DIR}"
}
_acquireScriptLock_() {
# DESC: Acquire script lock
# ARGS: $1 (optional) - Scope of script execution lock (system or user)
# OUTS: $SCRIPT_LOCK - Path to the directory indicating we have the script lock
# NOTE: This lock implementation is extremely simple but should be reliable
# across all platforms. It does *not* support locking a script with
# symlinks or multiple hardlinks as there's no portable way of doing so.
# If the lock was acquired it's automatically released in _safeExit_()
local LOCK_DIR
if [[ ${1:-} == 'system' ]]; then
LOCK_DIR="${TMPDIR:-/tmp/}$(basename "$0").lock"
else
LOCK_DIR="${TMPDIR:-/tmp/}$(basename "$0").$UID.lock"
fi
if command mkdir "${LOCK_DIR}" 2>/dev/null; then
readonly SCRIPT_LOCK="${LOCK_DIR}"
debug "Acquired script lock: ${tan}${SCRIPT_LOCK}${purple}"
else
error "Unable to acquire script lock: ${tan}${LOCK_DIR}${red}"
fatal "If you trust the script isn't running, delete the lock dir"
fi
}
_functionStack_() {
# DESC: Prints the function stack in use
# ARGS: None
# OUTS: Prints [function]:[file]:[line]
# NOTE: Does not print functions from the alert class
local _i
funcStackResponse=()
for ((_i = 1; _i < ${#BASH_SOURCE[@]}; _i++)); do
case "${FUNCNAME[$_i]}" in "_alert_" | "_trapCleanup_" | fatal | error | warning | notice | info | verbose | debug | dryrun | header | success | die) continue ;; esac
funcStackResponse+=("${FUNCNAME[$_i]}:$(basename ${BASH_SOURCE[$_i]}):${BASH_LINENO[_i - 1]}")
done
printf "( "
printf %s "${funcStackResponse[0]}"
printf ' < %s' "${funcStackResponse[@]:1}"
printf ' )\n'
}
_parseOptions_() {
# Iterate over options
# breaking -ab into -a -b when needed and --foo=bar into --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i = 1; i < ${#1}; i++)); do
c=${1:i:1}
options+=("-$c") # Add current char to options
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring == *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]:-}"
unset options
# Read the options and set stuff
while [[ ${1:-} == -?* ]]; do
case $1 in
# Custom options
# Common options
-h | --help)
_usage_ >&2
_safeExit_
;;
--loglevel)
shift
LOGLEVEL=${1}
;;
--logfile)
shift
LOGFILE="${1}"
;;
-n | --dryrun) DRYRUN=true ;;
-v | --verbose) VERBOSE=true ;;
-q | --quiet) QUIET=true ;;
--force) FORCE=true ;;
--endopts)
shift
break
;;
*) fatal "invalid option: '$1'." ;;
esac
shift
done
ARGS+=("$@") # Store the remaining user input as arguments.
}
_usage_() {
cat <<EOF
${bold}$(basename "$0") [OPTION]... [FILE]...${reset}
This is a script template. Edit this description to print help to users.
${bold}Options:${reset}
-h, --help Display this help and exit
--loglevel [LEVEL] One of: FATAL, ERROR, WARN, INFO, NOTICE, DEBUG, ALL, OFF
(Default is 'ERROR')
--logfile [FILE] Full PATH to logfile. (Default is '${HOME}/logs/$(basename "$0").log')
-n, --dryrun Non-destructive. Makes no permanent changes.
-q, --quiet Quiet (no output)
-v, --verbose Output more information. (Items echoed to 'verbose')
--force Skip all user interaction. Implied 'Yes' to all actions.
${bold}Example Usage:${reset}
$ $(basename "$0") -vn --logfile "/path/to/file.log" --loglevel 'WARN'
EOF
}
# ################################## INITIALIZE AND RUN THE SCRIPT
# (Comment or uncomment the lines below to customize script behavior)
trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT
# Trap errors in subshells and functions
set -o errtrace
# Exit on error. Append '||true' if you expect an error
set -o errexit
# Use last non-zero exit code in a pipeline
set -o pipefail
# Make `for f in *.txt` work when `*.txt` matches zero files
# shopt -s nullglob globstar
# Set IFS to preferred implementation
IFS=$' \n\t'
# Run in debug mode
# set -o xtrace
# Initialize color constants
_setColors_
# Disallow expansion of unset variables
set -o nounset
# Force arguments when invoking the script
# [[ $# -eq 0 ]] && _parseOptions_ "-h"
# Parse arguments passed to script
_parseOptions_ "$@"
# Create a temp directory '$TMP_DIR'
# _makeTempDir_ "$(basename "$0")"
# Acquire script lock
# _acquireScriptLock_
# Run the main logic script
_mainScript_
# Exit cleanly
_safeExit_

View File

@@ -1,2 +1,20 @@
# Sed Files
These files are used by utility functions to perform complex operations with sed. If you plan on using those functions, ensure they point to these files.
These files are used by utility functions to perform complex operations with `sed`. If you plan on using those functions, ensure they point to these files.
The functions which use these files are:
- `_stripStopwords_`
- `_encodeHTML_`
- `_decodeHTML_`
## Installation
To use these files without needing to edit their location within the utility functions themselves, follow these steps in your terminal at the root level of this repository:
```bash
mkdir "${HOME}/.sed"
ln -s "$(git rev-parse --show-toplevel)/sedfiles/stopwords.sed" "${HOME}/.sed/stopwords.sed"
ln -s "$(git rev-parse --show-toplevel)/sedfiles/stopwords.sed" "${HOME}/.sed/htmlEncode.sed"
ln -s "$(git rev-parse --show-toplevel)/sedfiles/stopwords.sed" "${HOME}/.sed/htmlDecode.sed"
```

294
template_source_utils.sh Executable file
View File

@@ -0,0 +1,294 @@
#!/usr/bin/env bash
_mainScript_() {
# Replace everything in _mainScript_() with your script's code
header "Showing alert colors"
debug "This is debug text"
info "This is info text"
notice "This is notice text"
dryrun "This is dryrun text"
warning "This is warning text"
error "This is error text"
success "This is success text"
input "This is input text"
}
#/_mainsScript_()
# ################################## Flags and defaults
# Script specific
# # Required variables
LOGFILE="${HOME}/logs/$(basename "$0").log"
QUIET=false
LOGLEVEL=ERROR
VERBOSE=false
FORCE=false
DRYRUN=false
declare -a ARGS=()
# ################################## Functions required for this template to work
_trapCleanup_() {
# DESC:
# Log errors and cleanup from script when an error is trapped. Called by 'trap'
# ARGS:
# $1: Line number where error was trapped
# $2: Line number in function
# $3: Command executing at the time of the trap
# $4: Names of all shell functions currently in the execution call stack
# $5: Scriptname
# $6: $BASH_SOURCE
# USAGE:
# trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT SIGTERM ERR
# OUTS:
# Exits script with error code 1
local _line=${1:-} # LINENO
local _linecallfunc=${2:-}
local _command="${3:-}"
local _funcstack="${4:-}"
local _script="${5:-}"
local _sourced="${6:-}"
if [[ "$(declare -f "fatal")" && "$(declare -f "_printFuncStack_")" ]]; then
_funcstack="'$(echo "${_funcstack}" | sed -E 's/ / < /g')'"
if [[ ${_script##*/} == "${_sourced##*/}" ]]; then
fatal "${7:-} command: '${_command}' (line: ${_line}) [func: $(_printFuncStack_)]"
else
fatal "${7:-} command: '${_command}' (func: ${_funcstack} called at line ${_linecallfunc} of '${_script##*/}') (line: ${_line} of '${_sourced##*/}') "
fi
else
printf "%s\n" "Fatal error trapped. Exiting..."
fi
if [ "$(declare -f "_safeExit_")" ]; then
_safeExit_ 1
else
exit 1
fi
}
_sourceUtilities_() {
# DESC:
# Sources bash utility functions
# ARGS:
# $1 (required): Directories or files containing utility functions
# OUTS:
# 0 if success
# 1 if failure
# USAGE:
# _sourceHelperFiles_ "/path/to/dir" "path/to/file.sh"
local _filesSourced=true
[[ $# == 0 ]] && _filesSourced=false
local _fileToSource
local _location
if [ ${_filesSourced} == true ]; then
for _location in "$@"; do
if [[ -d ${_location} ]]; then
for _fileToSource in "${_location}"/*.{sh,bash}; do
if [[ -f ${_fileToSource} ]]; then
if ! source "${_fileToSource}"; then
_filesSourced=false
break 2
fi
else
_filesSourced=false
break 2
fi
done
elif [[ -f ${_location} ]] && [[ ${_location} =~ .*\.(sh|bash)$ ]]; then
if ! source "${_fileToSource}"; then
_filesSourced=false
break
fi
else
_filesSourced=false
break
fi
done
fi
if [ ${_filesSourced} == true ]; then
return 0
else
printf "%s\n" "ERROR: Invalid argument to ${FUNCNAME[0]}: ${_location}"
if [ "$(declare -f "_safeExit_")" ]; then
_safeExit_ 1
else
exit 1
fi
fi
}
_parseOptions_() {
# DESC:
# Iterates through options passed to script and sets variables. Will break -ab into -a -b
# when needed and --foo=bar into --foo bar
# ARGS:
# $@ from command line
# OUTS:
# Sets array 'ARGS' containing all arguments passed to script that were not parsed as options
# USAGE:
# _parseOptions_ "$@"
# Iterate over options
local _optstring=h
declare -a _options
local _c
local i
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i = 1; i < ${#1}; i++)); do
_c=${1:i:1}
_options+=("-${_c}") # Add current char to options
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ ${_optstring} == *"${_c}:"* && ${1:i+1} ]]; then
_options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) _options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) _options+=(--endopts) ;;
# Otherwise, nothing special
*) _options+=("$1") ;;
esac
shift
done
set -- "${_options[@]:-}"
unset _options
# Read the options and set stuff
while [[ ${1:-} == -?* ]]; do
case $1 in
# Custom options
# Common options
-h | --help)
_usage_
_safeExit_
;;
--loglevel)
shift
LOGLEVEL=${1}
;;
--logfile)
shift
LOGFILE="${1}"
;;
-n | --dryrun) DRYRUN=true ;;
-v | --verbose) VERBOSE=true ;;
-q | --quiet) QUIET=true ;;
--force) FORCE=true ;;
--endopts)
shift
break
;;
*)
if [ "$(declare -f "_safeExit_")" ]; then
fatal "invalid option: $1"
else
printf "%s\n" "Invalid option: $1"
exit 1
fi
;;
esac
shift
done
ARGS+=("$@") # Store the remaining user input as arguments.
}
_usage_() {
cat <<USAGE_TEXT
${bold}$(basename "$0") [OPTION]... [FILE]...${reset}
This is a script template. Edit this description to print help to users.
${bold}Options:${reset}
-h, --help Display this help and exit
--loglevel [LEVEL] One of: FATAL, ERROR, WARN, INFO, NOTICE, DEBUG, ALL, OFF
(Default is 'ERROR')
--logfile [FILE] Full PATH to logfile. (Default is '${HOME}/logs/$(basename "$0").log')
-n, --dryrun Non-destructive. Makes no permanent changes.
-q, --quiet Quiet (no output)
-v, --verbose Output more information. (Items echoed to 'verbose')
--force Skip all user interaction. Implied 'Yes' to all actions.
${bold}Example Usage:${reset}
${gray}# Run the script and specify log level and log file.${reset}
$(basename "$0") -vn --logfile "/path/to/file.log" --loglevel 'WARN'
USAGE_TEXT
}
# ################################## INITIALIZE AND RUN THE SCRIPT
# (Comment or uncomment the lines below to customize script behavior)
trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT SIGTERM
# Trap errors in subshells and functions
set -o errtrace
# Exit on error. Append '||true' if you expect an error
set -o errexit
# Use last non-zero exit code in a pipeline
set -o pipefail
# Confirm we have BASH greater than v4
[ "${BASH_VERSINFO:-0}" -ge 4 ] || {
printf "%s\n" "ERROR: BASH_VERSINFO is '${BASH_VERSINFO:-0}'. This script requires BASH v4 or greater."
exit 1
}
# Make `for f in *.txt` work when `*.txt` matches zero files
shopt -s nullglob globstar
# Set IFS to preferred implementation
IFS=$' \n\t'
# Run in debug mode
# set -o xtrace
# Source utility functions
_sourceUtilities_ "${HOME}/repos/shell-scripting-templates/utilities"
# Initialize color constants
_setColors_
# Disallow expansion of unset variables
set -o nounset
# Force arguments when invoking the script
# [[ $# -eq 0 ]] && _parseOptions_ "-h"
# Parse arguments passed to script
_parseOptions_ "$@"
# Create a temp directory '$TMP_DIR'
# _makeTempDir_ "$(basename "$0")"
# Acquire script lock
# _acquireScriptLock_
# Source GNU utilities for use on MacOS
# _useGNUutils_
# Run the main logic script
_mainScript_
# Exit cleanly
_safeExit_

600
template_standalone.sh Executable file
View File

@@ -0,0 +1,600 @@
#!/usr/bin/env bash
_mainScript_() {
# Replace everything in _mainScript_() with your script's code
header "Showing alert colors"
debug "This is debug text"
info "This is info text"
notice "This is notice text"
dryrun "This is dryrun text"
warning "This is warning text"
error "This is error text"
success "This is success text"
input "This is input text"
}
# end _mainScript_
# ################################## Flags and defaults
# Script specific
# Required variables
LOGFILE="${HOME}/logs/$(basename "$0").log"
QUIET=false
LOGLEVEL=ERROR
VERBOSE=false
FORCE=false
DRYRUN=false
declare -a ARGS=()
# ################################## Custom utility functions (Pasted from repository)
# ################################## Functions required for this template to work
# Functions for providing alerts to the user and printing them to the log
_setColors_() {
# DESC:
# Sets colors use for alerts.
# ARGS:
# None
# OUTS:
# None
# USAGE:
# echo "${blue}Some text${reset}"
if tput setaf 1 >/dev/null 2>&1; then
bold=$(tput bold)
underline=$(tput smul)
reverse=$(tput rev)
reset=$(tput sgr0)
if [[ $(tput colors) -ge 256 ]] >/dev/null 2>&1; then
white=$(tput setaf 231)
blue=$(tput setaf 38)
yellow=$(tput setaf 11)
tan=$(tput setaf 3)
green=$(tput setaf 82)
red=$(tput setaf 1)
purple=$(tput setaf 171)
gray=$(tput setaf 250)
else
white=$(tput setaf 7)
blue=$(tput setaf 38)
yellow=$(tput setaf 3)
tan=$(tput setaf 3)
green=$(tput setaf 2)
red=$(tput setaf 1)
purple=$(tput setaf 13)
gray=$(tput setaf 7)
fi
else
bold="\033[4;37m"
reset="\033[0m"
underline="\033[4;37m"
reverse=""
white="\033[0;37m"
blue="\033[0;34m"
yellow="\033[0;33m"
tan="\033[0;33m"
green="\033[1;32m"
red="\033[0;31m"
purple="\033[0;35m"
gray="\033[0;37m"
fi
}
_alert_() {
# DESC:
# Controls all printing of messages to log files and stdout.
# ARGS:
# $1 (required) - The type of alert to print
# (success, header, notice, dryrun, debug, warning, error,
# fatal, info, input)
# $2 (required) - The message to be printed to stdout and/or a log file
# $3 (optional) - Pass '${LINENO}' to print the line number where the _alert_ was triggered
# OUTS:
# stdout: The message is printed to stdout
# log file: The message is printed to a log file
# USAGE:
# [_alertType] "[MESSAGE]" "${LINENO}"
# NOTES:
# - The colors of each alert type are set in this function
# - For specified alert types, the funcstac will be printed
local _color
local _alertType="${1}"
local _message="${2}"
local _line="${3:-}" # Optional line number
[[ $# -lt 2 ]] && fatal 'Missing required argument to _alert_'
if [[ -n ${_line} && ${_alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
_message="${_message} ${gray}(line: ${_line}) $(_printFuncStack_)"
elif [[ -n ${_line} && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
_message="${_message} ${gray}(line: ${_line})"
elif [[ -z ${_line} && ${_alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
_message="${_message} ${gray}$(_printFuncStack_)"
fi
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
_color="${bold}${red}"
elif [ "${_alertType}" == "info" ]; then
_color="${gray}"
elif [ "${_alertType}" == "warning" ]; then
_color="${red}"
elif [ "${_alertType}" == "success" ]; then
_color="${green}"
elif [ "${_alertType}" == "debug" ]; then
_color="${purple}"
elif [ "${_alertType}" == "header" ]; then
_color="${bold}${white}${underline}"
elif [ ${_alertType} == "notice" ]; then
_color="${bold}"
elif [ ${_alertType} == "input" ]; then
_color="${bold}${underline}"
elif [ "${_alertType}" = "dryrun" ]; then
_color="${blue}"
else
_color=""
fi
_writeToScreen_() {
("${QUIET}") && return 0 # Print to console when script is not 'quiet'
[[ ${VERBOSE} == false && ${_alertType} =~ ^(debug|verbose) ]] && return 0
if ! [[ -t 1 || -z ${TERM:-} ]]; then # Don't use colors on non-recognized terminals
_color=""
reset=""
fi
printf "%s ${_color}[%7s] %s${reset}\n" "$(date +"%r")" "${_alertType}" "${_message}"
}
_writeToScreen_
_writeToLog_() {
[[ ${_alertType} == "input" ]] && return 0
[[ ${LOGLEVEL} =~ (off|OFF|Off) ]] && return 0
if [ -z "${LOGFILE:-}" ]; then
LOGFILE="$(pwd)/$(basename "$0").log"
fi
[ ! -d "$(dirname "${LOGFILE}")" ] && mkdir -p "$(dirname "${LOGFILE}")"
[[ ! -f ${LOGFILE} ]] && touch "${LOGFILE}"
# Don't use colors in logs
local cleanmessage="$(echo "${_message}" | sed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
# Print message to log file
printf "%s [%7s] %s %s\n" "$(date +"%b %d %R:%S")" "${_alertType}" "[$(/bin/hostname)]" "${cleanmessage}" >>"${LOGFILE}"
}
# Write specified log level data to logfile
case "${LOGLEVEL:-ERROR}" in
ALL | all | All)
_writeToLog_
;;
DEBUG | debug | Debug)
_writeToLog_
;;
INFO | info | Info)
if [[ ${_alertType} =~ ^(error|fatal|warning|info|notice|success) ]]; then
_writeToLog_
fi
;;
NOTICE | notice | Notice)
if [[ ${_alertType} =~ ^(error|fatal|warning|notice|success) ]]; then
_writeToLog_
fi
;;
WARN | warn | Warn)
if [[ ${_alertType} =~ ^(error|fatal|warning) ]]; then
_writeToLog_
fi
;;
ERROR | error | Error)
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
_writeToLog_
fi
;;
FATAL | fatal | Fatal)
if [[ ${_alertType} =~ ^fatal ]]; then
_writeToLog_
fi
;;
OFF | off)
return 0
;;
*)
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
_writeToLog_
fi
;;
esac
} # /_alert_
error() { _alert_ error "${1}" "${2:-}"; }
warning() { _alert_ warning "${1}" "${2:-}"; }
notice() { _alert_ notice "${1}" "${2:-}"; }
info() { _alert_ info "${1}" "${2:-}"; }
success() { _alert_ success "${1}" "${2:-}"; }
dryrun() { _alert_ dryrun "${1}" "${2:-}"; }
input() { _alert_ input "${1}" "${2:-}"; }
header() { _alert_ header "${1}" "${2:-}"; }
debug() { _alert_ debug "${1}" "${2:-}"; }
fatal() {
_alert_ fatal "${1}" "${2:-}"
_safeExit_ "1"
}
_printFuncStack_() {
# DESC:
# Prints the function stack in use. Used for debugging, and error reporting.
# ARGS:
# None
# OUTS:
# stdout: Prints [function]:[file]:[line]
# NOTE:
# Does not print functions from the alert class
local _i
_funcStackResponse=()
for ((_i = 1; _i < ${#BASH_SOURCE[@]}; _i++)); do
case "${FUNCNAME[$_i]}" in "_alert_" | "_trapCleanup_" | fatal | error | warning | notice | info | debug | dryrun | header | success) continue ;; esac
_funcStackResponse+=("${FUNCNAME[$_i]}:$(basename ${BASH_SOURCE[$_i]}):${BASH_LINENO[_i - 1]}")
done
printf "( "
printf %s "${_funcStackResponse[0]}"
printf ' < %s' "${_funcStackResponse[@]:1}"
printf ' )\n'
}
_safeExit_() {
# DESC:
# Cleanup and exit from a script
# ARGS:
# $1 (optional) - Exit code (defaults to 0)
# OUTS:
# None
if [[ -d ${SCRIPT_LOCK:-} ]]; then
if command rm -rf "${SCRIPT_LOCK}"; then
debug "Removing script lock"
else
warning "Script lock could not be removed. Try manually deleting ${tan}'${LOCK_DIR}'"
fi
fi
if [[ -n ${TMP_DIR:-} && -d ${TMP_DIR:-} ]]; then
if [[ ${1:-} == 1 && -n "$(ls "${TMP_DIR}")" ]]; then
command rm -r "${TMP_DIR}"
else
command rm -r "${TMP_DIR}"
debug "Removing temp directory"
fi
fi
trap - INT TERM EXIT
exit ${1:-0}
}
_trapCleanup_() {
# DESC:
# Log errors and cleanup from script when an error is trapped. Called by 'trap'
# ARGS:
# $1: Line number where error was trapped
# $2: Line number in function
# $3: Command executing at the time of the trap
# $4: Names of all shell functions currently in the execution call stack
# $5: Scriptname
# $6: $BASH_SOURCE
# USAGE:
# trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT SIGTERM
# OUTS:
# Exits script with error code 1
local _line=${1:-} # LINENO
local _linecallfunc=${2:-}
local _command="${3:-}"
local _funcstack="${4:-}"
local _script="${5:-}"
local _sourced="${6:-}"
if [[ "$(declare -f "fatal")" && "$(declare -f "_printFuncStack_")" ]]; then
_funcstack="'$(echo "${_funcstack}" | sed -E 's/ / < /g')'"
if [[ ${_script##*/} == "${_sourced##*/}" ]]; then
fatal "${7:-} command: '${_command}' (line: ${_line}) [func: $(_printFuncStack_)]"
else
fatal "${7:-} command: '${_command}' (func: ${_funcstack} called at line ${_linecallfunc} of '${_script##*/}') (line: ${_line} of '${_sourced##*/}') "
fi
else
printf "%s\n" "Fatal error trapped. Exiting..."
fi
if [ "$(declare -f "_safeExit_")" ]; then
_safeExit_ 1
else
exit 1
fi
}
_makeTempDir_() {
# DESC:
# Creates a temp directory to house temporary files
# ARGS:
# $1 (Optional) - First characters/word of directory name
# OUTS:
# Sets $TMP_DIR variable to the path of the temp directory
# USAGE:
# _makeTempDir_ "$(basename "$0")"
[ -d "${TMP_DIR:-}" ] && return 0
if [ -n "${1:-}" ]; then
TMP_DIR="${TMPDIR:-/tmp/}${1}.${RANDOM}.${RANDOM}.$$"
else
TMP_DIR="${TMPDIR:-/tmp/}$(basename "$0").${RANDOM}.${RANDOM}.${RANDOM}.$$"
fi
(umask 077 && mkdir "${TMP_DIR}") || {
fatal "Could not create temporary directory! Exiting."
}
debug "\$TMP_DIR=${TMP_DIR}"
}
_acquireScriptLock_() {
# DESC:
# Acquire script lock to prevent running the same script a second time before the
# first instance exits
# ARGS:
# $1 (optional) - Scope of script execution lock (system or user)
# OUTS:
# exports $SCRIPT_LOCK - Path to the directory indicating we have the script lock
# Exits script if lock cannot be acquired
# NOTE:
# If the lock was acquired it's automatically released in _safeExit_()
local _lockDir
if [[ ${1:-} == 'system' ]]; then
_lockDir="${TMPDIR:-/tmp/}$(basename "$0").lock"
else
_lockDir="${TMPDIR:-/tmp/}$(basename "$0").$UID.lock"
fi
if command mkdir "${LOCK_DIR}" 2>/dev/null; then
readonly SCRIPT_LOCK="${_lockDir}"
debug "Acquired script lock: ${yellow}${SCRIPT_LOCK}${purple}"
else
if [ "$(declare -f "_safeExit_")" ]; then
error "Unable to acquire script lock: ${tan}${LOCK_DIR}${red}"
fatal "If you trust the script isn't running, delete the lock dir"
else
printf "%s\n" "ERROR: Could not acquire script lock. If you trust the script isn't running, delete: ${LOCK_DIR}"
exit 1
fi
fi
}
_setPATH_() {
# DESC:
# Add directories to $PATH so script can find executables
# ARGS:
# $@ - One or more paths
# OUTS: Adds items to $PATH
# USAGE:
# _setPATH_ "/usr/local/bin" "${HOME}/bin" "$(npm bin)"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _newPath
for _newPath in "$@"; do
if [ -d "${_newPath}" ]; then
if ! echo "${PATH}" | grep -Eq "(^|:)${_newPath}($|:)"; then
if PATH="${_newPath}:${PATH}"; then
debug "Added '${_newPath}' to PATH"
else
return 1
fi
else
debug "_setPATH_: '${_newPath}' already exists in PATH"
fi
else
debug "_setPATH_: can not find: ${_newPath}"
return 1
fi
done
return 0
}
_useGNUutils_() {
# DESC:
# Add GNU utilities to PATH to allow consistent use of sed/grep/tar/etc. on MacOS
# ARGS:
# None
# OUTS:
# 0 if successful
# 1 if unsuccessful
# PATH: Adds GNU utilities to the path
# USAGE:
# # if ! _useGNUUtils_; then exit 1; fi
# NOTES:
# GNU utilities can be added to MacOS using Homebrew
[ ! "$(declare -f "_setPATH_")" ] && fatal "${FUNCNAME[0]} needs function _setPATH_"
if _setPATH_ \
"/usr/local/opt/gnu-tar/libexec/gnubin" \
"/usr/local/opt/coreutils/libexec/gnubin" \
"/usr/local/opt/gnu-sed/libexec/gnubin" \
"/usr/local/opt/grep/libexec/gnubin"; then
return 0
else
return 1
fi
}
_parseOptions_() {
# DESC:
# Iterates through options passed to script and sets variables. Will break -ab into -a -b
# when needed and --foo=bar into --foo bar
# ARGS:
# $@ from command line
# OUTS:
# Sets array 'ARGS' containing all arguments passed to script that were not parsed as options
# USAGE:
# _parseOptions_ "$@"
# Iterate over options
local _optstring=h
declare -a _options
local _c
local i
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i = 1; i < ${#1}; i++)); do
_c=${1:i:1}
_options+=("-${_c}") # Add current char to options
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ ${_optstring} == *"${_c}:"* && ${1:i+1} ]]; then
_options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) _options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) _options+=(--endopts) ;;
# Otherwise, nothing special
*) _options+=("$1") ;;
esac
shift
done
set -- "${_options[@]:-}"
unset _options
# Read the options and set stuff
while [[ ${1:-} == -?* ]]; do
case $1 in
# Custom options
# Common options
-h | --help)
_usage_
_safeExit_
;;
--loglevel)
shift
LOGLEVEL=${1}
;;
--logfile)
shift
LOGFILE="${1}"
;;
-n | --dryrun) DRYRUN=true ;;
-v | --verbose) VERBOSE=true ;;
-q | --quiet) QUIET=true ;;
--force) FORCE=true ;;
--endopts)
shift
break
;;
*)
if [ "$(declare -f "_safeExit_")" ]; then
fatal "invalid option: $1"
else
printf "%s\n" "Invalid option: $1"
exit 1
fi
;;
esac
shift
done
ARGS+=("$@") # Store the remaining user input as arguments.
}
_usage_() {
cat <<USAGE_TEXT
${bold}$(basename "$0") [OPTION]... [FILE]...${reset}
This is a script template. Edit this description to print help to users.
${bold}Options:${reset}
-h, --help Display this help and exit
--loglevel [LEVEL] One of: FATAL, ERROR, WARN, INFO, NOTICE, DEBUG, ALL, OFF
(Default is 'ERROR')
--logfile [FILE] Full PATH to logfile. (Default is '${HOME}/logs/$(basename "$0").log')
-n, --dryrun Non-destructive. Makes no permanent changes.
-q, --quiet Quiet (no output)
-v, --verbose Output more information. (Items echoed to 'verbose')
--force Skip all user interaction. Implied 'Yes' to all actions.
${bold}Example Usage:${reset}
${gray}# Run the script and specify log level and log file.${reset}
$(basename "$0") -vn --logfile "/path/to/file.log" --loglevel 'WARN'
USAGE_TEXT
}
# ################################## INITIALIZE AND RUN THE SCRIPT
# (Comment or uncomment the lines below to customize script behavior)
trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT SIGTERM
# Trap errors in subshells and functions
set -o errtrace
# Exit on error. Append '||true' if you expect an error
set -o errexit
# Use last non-zero exit code in a pipeline
set -o pipefail
# Confirm we have BASH greater than v4
[ "${BASH_VERSINFO:-0}" -ge 4 ] || {
printf "%s\n" "ERROR: BASH_VERSINFO is '${BASH_VERSINFO:-0}'. This script requires BASH v4 or greater."
exit 1
}
# Make `for f in *.txt` work when `*.txt` matches zero files
shopt -s nullglob globstar
# Set IFS to preferred implementation
IFS=$' \n\t'
# Run in debug mode
# set -o xtrace
# Initialize color constants
_setColors_
# Disallow expansion of unset variables
set -o nounset
# Force arguments when invoking the script
# [[ $# -eq 0 ]] && _parseOptions_ "-h"
# Parse arguments passed to script
_parseOptions_ "$@"
# Create a temp directory '$TMP_DIR'
# _makeTempDir_ "$(basename "$0")"
# Acquire script lock
# _acquireScriptLock_
# Source GNU utilities for use on MacOS
_useGNUutils_
# Run the main logic script
_mainScript_
# Exit cleanly
_safeExit_

View File

@@ -84,7 +84,7 @@ teardown() {
@test "_alert_: notice: with LINE" {
run notice "testing" "$LINENO"
assert_output --regexp ".*\[ notice\] testing \(line: [0-9]{1,3}\)"
assert_output --regexp ".*\[ notice\] testing .*\(line: [0-9]{1,3}\)"
}
@test "_alert_: refute debug" {
@@ -100,7 +100,7 @@ teardown() {
@test "_alert_: header" {
run header "testing"
assert_output --regexp "\[ header\] == testing =="
assert_output --regexp "\[ header\] testing"
}
@test "_alert_: info" {
@@ -110,12 +110,12 @@ teardown() {
@test "_alert_: fatal: with LINE" {
run fatal "testing" "$LINENO"
assert_line --index 0 --regexp ".*\[ fatal\] testing \(line: [0-9]{1,3}\) \( run:.*\)"
assert_line --index 0 --regexp ".*\[ fatal\] testing .*\(line: [0-9]{1,3}\) \( run:.*\)"
}
@test "_alert_: error" {
run error "testing"
assert_output --regexp ".*\[ error\] testing \( run:.*\)"
assert_output --regexp ".*\[ error\] testing .*\( run:.*\)"
}
@test "_alert_: input" {

View File

@@ -8,7 +8,9 @@ load 'test_helper/bats-assert/load'
######## SETUP TESTS ########
ROOTDIR="$(git rev-parse --show-toplevel)"
SOURCEFILE="${ROOTDIR}/utilities/arrays.bash"
BASEHELPERS="${ROOTDIR}/utilities/misc.bash"
ALERTS="${ROOTDIR}/utilities/alerts.bash"
CHECKS="${ROOTDIR}/utilities/checks.bash"
if test -f "${SOURCEFILE}" >&2; then
source "${SOURCEFILE}"
@@ -27,6 +29,22 @@ else
exit 1
fi
if test -f "${BASEHELPERS}" >&2; then
source "${BASEHELPERS}"
else
echo "Sourcefile not found: ${BASEHELPERS}" >&2
printf "Can not run tests.\n" >&2
exit 1
fi
if test -f "${CHECKS}" >&2; then
source "${CHECKS}"
else
echo "Sourcefile not found: ${CHECKS}" >&2
printf "Can not run tests.\n" >&2
exit 1
fi
setup() {
# Set arrays
@@ -82,40 +100,196 @@ teardown() {
assert_failure
}
@test "_join_: Join array comma" {
run _join_ , "${B[@]}"
@test "_joinArray_: Join array comma" {
run _joinArray_ , "${B[@]}"
assert_success
assert_output "1,2,3,4,5,6"
}
@test "_join_: Join array space" {
run _join_ " " "${B[@]}"
@test "_joinArray_: Join array space" {
run _joinArray_ " " "${B[@]}"
assert_success
assert_output "1 2 3 4 5 6"
}
@test "_join_: Join string complex" {
run _join_ , a "b c" d
@test "_joinArray_: Join string complex" {
run _joinArray_ , a "b c" d
assert_success
assert_output "a,b c,d"
}
@test "_join_: join string simple" {
run _join_ / var usr tmp
@test "_joinArray_: join string simple" {
run _joinArray_ / var usr tmp
assert_success
assert_output "var/usr/tmp"
}
@test "_setdiff_: Print elements not common to arrays" {
set +o nounset
run _setdiff_ "${A[*]}" "${B[*]}"
assert_output "one two three"
@test "_setDiff_: Print elements not common to arrays" {
run _setDiff_ "A[@]" "B[@]"
assert_success
assert_line --index 0 "one"
assert_line --index 1 "two"
assert_line --index 2 "three"
run _setdiff_ "${B[*]}" "${A[*]}"
assert_output "4 5 6"
run _setDiff_ "B[@]" "A[@]"
assert_success
assert_line --index 0 "4"
assert_line --index 1 "5"
assert_line --index 2 "6"
}
@test "_removeDupes_: remove duplicates" {
set +o nounset
run _removeDupes_ "${DUPES[@]}"
assert_line --index 0 "3"
@test "_setDiff_: Fail when no diff" {
run _setDiff_ "A[@]" "A[@]"
assert_failure
}
@test "_randomArrayElement_" {
run _randomArrayElement_ "${A[@]}"
assert_success
assert_output --regexp '^one|two|three|1|2|3$'
}
@test "_dedupeArray_: remove duplicates" {
run _dedupeArray_ "${DUPES[@]}"
assert_success
assert_line --index 0 "1"
assert_line --index 1 "2"
assert_line --index 2 "1"
assert_line --index 3 ""
assert_line --index 2 "3"
}
@test "_isEmptyArray_: empty" {
declare -a emptyArray=()
run _isEmptyArray_ "${emptyArray[@]}"
assert_success
}
@test "_isEmptyArray_: not empty" {
fullArray=(1 2 3)
run _isEmptyArray_ "${fullArray[@]}"
assert_failure
}
@test "_sortArray_" {
unsorted_array=("c" "b" "c" "4" "1" "3" "a" "2" "d")
run _sortArray_ "${unsorted_array[@]}"
assert_success
assert_line --index 0 "1"
assert_line --index 1 "2"
assert_line --index 2 "3"
assert_line --index 3 "4"
assert_line --index 4 "a"
assert_line --index 5 "b"
assert_line --index 6 "c"
assert_line --index 7 "c"
assert_line --index 8 "d"
}
@test "_reverseSortArray_" {
unsorted_array=("c" "b" "c" "4" "1" "3" "a" "2" "d")
run _reverseSortArray_ "${unsorted_array[@]}"
assert_success
assert_line --index 0 "d"
assert_line --index 1 "c"
assert_line --index 2 "c"
assert_line --index 3 "b"
assert_line --index 4 "a"
assert_line --index 5 "4"
assert_line --index 6 "3"
assert_line --index 7 "2"
assert_line --index 8 "1"
}
@test "_mergeArrays_" {
a1=(1 2 3)
a2=(3 2 1)
run _mergeArrays_ "a1[@]" "a2[@]"
assert_success
assert_line --index 0 "1"
assert_line --index 1 "2"
assert_line --index 2 "3"
assert_line --index 3 "3"
assert_line --index 4 "2"
assert_line --index 5 "1"
}
@test "_forEachDo_" {
test_func() {
printf "print value: %s\n" "$1"
return 0
}
array=(1 2 3 4 5)
run _forEachDo_ "test_func" < <(printf "%s\n" "${array[@]}")
assert_success
assert_line --index 0 "print value: 1"
assert_line --index 1 "print value: 2"
assert_line --index 2 "print value: 3"
assert_line --index 3 "print value: 4"
assert_line --index 4 "print value: 5"
}
@test "_forEachValidate_: success" {
array=("a" "abcdef" "ppll" "xyz")
run _forEachValidate_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
assert_success
}
@test "_forEachValidate_: failure" {
array=("a" "abcdef" "ppll99" "xyz")
run _forEachValidate_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
assert_failure
}
@test "_forEachFind_: success" {
array=("1" "234" "success" "45p9")
run _forEachFind_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
assert_success
assert_output "success"
}
@test "_forEachFind_: failure" {
array=("1" "2" "3" "4")
run _forEachFind_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
assert_failure
}
@test "_forEachFilter_" {
array=(1 2 3 a ab 5 cde 6)
run _forEachFilter_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
assert_success
assert_line --index 0 "a"
assert_line --index 1 "ab"
assert_line --index 2 "cde"
}
@test "_forEachReject_" {
array=(1 2 3 a ab 5 cde 6)
run _forEachReject_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
assert_success
assert_line --index 0 "1"
assert_line --index 1 "2"
assert_line --index 2 "3"
assert_line --index 3 "5"
assert_line --index 4 "6"
}
@test "_forEachSome_: success" {
array=("1" "234" "success" "45p9")
run _forEachSome_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
assert_success
}
@test "_forEachSome_: failure" {
array=("1" "2" "3" "4")
run _forEachSome_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
assert_failure
}

256
test/checks.bats Executable file
View File

@@ -0,0 +1,256 @@
#!/usr/bin/env bats
#shellcheck disable
load 'test_helper/bats-support/load'
load 'test_helper/bats-file/load'
load 'test_helper/bats-assert/load'
######## SETUP TESTS ########
ROOTDIR="$(git rev-parse --show-toplevel)"
SOURCEFILE="${ROOTDIR}/utilities/checks.bash"
BASEHELPERS="${ROOTDIR}/utilities/misc.bash"
ALERTS="${ROOTDIR}/utilities/alerts.bash"
if test -f "${SOURCEFILE}" >&2; then
source "${SOURCEFILE}"
else
echo "Sourcefile not found: ${SOURCEFILE}" >&2
printf "Can not run tests.\n" >&2
exit 1
fi
if test -f "${ALERTS}" >&2; then
source "${ALERTS}"
_setColors_ #Set color constants
else
echo "Sourcefile not found: ${ALERTS}" >&2
printf "Can not run tests.\n" >&2
exit 1
fi
if test -f "${BASEHELPERS}" >&2; then
source "${BASEHELPERS}"
else
echo "Sourcefile not found: ${BASEHELPERS}" >&2
printf "Can not run tests.\n" >&2
exit 1
fi
setup() {
TESTDIR="$(temp_make)"
curPath="${PWD}"
BATSLIB_FILE_PATH_REM="#${TEST_TEMP_DIR}"
BATSLIB_FILE_PATH_ADD='<temp>'
pushd "${TESTDIR}" &>/dev/null
######## DEFAULT FLAGS ########
LOGFILE="${TESTDIR}/logs/log.txt"
QUIET=false
LOGLEVEL=OFF
VERBOSE=false
FORCE=false
DRYRUN=false
set -o errtrace
set -o nounset
set -o pipefail
}
teardown() {
set +o nounset
set +o errtrace
set +o pipefail
popd &>/dev/null
temp_del "${TESTDIR}"
}
######## RUN TESTS ########
@test "Sanity..." {
run true
assert_success
assert_output ""
}
@test "_functionExists_: Success" {
run _functionExists_ "_varIsEmpty_"
assert_success
}
@test "_functionExists_: Failure" {
run _functionExists_ "_someUndefinedFunction_"
assert_failure
}
@test "_binaryExists_: true" {
run _binaryExists_ "vi"
assert_success
}
@test "_binaryExists_: false" {
run _binaryExists_ "someNonexistantBinary"
assert_failure
}
@test "_isEmail_: true" {
run _isEmail_ "some.email+name@gmail.com"
assert_success
}
@test "_isEmail_: false" {
run _isEmail_ "testemail"
assert_failure
}
@test "_isIPv4_: true" {
run _isIPv4_ "192.168.1.1"
assert_success
run _isIPv4_ "4.2.2.2"
assert_success
run _isIPv4_ "0.192.168.1"
assert_success
run _isIPv4_ "255.255.255.255"
assert_success
}
@test "_isIPv4_: false" {
run _isIPv4_ "1.b.c.d"
assert_failure
run _isIPv4_ "1234.123.123.123"
assert_failure
run _isIPv4_ "192.168.0"
assert_failure
run _isIPv4_ "255.255.255.256"
assert_failure
}
@test "_isIPv6_: true" {
run _isIPv6_ "2001:db8:85a3:8d3:1319:8a2e:370:7348"
assert_success
run _isIPv6_ "fe80::1ff:fe23:4567:890a"
assert_success
run _isIPv6_ "fe80::1ff:fe23:4567:890a%eth2"
assert_success
run _isIPv6_ "::"
assert_success
run _isIPv6_ "2001:db8::"
assert_success
}
@test "_isIPv6_: false" {
run _isIPv6_ "2001:0db8:85a3:0000:0000:8a2e:0370:7334:foo:bar"
assert_failure
run _isIPv6_ "fezy::1ff:fe23:4567:890a"
assert_failure
run _isIPv6_ "192.168.0"
}
@test "_isFile_: true" {
touch testfile.txt
run _isFile_ "testfile.txt"
assert_success
}
@test "_isFile_: false" {
run _isFile_ "testfile.txt"
assert_failure
}
@test "_isDir_: true" {
mkdir -p "some/path"
run _isDir_ "some/path"
assert_success
}
@test "_isDir_: false" {
run _isDir_ "some/path"
assert_failure
}
@test "_isAlpha_: true " {
testVar="abc"
run _isAlpha_ "${testVar}"
assert_success
}
@test "_isAlpha_: false " {
testVar="ab c"
run _isAlpha_ "${testVar}"
assert_failure
}
@test "_isNum_: true " {
testVar="123"
run _isNum_ "${testVar}"
assert_success
}
@test "_isNum_: false " {
testVar="12 3"
run _isNum_ "${testVar}"
assert_failure
}
@test "_isAlphaDash_: true " {
testVar="abc_123-xyz"
run _isAlphaDash_ "${testVar}"
assert_success
}
@test "_isAlphaDash_: false " {
testVar="abc_123 xyz"
run _isAlphaDash_ "${testVar}"
assert_failure
}
@test "_isAlphaNum_: true " {
testVar="abc123"
run _isAlphaNum_ "${testVar}"
assert_success
}
@test "_isAlphaNum_: false " {
testVar="ab c123"
run _isAlphaNum_ "${testVar}"
assert_failure
}
@test "_varIsFalse_: true" {
testvar=false
run _varIsFalse_ "${testvar}"
assert_success
}
@test "_varIsFalse_: false" {
testvar=true
run _variableIsFalse_ "${testvar}"
assert_failure
}
@test "_varIsTrue_: true" {
testvar=true
run _varIsTrue_ "${testvar}"
assert_success
}
@test "_varIsTrue_: false" {
testvar=false
run _varIsTrue_ "${testvar}"
assert_failure
}
@test "_varIsEmpty_: true" {
testvar=""
run _varIsEmpty_ "${testvar}"
assert_success
}
@test "_varIsEmpty_: false" {
testvar=test
run _varIsEmpty_ "${testvar}"
assert_failure
}

View File

@@ -8,6 +8,7 @@ load 'test_helper/bats-assert/load'
######## SETUP TESTS ########
ROOTDIR="$(git rev-parse --show-toplevel)"
SOURCEFILE="${ROOTDIR}/utilities/dates.bash"
BASEHELPERS="${ROOTDIR}/utilities/misc.bash"
ALERTS="${ROOTDIR}/utilities/alerts.bash"
if test -f "${SOURCEFILE}" >&2; then
@@ -27,6 +28,14 @@ else
exit 1
fi
if test -f "${BASEHELPERS}" >&2; then
source "${BASEHELPERS}"
else
echo "Sourcefile not found: ${BASEHELPERS}" >&2
printf "Can not run tests.\n" >&2
exit 1
fi
setup() {
TESTDIR="$(temp_make)"
@@ -47,6 +56,30 @@ setup() {
assert_output ""
}
@test "_dateUnixTimestamp_" {
run _dateUnixTimestamp_
assert_success
assert_output --regexp "^[0-9]+$"
}
@test "_convertToUnixTimestamp_" {
run _convertToUnixTimestamp_ "2020-07-07 18:38"
assert_success
assert_output "1594161480"
}
@test "_readableUnixTimestamp_: Default Format" {
run _readableUnixTimestamp_ "1591554426"
assert_success
assert_output "2020-06-07 14:27:06"
}
@test "_readableUnixTimestamp_: Custom format" {
run _readableUnixTimestamp_ "1591554426" "%Y-%m-%d"
assert_success
assert_output "2020-06-07"
}
@test "_monthToNumber_: 1" {
run _monthToNumber_ "dec"
@@ -85,20 +118,20 @@ setup() {
@test "_parseDate_: YYYY MM DD 1" {
run _parseDate_ "2019 06 01"
assert_success
assert_output --regexp "_parseDate_found: +2019 06 01"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +June"
assert_output --regexp "_parseDate_month: +6"
assert_output --regexp "PARSE_DATE_FOUND: +2019 06 01"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +June"
assert_output --regexp "PARSE_DATE_MONTH: +6"
}
@test "_parseDate_: YYYY MM DD 2" {
run _parseDate_ "this is text 2019-06-01 and more text"
assert_success
assert_output --regexp "_parseDate_found: +2019-06-01"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +June"
assert_output --regexp "_parseDate_month: +6"
assert_output --regexp "_parseDate_day: +1"
assert_output --regexp "PARSE_DATE_FOUND: +2019-06-01"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +June"
assert_output --regexp "PARSE_DATE_MONTH: +6"
assert_output --regexp "PARSE_DATE_DAY: +1"
}
@test "_parseDate_: YYYY MM DD fail 1" {
@@ -114,101 +147,101 @@ setup() {
@test "_parseDate_: Month DD, YYYY" {
run _parseDate_ "this is text Oct 22, 2019 and more text"
assert_success
assert_output --regexp "_parseDate_found: +Oct 22, +2019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +October"
assert_output --regexp "_parseDate_month: +10"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +Oct 22, +2019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +October"
assert_output --regexp "PARSE_DATE_MONTH: +10"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: Month DD YYYY" {
run _parseDate_ "Oct 22 2019"
assert_success
assert_output --regexp "_parseDate_found: +Oct 22 2019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +October"
assert_output --regexp "_parseDate_month: +10"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +Oct 22 2019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +October"
assert_output --regexp "PARSE_DATE_MONTH: +10"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: Month DD, YY" {
run _parseDate_ "this is text Oct 22, 19 and more text"
assert_success
assert_output --regexp "_parseDate_found: +Oct 22, 19"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +October"
assert_output --regexp "_parseDate_month: +10"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +Oct 22, 19"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +October"
assert_output --regexp "PARSE_DATE_MONTH: +10"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: Month DD YY" {
run _parseDate_ "Oct 22 19"
assert_success
assert_output --regexp "_parseDate_found: +Oct 22 19"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +October"
assert_output --regexp "_parseDate_month: +10"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +Oct 22 19"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +October"
assert_output --regexp "PARSE_DATE_MONTH: +10"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: DD Month, YYYY" {
run _parseDate_ "22 June, 2019 and more text"
assert_success
assert_output --regexp "_parseDate_found: +22 June, 2019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +June"
assert_output --regexp "_parseDate_month: +6"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +22 June, 2019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +June"
assert_output --regexp "PARSE_DATE_MONTH: +6"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: DD Month YYYY" {
run _parseDate_ "some text66-here-22 June 2019 and more text"
assert_success
assert_output --regexp "_parseDate_found: +22 June 2019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +June"
assert_output --regexp "_parseDate_month: +6"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +22 June 2019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +June"
assert_output --regexp "PARSE_DATE_MONTH: +6"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: MM DD YYYY 1" {
run _parseDate_ "this is text 12 22 2019 and more text"
assert_success
assert_output --regexp "_parseDate_found: +12 22 2019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +December"
assert_output --regexp "_parseDate_month: +12"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +12 22 2019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +December"
assert_output --regexp "PARSE_DATE_MONTH: +12"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: MM DD YYYY 2" {
run _parseDate_ "12 01 2019"
assert_success
assert_output --regexp "_parseDate_found: +12 01 2019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +December"
assert_output --regexp "_parseDate_month: +12"
assert_output --regexp "_parseDate_day: +1"
assert_output --regexp "PARSE_DATE_FOUND: +12 01 2019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +December"
assert_output --regexp "PARSE_DATE_MONTH: +12"
assert_output --regexp "PARSE_DATE_DAY: +1"
}
@test "_parseDate_: MM DD YYYY 3" {
run _parseDate_ "a-test-01-12-2019-is here"
assert_success
assert_output --regexp "_parseDate_found: +01-12-2019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +January"
assert_output --regexp "_parseDate_month: +1"
assert_output --regexp "_parseDate_day: +12"
assert_output --regexp "PARSE_DATE_FOUND: +01-12-2019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +January"
assert_output --regexp "PARSE_DATE_MONTH: +1"
assert_output --regexp "PARSE_DATE_DAY: +12"
}
@test "_parseDate_: DD MM YYYY 1 " {
run _parseDate_ "a-test-22/12/2019-is here"
assert_success
assert_output --regexp "_parseDate_found: +22/12/2019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +December"
assert_output --regexp "_parseDate_month: +12"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +22/12/2019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +December"
assert_output --regexp "PARSE_DATE_MONTH: +12"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: DD MM YYYY 2 " {
@@ -219,31 +252,31 @@ setup() {
@test "_parseDate_: DD MM YY" {
run _parseDate_ "a-test-22-12-19-is here"
assert_success
assert_output --regexp "_parseDate_found: +22-12-19"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +December"
assert_output --regexp "_parseDate_month: +12"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +22-12-19"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +December"
assert_output --regexp "PARSE_DATE_MONTH: +12"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: MM DD YY 1 " {
run _parseDate_ "a-test-12/22/19-is here"
assert_success
assert_output --regexp "_parseDate_found: +12/22/19"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +December"
assert_output --regexp "_parseDate_month: +12"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +12/22/19"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +December"
assert_output --regexp "PARSE_DATE_MONTH: +12"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: MM DD YY 2 " {
run _parseDate_ "6 8 19"
assert_success
assert_output --regexp "_parseDate_found: +6 8 19"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +June"
assert_output --regexp "_parseDate_month: +6"
assert_output --regexp "_parseDate_day: +8"
assert_output --regexp "PARSE_DATE_FOUND: +6 8 19"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +June"
assert_output --regexp "PARSE_DATE_MONTH: +6"
assert_output --regexp "PARSE_DATE_DAY: +8"
}
@test "_parseDate_: MM DD YY 3 " {
@@ -264,139 +297,139 @@ setup() {
@test "_parseDate_: Month, YYYY 1 " {
run _parseDate_ "a-test-January, 2019-is here"
assert_success
assert_output --regexp "_parseDate_found: +January, 2019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +January"
assert_output --regexp "_parseDate_month: +1"
assert_output --regexp "_parseDate_day: +1"
assert_output --regexp "PARSE_DATE_FOUND: +January, 2019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +January"
assert_output --regexp "PARSE_DATE_MONTH: +1"
assert_output --regexp "PARSE_DATE_DAY: +1"
}
@test "_parseDate_: Month, YYYY 2 " {
run _parseDate_ "mar-2019"
assert_success
assert_output --regexp "_parseDate_found: +mar-2019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +March"
assert_output --regexp "_parseDate_month: +3"
assert_output --regexp "_parseDate_day: +1"
assert_output --regexp "PARSE_DATE_FOUND: +mar-2019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +March"
assert_output --regexp "PARSE_DATE_MONTH: +3"
assert_output --regexp "PARSE_DATE_DAY: +1"
}
@test "_parseDate_: YYYYMMDDHHMM 1" {
run _parseDate_ "201901220228"
assert_success
assert_output --regexp "_parseDate_found: +201901220228"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +January"
assert_output --regexp "_parseDate_month: +1"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "_parseDate_hour: +2"
assert_output --regexp "_parseDate_minute: +28"
assert_output --regexp "PARSE_DATE_FOUND: +201901220228"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +January"
assert_output --regexp "PARSE_DATE_MONTH: +1"
assert_output --regexp "PARSE_DATE_DAY: +22"
assert_output --regexp "PARSE_DATE_HOUR: +2"
assert_output --regexp "PARSE_DATE_MINUTE: +28"
}
@test "_parseDate_: YYYYMMDDHHMM 2" {
run _parseDate_ "asdf 201901220228asdf "
assert_success
assert_output --regexp "_parseDate_found: +201901220228"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +January"
assert_output --regexp "_parseDate_month: +1"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "_parseDate_hour: +2"
assert_output --regexp "_parseDate_minute: +28"
assert_output --regexp "PARSE_DATE_FOUND: +201901220228"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +January"
assert_output --regexp "PARSE_DATE_MONTH: +1"
assert_output --regexp "PARSE_DATE_DAY: +22"
assert_output --regexp "PARSE_DATE_HOUR: +2"
assert_output --regexp "PARSE_DATE_MINUTE: +28"
}
@test "_parseDate_: YYYYMMDDHH 1" {
run _parseDate_ "asdf 2019012212asdf "
assert_success
assert_output --regexp "_parseDate_found: +2019012212"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +January"
assert_output --regexp "_parseDate_month: +1"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "_parseDate_hour: +12"
assert_output --regexp "_parseDate_minute: +00"
assert_output --regexp "PARSE_DATE_FOUND: +2019012212"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +January"
assert_output --regexp "PARSE_DATE_MONTH: +1"
assert_output --regexp "PARSE_DATE_DAY: +22"
assert_output --regexp "PARSE_DATE_HOUR: +12"
assert_output --regexp "PARSE_DATE_MINUTE: +00"
}
@test "_parseDate_: YYYYMMDDHH 2" {
run _parseDate_ "2019012212"
assert_success
assert_output --regexp "_parseDate_found: +2019012212"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +January"
assert_output --regexp "_parseDate_month: +1"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "_parseDate_hour: +12"
assert_output --regexp "_parseDate_minute: +00"
assert_output --regexp "PARSE_DATE_FOUND: +2019012212"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +January"
assert_output --regexp "PARSE_DATE_MONTH: +1"
assert_output --regexp "PARSE_DATE_DAY: +22"
assert_output --regexp "PARSE_DATE_HOUR: +12"
assert_output --regexp "PARSE_DATE_MINUTE: +00"
}
@test "_parseDate_: MMDDYYYY 1" {
run _parseDate_ "01222019"
assert_success
assert_output --regexp "_parseDate_found: +01222019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +January"
assert_output --regexp "_parseDate_month: +1"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +01222019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +January"
assert_output --regexp "PARSE_DATE_MONTH: +1"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: MMDDYYYY 2" {
run _parseDate_ "asdf 11222019 asdf"
assert_success
assert_output --regexp "_parseDate_found: +11222019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +November"
assert_output --regexp "_parseDate_month: +11"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +11222019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +November"
assert_output --regexp "PARSE_DATE_MONTH: +11"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: DDMMYYYY 1" {
run _parseDate_ "16012019"
assert_success
assert_output --regexp "_parseDate_found: +16012019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +January"
assert_output --regexp "_parseDate_month: +1"
assert_output --regexp "_parseDate_day: +16"
assert_output --regexp "PARSE_DATE_FOUND: +16012019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +January"
assert_output --regexp "PARSE_DATE_MONTH: +1"
assert_output --regexp "PARSE_DATE_DAY: +16"
}
@test "_parseDate_: DDMMYYYY 2" {
run _parseDate_ "asdf 16112019 asdf"
assert_success
assert_output --regexp "_parseDate_found: +16112019"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +November"
assert_output --regexp "_parseDate_month: +11"
assert_output --regexp "_parseDate_day: +16"
assert_output --regexp "PARSE_DATE_FOUND: +16112019"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +November"
assert_output --regexp "PARSE_DATE_MONTH: +11"
assert_output --regexp "PARSE_DATE_DAY: +16"
}
@test "_parseDate_: YYYYDDMM " {
run _parseDate_ "20192210"
assert_success
assert_output --regexp "_parseDate_found: +20192210"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +October"
assert_output --regexp "_parseDate_month: +10"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +20192210"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +October"
assert_output --regexp "PARSE_DATE_MONTH: +10"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: YYYYMMDD 1" {
run _parseDate_ "20191022"
assert_success
assert_output --regexp "_parseDate_found: +20191022"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +October"
assert_output --regexp "_parseDate_month: +10"
assert_output --regexp "_parseDate_day: +22"
assert_output --regexp "PARSE_DATE_FOUND: +20191022"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +October"
assert_output --regexp "PARSE_DATE_MONTH: +10"
assert_output --regexp "PARSE_DATE_DAY: +22"
}
@test "_parseDate_: YYYYMMDD 2" {
run _parseDate_ "20191010"
assert_success
assert_output --regexp "_parseDate_found: +20191010"
assert_output --regexp "_parseDate_year: +2019"
assert_output --regexp "_parseDate_monthName: +October"
assert_output --regexp "_parseDate_month: +10"
assert_output --regexp "_parseDate_day: +10"
assert_output --regexp "PARSE_DATE_FOUND: +20191010"
assert_output --regexp "PARSE_DATE_YEAR: +2019"
assert_output --regexp "PARSE_DATE_MONTH_NAME: +October"
assert_output --regexp "PARSE_DATE_MONTH: +10"
assert_output --regexp "PARSE_DATE_DAY: +10"
}
@test "_parseDate_: YYYYMMDD fail" {
@@ -426,7 +459,33 @@ setup() {
assert_output "12 27, 2019"
}
@test "_formatDate_: fail - no input " {
run _formatDate_
assert_failure
@test "_convertSecs_: Seconds to human readable" {
run _fromSeconds_ "9255"
assert_success
assert_output "02:34:15"
}
@test "_toSeconds_: HH MM SS to Seconds" {
run _toSeconds_ 12 3 33
assert_success
assert_output "43413"
}
@test "_countdown_: custom message, default wait" {
run _countdown_ 10 0 "something"
assert_line --index 0 --partial "something 10"
assert_line --index 9 --partial "something 1"
}
@test "_countdown_: default message, custom wait" {
run _countdown_ 5 0
assert_line --index 0 --partial "... 5"
assert_line --index 4 --partial "... 1"
}
@test "_countdown_: all defaults" {
run _countdown_
assert_line --index 0 --partial "... 10"
assert_line --index 9 --partial "... 1"
}

View File

@@ -7,8 +7,8 @@ load 'test_helper/bats-assert/load'
######## SETUP TESTS ########
ROOTDIR="$(git rev-parse --show-toplevel)"
SOURCEFILE="${ROOTDIR}/utilities/numbers.bash"
BASEHELPERS="${ROOTDIR}/utilities/baseHelpers.bash"
SOURCEFILE="${ROOTDIR}/utilities/debug.bash"
BASEHELPERS="${ROOTDIR}/utilities/misc.bash"
ALERTS="${ROOTDIR}/utilities/alerts.bash"
if test -f "${SOURCEFILE}" >&2; then
@@ -53,7 +53,7 @@ setup() {
VERBOSE=false
FORCE=false
DRYRUN=false
PASS=123
set -o errtrace
set -o nounset
set -o pipefail
@@ -63,7 +63,6 @@ teardown() {
set +o nounset
set +o errtrace
set +o pipefail
popd &>/dev/null
temp_del "${TESTDIR}"
}
@@ -76,33 +75,28 @@ teardown() {
assert_output ""
}
@test "_convertSecs_: Seconds to human readable" {
run _fromSeconds_ "9255"
@test "_printAnsi_" {
testString="$(tput bold)$(tput setaf 9)This is bold red text$(tput sgr0).$(tput setaf 10)This is green text$(tput sgr0)"
run _printAnsi_ "${testString}"
assert_success
assert_output "02:34:15"
assert_output "\e[1m\e[91mThis is bold red text\e(B\e[m.\e[92mThis is green text\e(B\e[m"
}
@test "_toSeconds_: HH MM SS to Seconds" {
run _toSeconds_ 12 3 33
@test "_printArray_: Array" {
testArray=(1 2 3)
run _printArray_ "testArray"
assert_success
assert_output "43413"
assert_line --index 0 "0 = 1"
assert_line --index 1 "1 = 2"
assert_line --index 2 "2 = 3"
}
@test "_countdown_: custom message, default wait" {
run _countdown_ 10 0 "something"
assert_line --index 0 --partial "something 10"
assert_line --index 9 --partial "something 1"
}
@test "_printArray_: Associative array" {
declare -A assoc_array
assoc_array=([foo]=bar [baz]=foobar)
run _printArray_ "assoc_array"
assert_success
assert_line --index 0 "foo = bar"
assert_line --index 1 "baz = foobar"
@test "_countdown_: default message, custom wait" {
run _countdown_ 5 0
assert_line --index 0 --partial "... 5"
assert_line --index 4 --partial "... 1"
}
@test "_countdown_: all defaults" {
run _countdown_
assert_line --index 0 --partial "... 10"
assert_line --index 9 --partial "... 1"
}

View File

@@ -8,7 +8,7 @@ load 'test_helper/bats-assert/load'
######## SETUP TESTS ########
ROOTDIR="$(git rev-parse --show-toplevel)"
SOURCEFILE="${ROOTDIR}/utilities/files.bash"
BASEHELPERS="${ROOTDIR}/utilities/baseHelpers.bash"
BASEHELPERS="${ROOTDIR}/utilities/misc.bash"
ALERTS="${ROOTDIR}/utilities/alerts.bash"
if test -f "${SOURCEFILE}" >&2; then
@@ -260,9 +260,6 @@ _testParseFilename_() {
assert_line --index 3 --regexp "\[ debug\].*${PARSE_EXT}: tar\.gzip\.bzip$"
assert_line --index 4 --regexp "\[ debug\].*${PARSE_BASENOEXT}: testfile$"
}
# _parseFilename_ "test.tar.gz"
# _parseFilename_ "test.tar.gzip"
}
_testMakeSymlink_() {
@@ -338,7 +335,7 @@ _testMakeSymlink_() {
_testParseYAML_() {
@test "_parseYAML: success" {
run _parseYAML_ "$YAML1"
run _parseYAML_ "$YAML1" ""
assert_success
assert_output "$( cat "$YAML1parse")"
}
@@ -383,59 +380,140 @@ _testParseYAML_() {
assert_output "hello world"
}
@test "_uniqueFileName_: no extension" {
@test "_createUniqueFilename_: no extension" {
touch "test"
run _uniqueFileName_ "test"
run _createUniqueFilename_ "test"
assert_output --regexp ".*/test\.1$"
}
@test "_uniqueFileName_: no extension - internal integer" {
@test "_createUniqueFilename_: no extension - internal integer" {
touch "test"
touch "test.1"
run _uniqueFileName_ -i "test"
run _createUniqueFilename_ -i "test"
assert_output --regexp ".*/test\.2$"
}
@test "_uniqueFileName_: Count to 3" {
@test "_createUniqueFilename_: Count to 3" {
touch "test.txt"
touch "test.txt.1"
touch "test.txt.2"
run _uniqueFileName_ "test.txt"
run _createUniqueFilename_ "test.txt"
assert_output --regexp ".*/test\.txt\.3$"
}
@test "_uniqueFileName_: internal integer" {
@test "_createUniqueFilename_: internal integer" {
touch "test.txt"
touch "test.1.txt"
touch "test.2.txt"
run _uniqueFileName_ -i "test.txt"
run _createUniqueFilename_ -i "test.txt"
assert_output --regexp ".*/test\.3\.txt$"
}
@test "_uniqueFileName_: Don't confuse existing numbers" {
@test "_createUniqueFilename_: two extensions" {
touch "test.tar.gz"
touch "test.1.tar.gz"
touch "test.2.tar.gz"
run _createUniqueFilename_ -i "test.tar.gz"
assert_output --regexp ".*/test\.3\.tar.gz$"
}
@test "_createUniqueFilename_: Don't confuse existing numbers" {
touch "test-2.txt"
run _uniqueFileName_ "test-2.txt"
run _createUniqueFilename_ "test-2.txt"
assert_output --regexp ".*/test-2\.txt\.1$"
}
@test "_uniqueFileName_: User specified separator" {
@test "_createUniqueFilename_: User specified separator" {
touch "test.txt"
run _uniqueFileName_ "test.txt" " "
run _createUniqueFilename_ "test.txt" " "
assert_output --regexp ".*/test\.txt 1$"
}
@test "_uniqueFileName_: failure" {
run _uniqueFileName_
@test "_createUniqueFilename_: failure" {
run _createUniqueFilename_
assert_failure
}
@test "_fileName_: with extension" {
run _fileName_ "./path/to/file/test.txt"
assert_success
assert_output "test.txt"
}
@test "_fileName_: without extension" {
run _fileName_ "path/to/file/test"
assert_success
assert_output "test"
}
@test "_fileBasename_" {
run _fileBasename_ "path/to/file/test.txt"
assert_success
assert_output "test"
}
@test "_fileExtension_: simple extension" {
run _fileExtension_ "path/to/file/test.txt"
assert_success
assert_output "txt"
}
@test "_fileExtension_: no extension" {
run _fileExtension_ "path/to/file/test"
assert_failure
}
@test "_fileExtension_: two level extension" {
run _fileExtension_ "path/to/file/test.tar.bz2"
assert_success
assert_output "tar.bz2"
}
@test "_fileDirectory_" {
run _fileDirectory_ "path/to/file/test.txt"
assert_success
assert_output "path/to/file"
}
@test "_fileAbsPath_: file" {
touch "./test.txt"
run _fileAbsPath_ "./test.txt"
assert_success
assert_output --regexp "/.*/files\.bats.*/test\.txt$"
}
@test "_fileAbsPath_: directory" {
mkdir "./testdir"
run _fileAbsPath_ "./testdir"
assert_success
assert_output --regexp "/.*/files\.bats.*/testdir$"
}
@test "_fileAbsPath_: fail when not found" {
run _fileAbsPath_ "./test.txt"
assert_failure
}
@test "_fileContains_: No match" {
echo "some text" > "./test.txt"
run _fileContains_ "./test.txt" "nothing here"
assert_failure
}
@test "_fileContains_: Pattern matched" {
echo "some text" > "./test.txt"
run _fileContains_ "./test.txt" "some*"
assert_success
}
_testBackupFile_
_testListFiles_
_testParseFilename_

View File

@@ -7,7 +7,7 @@ load 'test_helper/bats-assert/load'
######## SETUP TESTS ########
ROOTDIR="$(git rev-parse --show-toplevel)"
SOURCEFILE="${ROOTDIR}/utilities/baseHelpers.bash"
SOURCEFILE="${ROOTDIR}/utilities/misc.bash"
ALERTS="${ROOTDIR}/utilities/alerts.bash"
if test -f "${SOURCEFILE}" >&2; then
@@ -67,18 +67,6 @@ teardown() {
assert_output ""
}
_testCheckBinary_() {
@test "_checkBinary_: true" {
run _checkBinary_ "vi"
assert_success
}
@test "_checkBinary_: false" {
run _checkBinary_ "someNonexistantBinary"
assert_failure
}
}
_testExecute_() {
@test "_execute_: Debug command" {
DRYRUN=true
@@ -91,7 +79,7 @@ _testExecute_() {
run _execute_
assert_failure
assert_output --regexp "_execute_ needs a command$"
assert_output --regexp "\[ fatal\] Missing required argument to _execute_"
}
@test "_execute_: Bad command" {
@@ -167,73 +155,60 @@ _testExecute_() {
assert_file_not_exist "testfile.txt"
}
}
_testExecute_
_testFindBaseDirectory_() {
@test "_findBaseDir_" {
@test "_findBaseDir_" {
run _findBaseDir_
assert_output --regexp "^/usr/local/Cellar/bats-core/[0-9]\.[0-9]\.[0-9]"
}
}
_testHaveFunction_() {
@test "_haveFunction_: Success" {
run _haveFunction_ "_haveFunction_"
@test "_generateUUID_" {
run _generateUUID_
assert_success
}
@test "_haveFunction_: Failure" {
run _haveFunction_ "_someUndefinedFunction_"
assert_failure
}
assert_output --regexp "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
}
_testProgressBar_() {
@test "_progressBar_: verbose" {
@test "_makeProgressBar_: verbose" {
verbose=true
run _progressBar_ 100
run _makeProgressBar_ 100
assert_success
assert_output ""
verbose=false
}
}
@test "_progressBar_: quiet" {
@test "_makeProgressBar_: quiet" {
quiet=true
run _progressBar_ 100
run _makeProgressBar_ 100
assert_success
assert_output ""
quiet=false
}
}
_testSeekConfirmation_() {
@test "_seekConfirmation_: yes" {
@test "_seekConfirmation_: yes" {
run _seekConfirmation_ 'test' <<<"y"
assert_success
assert_output --partial "[ input] test"
}
}
@test "_seekConfirmation_: no" {
@test "_seekConfirmation_: no" {
run _seekConfirmation_ 'test' <<<"n"
assert_failure
assert_output --partial "[ input] test"
}
}
@test "_seekConfirmation_: Force" {
@test "_seekConfirmation_: Force" {
FORCE=true
run _seekConfirmation_ "test"
assert_success
assert_output --partial "test"
}
}
@test "_seekConfirmation_: Quiet" {
@test "_seekConfirmation_: Quiet" {
QUIET=true
run _seekConfirmation_ 'test' <<<"y"
@@ -241,23 +216,4 @@ _testSeekConfirmation_() {
refute_output --partial "test"
quiet=false
}
}
_testSetPATH_() {
@test "_setPATH_" {
mkdir -p "${TESTDIR}/testing/from/bats"
_setPATH_ "${TESTDIR}/testing/from/bats" "${TESTDIR}/testing/again"
run echo "${PATH}"
assert_output --regexp "/testing/from/bats"
refute_output --regexp "/testing/again"
}
}
_testCheckBinary_
_testExecute_
_testFindBaseDirectory_
_testHaveFunction_
_testProgressBar_
_testSeekConfirmation_
_testSetPATH_

View File

@@ -47,11 +47,11 @@ teardown() {
run $s -K
assert_failure
assert_output --partial "[ fatal] invalid option: '-K'"
assert_output --partial "[ fatal] invalid option: -K"
assert_file_exist "${TESTDIR}/logs/log.txt"
run cat "${TESTDIR}/logs/log.txt"
assert_line --index 0 --regexp "\[ fatal\] .* invalid option: '-K'\. \(.*"
assert_line --index 0 --regexp "\[ fatal\] .* invalid option: -K \(.*"
}
@test "success" {

128
test/standaloneTemplate.bats Executable file
View File

@@ -0,0 +1,128 @@
#!/usr/bin/env bats
load 'test_helper/bats-support/load'
load 'test_helper/bats-file/load'
load 'test_helper/bats-assert/load'
######## SETUP TESTS ########
ROOTDIR="$(git rev-parse --show-toplevel)"
s="${ROOTDIR}/standaloneTemplate.sh"
if [ -f "${s}" ]; then
base="$(basename "${s}")"
else
printf "No executable '${s}' found.\n" >&2
printf "Can not run tests.\n" >&2
exit 1
fi
setup() {
TESTDIR="$(temp_make)"
curPath="${PWD}"
BATSLIB_FILE_PATH_REM="#${TEST_TEMP_DIR}"
BATSLIB_FILE_PATH_ADD='<temp>'
s="$s --logfile=${TESTDIR}/logs/log.txt" # Logs go to temp directory
pushd "${TESTDIR}" >&2
}
teardown() {
popd >&2
temp_del "${TESTDIR}"
}
######## RUN TESTS ##########
@test "sanity" {
run true
assert_success
assert [ "$output" = "" ]
}
@test "Fail - fail on bad args and create logfile" {
run $s -K
assert_failure
assert_output --partial "[ fatal] invalid option: -K"
assert_file_exist "${TESTDIR}/logs/log.txt"
run cat "${TESTDIR}/logs/log.txt"
assert_line --index 0 --regexp "\[ fatal\] .* invalid option: -K \(.*"
}
@test "success" {
run $s
assert_success
assert_output --partial "[ info] This is info text"
assert_output --partial "[ notice] This is notice text"
assert_output --partial "[ dryrun] This is dryrun text"
assert_output --partial "[warning] This is warning text"
assert_output --partial "[ error] This is error text"
assert_output --partial "[success] This is success text"
assert_output --partial "[ input] This is input text"
assert_file_exist "${TESTDIR}/logs/log.txt"
run cat "${TESTDIR}/logs/log.txt"
assert_line --index 0 --regexp "\[ error\] \[.*\] This is error text \( _mainScript_:standaloneTemplate.* \)"
assert_line --index 1 ""
}
@test "success and INFO level log" {
run $s --loglevel=INFO
assert_success
assert_output --partial "[ info] This is info text"
run cat "${TESTDIR}/logs/log.txt"
assert_line --index 0 --regexp "\[ info\].*This is info text"
assert_line --index 1 --regexp "\[ notice\].*This is notice text"
assert_line --index 2 --regexp "\[warning\].*This is warning text"
assert_line --index 3 --regexp "\[ error\].*This is error text"
assert_line --index 4 --regexp "\[success\].*This is success text"
assert_line --index 5 ""
}
@test "success and NOTICE level log" {
run $s --loglevel=NOTICE
assert_success
assert_output --partial "[ info] This is info text"
run cat "${TESTDIR}/logs/log.txt"
assert_line --index 0 --regexp "\[ notice\].*This is notice text"
assert_line --index 1 --regexp "\[warning\].*This is warning text"
assert_line --index 2 --regexp "\[ error\].*This is error text"
assert_line --index 3 --regexp "\[success\].*This is success text"
assert_line --index 4 ""
}
@test "Usage (-h)" {
run $s -h
assert_success
assert_line --partial --index 0 "$base [OPTION]... [FILE]..."
}
@test "Usage (--help)" {
run $s --help
assert_success
assert_line --partial --index 0 "$base [OPTION]... [FILE]..."
}
@test "quiet (-q)" {
run $s -q --loglevel=INFO
assert_success
assert_output ""
run cat "${TESTDIR}/logs/log.txt"
run cat "${TESTDIR}/logs/log.txt"
assert_line --index 0 --regexp "\[ info\].*This is info text"
assert_line --index 1 --regexp "\[ notice\].*This is notice text"
assert_line --index 2 --regexp "\[warning\].*This is warning text"
assert_line --index 3 --regexp "\[ error\].*This is error text"
assert_line --index 4 --regexp "\[success\].*This is success text"
assert_line --index 5 ""
}

View File

@@ -7,8 +7,8 @@ load 'test_helper/bats-assert/load'
######## SETUP TESTS ########
ROOTDIR="$(git rev-parse --show-toplevel)"
SOURCEFILE="${ROOTDIR}/utilities/textProcessing.bash"
BASEHELPERS="${ROOTDIR}/utilities/baseHelpers.bash"
SOURCEFILE="${ROOTDIR}/utilities/strings.bash"
BASEHELPERS="${ROOTDIR}/utilities/misc.bash"
ALERTS="${ROOTDIR}/utilities/alerts.bash"
if test -f "${SOURCEFILE}" >&2; then
@@ -76,6 +76,35 @@ teardown() {
assert_output ""
}
@test "_splitString_" {
run _splitString_ "a,b,cd" ","
assert_success
assert_line --index 0 "a"
assert_line --index 1 "b"
assert_line --index 2 "cd"
}
@test "_stringContains_: success" {
run _stringContains_ "hello world!" "lo"
assert_success
}
@test "_stringContains_: failure" {
run _stringContains_ "hello world!" "zebra"
assert_failure
}
@test "_stringRegex_: success" {
run _stringRegex_ "hello world!" "[a-z].*!$"
assert_success
}
@test "_stringRegex_: failure" {
run _stringRegex_ "hello world!" "^.*[0-9]+"
assert_failure
}
_testCleanString_() {
@test "_cleanString_: fail" {
@@ -160,46 +189,46 @@ _testCleanString_
_testStopWords_() {
@test "_stopWords_: success" {
run _stopWords_ "A string to be parsed"
@test "_stripStopwords_: success" {
run _stripStopwords_ "A string to be parsed"
assert_success
assert_output "string parsed"
}
@test "_stopWords_: success w/ user terms" {
run _stopWords_ "A string to be parsed to help pass this test being performed by bats" "bats,string"
@test "_stripStopwords_: success w/ user terms" {
run _stripStopwords_ "A string to be parsed to help pass this test being performed by bats" "bats,string"
assert_success
assert_output "parsed pass performed"
}
@test "_stopWords_: No changes" {
run _stopWords_ "string parsed pass performed"
@test "_stripStopwords_: No changes" {
run _stripStopwords_ "string parsed pass performed"
assert_success
assert_output "string parsed pass performed"
}
@test "_stopWords_: fail" {
run _stopWords_
@test "_stripStopwords_: fail" {
run _stripStopwords_
assert_failure
}
}
_testStopWords_
@test "_escape_" {
run _escape_ "Here is some / text to & be - escaped"
@test "_escapeString_" {
run _escapeString_ "Here is some / text to & be - escaped"
assert_success
assert_output "Here\ is\ some\ /\ text\ to\ &\ be\ -\ escaped"
}
@test "_htmlEncode_" {
run _htmlEncode_ "Here's some text& to > be h?t/M(l• en™code磧¶d"
@test "_encodeHTML_" {
run _encodeHTML_ "Here's some text& to > be h?t/M(l• en™code磧¶d"
assert_success
assert_output "Here's some text&amp; to &gt; be h?t/M(l&bull; en&trade;code&ccedil;&pound;&sect;&para;d"
}
@test "_htmlDecode_" {
run _htmlDecode_ "&clubs;Here's some text &amp; to &gt; be h?t/M(l&bull; en&trade;code&ccedil;&pound;&sect;&para;d"
@test "_decodeHTML_" {
run _decodeHTML_ "&clubs;Here's some text &amp; to &gt; be h?t/M(l&bull; en&trade;code&ccedil;&pound;&sect;&para;d"
assert_success
assert_output "♣Here's some text & to > be h?t/M(l• en™code磧¶d"
}
@@ -232,27 +261,27 @@ _testStopWords_
assert_output "MAKE THIS UPPERCASE"
}
@test "_urlEncode_" {
run _urlEncode_ "Here's some.text%that&needs_to-be~encoded+a*few@more(characters)"
@test "_encodeURL_" {
run _encodeURL_ "Here's some.text%that&needs_to-be~encoded+a*few@more(characters)"
assert_success
assert_output "Here%27s%20some.text%25that%26needs_to-be~encoded%2Ba%2Afew%40more%28characters%29"
}
@test "_urlDecode_" {
run _urlDecode_ "Here%27s%20some.text%25that%26needs_to-be~encoded%2Ba%2Afew%40more%28characters%29"
@test "_decodeURL_" {
run _decodeURL_ "Here%27s%20some.text%25that%26needs_to-be~encoded%2Ba%2Afew%40more%28characters%29"
assert_success
assert_output "Here's some.text%that&needs_to-be~encoded+a*few@more(characters)"
}
@test "_regex_: success" {
run _regex_ "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$' || echo "no match found"
@test "_regexCapture_: success" {
run _regexCapture_ "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$' || echo "no match found"
assert_success
assert_output "#FFFFFF"
}
@test "_regex_: failure" {
run _regex_ "gggggg" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
@test "_regexCapture_: failure" {
run _regexCapture_ "gggggg" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
assert_failure
}

93
test/template_utils.bats Executable file
View File

@@ -0,0 +1,93 @@
#!/usr/bin/env bats
#shellcheck disable
load 'test_helper/bats-support/load'
load 'test_helper/bats-file/load'
load 'test_helper/bats-assert/load'
######## SETUP TESTS ########
ROOTDIR="$(git rev-parse --show-toplevel)"
SOURCEFILE="${ROOTDIR}/utilities/template_utils.bash"
ALERTS="${ROOTDIR}/utilities/alerts.bash"
if test -f "${SOURCEFILE}" >&2; then
source "${SOURCEFILE}"
else
echo "Sourcefile not found: ${SOURCEFILE}" >&2
printf "Can not run tests.\n" >&2
exit 1
fi
if test -f "${ALERTS}" >&2; then
source "${ALERTS}"
_setColors_ #Set color constants
else
echo "Sourcefile not found: ${ALERTS}" >&2
printf "Can not run tests.\n" >&2
exit 1
fi
setup() {
TESTDIR="$(temp_make)"
curPath="${PWD}"
BATSLIB_FILE_PATH_REM="#${TEST_TEMP_DIR}"
BATSLIB_FILE_PATH_ADD='<temp>'
pushd "${TESTDIR}" >&2
######## DEFAULT FLAGS ########
LOGFILE="${TESTDIR}/logs/log.txt"
QUIET=false
LOGLEVEL=ERROR
VERBOSE=false
FORCE=false
DRYRUN=false
set -o errtrace
set -o nounset
set -o pipefail
}
teardown() {
set +o nounset
set +o errtrace
set +o pipefail
popd >&2
temp_del "${TESTDIR}"
}
######## RUN TESTS ########
@test "Sanity..." {
run true
assert_success
assert_output ""
}
@test "_setPATH_: fail on dir not found" {
mkdir -p "${TESTDIR}/testing/from/bats"
mkdir -p "${TESTDIR}/testing/from/bats_again"
run _setPATH_ "${TESTDIR}/testing/from/bats" "${TESTDIR}/testing/again" "${TESTDIR}/testing/from/bats_again"
assert_failure
}
@test "_setPATH_: success" {
mkdir -p "${TESTDIR}/testing/from/bats"
mkdir -p "${TESTDIR}/testing/from/bats_again"
_setPATH_ "${TESTDIR}/testing/from/bats" "${TESTDIR}/testing/from/bats_again"
run echo "${PATH}"
assert_output --regexp "/testing/from/bats"
refute_output --regexp "/testing/again"
assert_output --regexp "/testing/from/bats_again"
}
@test "_makeTempDir_" {
VERBOSE=true
run _makeTempDir_
assert_success
assert_output --regexp "\\\$TMP_DIR=/.*\.[0-9]+\.[0-9]+\.[0-9]+$"
}

View File

@@ -1,16 +1,22 @@
_setColors_() {
# DESC: Sets colors use for alerts.
# ARGS: None
# OUTS: None
# USAGE: echo "${blue}Some text${reset}"
# Functions for providing alerts to the user and logging them
if tput setaf 1 &>/dev/null; then
_setColors_() {
# DESC:
# Sets colors use for alerts.
# ARGS:
# None
# OUTS:
# None
# USAGE:
# echo "${blue}Some text${reset}"
if tput setaf 1 >/dev/null 2>&1; then
bold=$(tput bold)
underline=$(tput smul)
reverse=$(tput rev)
reset=$(tput sgr0)
if [[ $(tput colors) -ge 256 ]] 2>/dev/null; then
if [[ $(tput colors) -ge 256 ]] >/dev/null 2>&1; then
white=$(tput setaf 231)
blue=$(tput setaf 38)
yellow=$(tput setaf 11)
@@ -46,68 +52,75 @@ _setColors_() {
}
_alert_() {
# DESC: Controls all printing of messages to log files and stdout.
# ARGS: $1 (required) - The type of alert to print
# DESC:
# Controls all printing of messages to log files and stdout.
# ARGS:
# $1 (required) - The type of alert to print
# (success, header, notice, dryrun, debug, warning, error,
# fatal, info, input)
# $2 (required) - The message to be printed to stdout and/or a log file
# $3 (optional) - Pass '${LINENO}' to print the line number where the _alert_ was triggered
# OUTS: None
# USAGE: [ALERTTYPE] "[MESSAGE]" "${LINENO}"
# NOTES: The colors of each alert type are set in this function
# For specified alert types, the funcstac will be printed
# OUTS:
# stdout: The message is printed to stdout
# log file: The message is printed to a log file
# USAGE:
# [_alertType] "[MESSAGE]" "${LINENO}"
# NOTES:
# - The colors of each alert type are set in this function
# - For specified alert types, the funcstac will be printed
local function_name color
local alertType="${1}"
local message="${2}"
local line="${3:-}" # Optional line number
local _color
local _alertType="${1}"
local _message="${2}"
local _line="${3:-}" # Optional line number
if [[ -n ${line} && ${alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
message="${message} (line: ${line}) $(_functionStack_)"
elif [[ -n ${line} && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
message="${message} (line: ${line})"
elif [[ -z ${line} && ${alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
message="${message} $(_functionStack_)"
[[ $# -lt 2 ]] && fatal 'Missing required argument to _alert_'
if [[ -n ${_line} && ${_alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
_message="${_message} ${gray}(line: ${_line}) $(_printFuncStack_)"
elif [[ -n ${_line} && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
_message="${_message} ${gray}(line: ${_line})"
elif [[ -z ${_line} && ${_alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
_message="${_message} ${gray}$(_printFuncStack_)"
fi
if [[ ${alertType} =~ ^(error|fatal) ]]; then
color="${bold}${red}"
elif [ "${alertType}" == "info" ]; then
color="${gray}"
elif [ "${alertType}" == "warning" ]; then
color="${red}"
elif [ "${alertType}" == "success" ]; then
color="${green}"
elif [ "${alertType}" == "debug" ]; then
color="${purple}"
elif [ "${alertType}" == "header" ]; then
color="${bold}${tan}"
elif [ ${alertType} == "notice" ]; then
color="${bold}"
elif [ ${alertType} == "input" ]; then
color="${bold}${underline}"
elif [ "${alertType}" = "dryrun" ]; then
color="${blue}"
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
_color="${bold}${red}"
elif [ "${_alertType}" == "info" ]; then
_color="${gray}"
elif [ "${_alertType}" == "warning" ]; then
_color="${red}"
elif [ "${_alertType}" == "success" ]; then
_color="${green}"
elif [ "${_alertType}" == "debug" ]; then
_color="${purple}"
elif [ "${_alertType}" == "header" ]; then
_color="${bold}${white}${underline}"
elif [ ${_alertType} == "notice" ]; then
_color="${bold}"
elif [ ${_alertType} == "input" ]; then
_color="${bold}${underline}"
elif [ "${_alertType}" = "dryrun" ]; then
_color="${blue}"
else
color=""
_color=""
fi
_writeToScreen_() {
("${QUIET}") && return 0 # Print to console when script is not 'quiet'
[[ ${VERBOSE} == false && ${alertType} =~ ^(debug|verbose) ]] && return 0
[[ ${VERBOSE} == false && ${_alertType} =~ ^(debug|verbose) ]] && return 0
if ! [[ -t 1 ]]; then # Don't use colors on non-recognized terminals
color=""
if ! [[ -t 1 || -z ${TERM:-} ]]; then # Don't use colors on non-recognized terminals
_color=""
reset=""
fi
echo -e "$(date +"%r") ${color}$(printf "[%7s]" "${alertType}") ${message}${reset}"
printf "%s ${_color}[%7s] %s${reset}\n" "$(date +"%r")" "${_alertType}" "${_message}"
}
_writeToScreen_
_writeToLog_() {
[[ ${alertType} == "input" ]] && return 0
[[ ${_alertType} == "input" ]] && return 0
[[ ${LOGLEVEL} =~ (off|OFF|Off) ]] && return 0
if [ -z "${LOGFILE:-}" ]; then
LOGFILE="$(pwd)/$(basename "$0").log"
@@ -116,12 +129,9 @@ _alert_() {
[[ ! -f ${LOGFILE} ]] && touch "${LOGFILE}"
# Don't use colors in logs
if command -v gsed &>/dev/null; then
local cleanmessage="$(echo "${message}" | gsed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
else
local cleanmessage="$(echo "${message}" | sed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
fi
echo -e "$(date +"%b %d %R:%S") $(printf "[%7s]" "${alertType}") [$(/bin/hostname)] ${cleanmessage}" >>"${LOGFILE}"
local cleanmessage="$(echo "${_message}" | sed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
# Print message to log file
printf "%s [%7s] %s %s\n" "$(date +"%b %d %R:%S")" "${_alertType}" "[$(/bin/hostname)]" "${cleanmessage}" >>"${LOGFILE}"
}
# Write specified log level data to logfile
@@ -133,27 +143,27 @@ _alert_() {
_writeToLog_
;;
INFO | info | Info)
if [[ ${alertType} =~ ^(die|error|fatal|warning|info|notice|success) ]]; then
if [[ ${_alertType} =~ ^(error|fatal|warning|info|notice|success) ]]; then
_writeToLog_
fi
;;
NOTICE | notice | Notice)
if [[ ${alertType} =~ ^(die|error|fatal|warning|notice|success) ]]; then
if [[ ${_alertType} =~ ^(error|fatal|warning|notice|success) ]]; then
_writeToLog_
fi
;;
WARN | warn | Warn)
if [[ ${alertType} =~ ^(die|error|fatal|warning) ]]; then
if [[ ${_alertType} =~ ^(error|fatal|warning) ]]; then
_writeToLog_
fi
;;
ERROR | error | Error)
if [[ ${alertType} =~ ^(die|error|fatal) ]]; then
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
_writeToLog_
fi
;;
FATAL | fatal | Fatal)
if [[ ${alertType} =~ ^(die|fatal) ]]; then
if [[ ${_alertType} =~ ^fatal ]]; then
_writeToLog_
fi
;;
@@ -161,7 +171,7 @@ _alert_() {
return 0
;;
*)
if [[ ${alertType} =~ ^(die|error|fatal) ]]; then
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
_writeToLog_
fi
;;
@@ -176,30 +186,117 @@ info() { _alert_ info "${1}" "${2:-}"; }
success() { _alert_ success "${1}" "${2:-}"; }
dryrun() { _alert_ dryrun "${1}" "${2:-}"; }
input() { _alert_ input "${1}" "${2:-}"; }
header() { _alert_ header "== ${1} ==" "${2:-}"; }
header() { _alert_ header "${1}" "${2:-}"; }
debug() { _alert_ debug "${1}" "${2:-}"; }
die() {
_alert_ fatal "${1}" "${2:-}"
_safeExit_ "1"
}
fatal() {
_alert_ fatal "${1}" "${2:-}"
_safeExit_ "1"
}
_functionStack_() {
# DESC: Prints the function stack in use
# ARGS: None
# OUTS: Prints [function]:[file]:[line]
# NOTE: Does not print functions from the alert class
_printFuncStack_() {
# DESC:
# Prints the function stack in use. Used for debugging, and error reporting.
# ARGS:
# None
# OUTS:
# stdout: Prints [function]:[file]:[line]
# NOTE:
# Does not print functions from the alert class
local _i
funcStackResponse=()
_funcStackResponse=()
for ((_i = 1; _i < ${#BASH_SOURCE[@]}; _i++)); do
case "${FUNCNAME[$_i]}" in "_alert_" | "_trapCleanup_" | fatal | error | warning | notice | info | verbose | debug | dryrun | header | success | die) continue ;; esac
funcStackResponse+=("${FUNCNAME[$_i]}:$(basename ${BASH_SOURCE[$_i]}):${BASH_LINENO[_i - 1]}")
case "${FUNCNAME[$_i]}" in "_alert_" | "_trapCleanup_" | fatal | error | warning | notice | info | debug | dryrun | header | success) continue ;; esac
_funcStackResponse+=("${FUNCNAME[$_i]}:$(basename ${BASH_SOURCE[$_i]}):${BASH_LINENO[_i - 1]}")
done
printf "( "
printf %s "${funcStackResponse[0]}"
printf ' < %s' "${funcStackResponse[@]:1}"
printf %s "${_funcStackResponse[0]}"
printf ' < %s' "${_funcStackResponse[@]:1}"
printf ' )\n'
}
_centerOutput_() {
# DESC:
# Prints text centered in the terminal window with an optional fill character
# ARGS:
# $1 (required): Text to center
# $2 (optional): Fill character
# OUTS:
# 0 - Success
# 1 - Failure
# stdout:
# USAGE:
# _centerOutput_ "Text to print in the center" "-"
# CREDIT:
# https://github.com/labbots/bash-utility
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _input="${1}"
local _symbol="${2:- }"
local _filler
local _out
local _no_ansi_out
local i
_no_ansi_out=$(_stripANSI_ "${_input}")
declare -i _str_len=${#_no_ansi_out}
declare -i _filler_len="$(((COLUMNS - _str_len) / 2))"
[[ -n ${_symbol} ]] && _symbol="${_symbol:0:1}"
for ((i = 0; i < _filler_len; i++)); do
_filler+="${_symbol}"
done
_out="${_filler}${_input}${_filler}"
[[ $(((COLUMNS - _str_len) % 2)) -ne 0 ]] && _out+="${_symbol}"
printf "%s\n" "${_out}"
}
_columnizeOutput_() {
# DESC:
# Creates a column output for key/value pairs with line wrapping for the right column (value). Attempts to wrap at a sane line length (~100 cols) on larger screens.
# ARGS:
# $1 (required): Left padding of table
# $2 (required): Width of first column
# $3 (required): Key name (left column text)
# $4 (required): Long value (right column text. Wraps around if too long)
# OUTS:
# stdout: Prints the columnized output
# NOTE:
# Long text or ANSI colors in the first column may create display issues
# USAGE:
# _columnizeOutput_ 0 30 10 "Key" "Long value text"
[[ $# -lt 5 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _leftIndent=$1
local _leftColumn=$2
local _key="$3"
local _value="$4"
local _line
local _rightIndent
if [ "$(tput cols)" -gt 180 ]; then
_rightIndent=80
elif [ "$(tput cols)" -gt 160 ]; then
_rightIndent=60
elif [ "$(tput cols)" -gt 130 ]; then
_rightIndent=30
elif [ "$(tput cols)" -gt 120 ]; then
_rightIndent=20
elif [ "$(tput cols)" -gt 110 ]; then
_rightIndent=10
else
_rightIndent=0
fi
local _rightWrapLength=$(($(tput cols) - _leftColumn - _leftIndent - _rightIndent))
local _first_line=0
while read -r _line; do
if [[ ${_first_line} -eq 0 ]]; then
_first_line=1
else
_key=" "
fi
printf "%-${_leftIndent}s%-${_leftColumn}b %b\n" "" "${_key}" "${_line}"
done <<<"$(fold -w${_rightWrapLength} -s <<<"${_value}")"
}

View File

@@ -1,98 +1,450 @@
# Functions for manipulating arrays
_dedupeArray_() {
# DESC:
# Removes duplicate array elements
# ARGS:
# $1 (Required) - Input array
# OUTS:
# stdout: Prints de-duped elements
# USAGE:
# _removeDups_ "${array[@]}"
# NOTE:
# List order may not stay the same
# CREDIT:
# https://github.com/dylanaraps/pure-bash-bible
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
declare -A _tmpArray
declare -a _uniqueArray
local _i
for _i in "$@"; do
{ [[ -z ${_i} || ${_tmpArray[${_i}]:-} ]]; } && continue
_uniqueArray+=("${_i}") && _tmpArray[${_i}]=x
done
printf '%s\n' "${_uniqueArray[@]}"
}
_forEachDo_() {
# DESC:
# Iterates over elements and passes each to a function
# ARGS:
# $1 (Required) - Function name to pass each item to
# OUTS:
# 0 - Success
# Return code of called function
# stdout: Output of called function
# USAGE:
# printf "%s\n" "${arr1[@]}" | _forEachDo_ "test_func"
# _forEachDo_ "test_func" < <(printf "%s\n" "${arr1[@]}") #alternative approach
# CREDIT:
# https://github.com/labbots/bash-utility
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _func="${1}"
local IFS=$'\n'
local _it
while read -r _it; do
if [[ ${_func} == *"$"* ]]; then
eval "${_func}"
else
[ ! "$(declare -f "${_func}")" ] && fatal "${FUNCNAME[0]} could not find function ${_func}"
eval "${_func}" "'${_it}'"
fi
declare -i _ret="$?"
if [[ ${_ret} -ne 0 ]]; then
return ${_ret}
fi
done
}
_forEachValidate_() {
# DESC:
# Iterates over elements and passes each to a function for validation. Iteration stops when the function returns 1.
# ARGS:
# $1 (Required) - Function name to pass each item to for validation. (Must return 0 on success)
# OUTS:
# 0 - Success
# 1 - Iteratee function fails
# USAGE:
# printf "%s\n" "${array[@]}" | _forEachValidate_ "_isAlpha_"
# _forEachValidate_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
# CREDIT:
# https://github.com/labbots/bash-utility
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _func="${1}"
local IFS=$'\n'
local _it
while read -r _it; do
if [[ ${_func} == *"$"* ]]; then
eval "${_func}"
else
[ ! "$(declare -f "${_func}")" ] && fatal "${FUNCNAME[0]} could not find function ${_func}"
eval "${_func}" "'${_it}'"
fi
declare -i _ret="$?"
if [[ ${_ret} -ne 0 ]]; then
return 1
fi
done
}
_forEachFind_() {
# DESC:
# Iterates over elements, returning the first value that is validated by a function
# ARGS:
# $1 (Required) - Function name to pass each item to for validation. (Must return 0 on success)
# OUTS:
# 0 - If successful
# 1 - If iteratee function fails
# stdout: First value that is validated by the function
# USAGE:
# printf "%s\n" "${array[@]}" | _forEachFind_ "_isAlpha_"
# _forEachFind_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
# CREDIT:
# https://github.com/labbots/bash-utility
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
declare _func="${1}"
declare IFS=$'\n'
while read -r _it; do
if [[ ${_func} == *"$"* ]]; then
eval "${_func}"
else
eval "${_func}" "'${_it}'"
fi
declare -i _ret="$?"
if [[ ${_ret} == 0 ]]; then
printf "%s" "${_it}"
return 0
fi
done
return 1
}
_forEachFilter_() {
# DESC:
# Iterates over elements, returning only those that are validated by a function
# ARGS:
# $1 (Required) - Function name to pass each item to for validation. (Must return 0 on success)
# OUTS:
# 0 - Success
# 1 - Failure
# stdout: Values matching the validation function
# USAGE:
# printf "%s\n" "${array[@]}" | _forEachFind_ "_isAlpha_"
# _forEachFilter_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
# CREDIT:
# https://github.com/labbots/bash-utility
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _func="${1}"
local IFS=$'\n'
while read -r _it; do
if [[ ${_func} == *"$"* ]]; then
eval "${_func}"
else
eval "${_func}" "'${_it}'"
fi
declare -i _ret="$?"
if [[ ${_ret} == 0 ]]; then
printf "%s\n" "${_it}"
fi
done
}
_forEachReject_() {
# DESC:
# The opposite of _forEachFilter_. Iterates over elements, returning only those that are not validated by a function
# ARGS:
# $1 (Required) - Function name to pass each item to for validation. (Must return 0 on success, 1 on failure)
# OUTS:
# 0 - Success
# stdout: Values NOT matching the validation function
# USAGE:
# printf "%s\n" "${array[@]}" | _forEachReject_ "_isAlpha_"
# _forEachReject_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
# CREDIT:
# https://github.com/labbots/bash-utility
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _func="${1}"
local IFS=$'\n'
while read -r _it; do
if [[ ${_func} == *"$"* ]]; then
eval "${_func}"
else
eval "${_func}" "'${_it}'"
fi
declare -i _ret=$?
if [[ ${_ret} -ne 0 ]]; then
echo "${_it}"
fi
done
}
_forEachSome_() {
# DESC:
# Iterates over elements, returning true if any of the elements validate as true from the function.
# ARGS:
# $1 (Required) - Function name to pass each item to for validation. (Must return 0 on success, 1 on failure)
# OUTS:
# 0 If match successful
# 1 If no match found
# USAGE:
# printf "%s\n" "${array[@]}" | _forEachSome_ "_isAlpha_"
# _forEachSome_ "_isAlpha_" < <(printf "%s\n" "${array[@]}")
# CREDIT:
# https://github.com/labbots/bash-utility
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _func="${1}"
local _IFS=$'\n'
while read -r _it; do
if [[ ${_func} == *"$"* ]]; then
eval "${_func}"
else
eval "${_func}" "'${_it}'"
fi
declare -i _ret=$?
if [[ ${_ret} -eq 0 ]]; then
return 0
fi
done
return 1
}
_inArray_() {
# DESC: Determine if a value is in an array
# ARGS: $1 (Required) - Value to search for
# DESC:
# Determine if a value is in an array. Default is case sensitive.
# Pass -i flag to ignore case.
# ARGS:
# $1 (Required) - Value to search for
# $2 (Required) - Array written as ${ARRAY[@]}
# OUTS: true/false
# USAGE: if _inArray_ "VALUE" "${ARRAY[@]}"; then ...
# OUTS:
# 0 if true
# 1 if untrue
# USAGE:
# if _inArray_ "VALUE" "${ARRAY[@]}"; then ...
# if _inArray_ -i "VALUE" "${ARRAY[@]}"; then ...
# CREDIT:
# https://github.com/labbots/bash-utility
[[ $# -lt 2 ]] && fatal 'Missing required argument to _inArray_()!'
[[ $# -lt 2 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local value="$1"
local opt
local _case_insensitive=false
local OPTIND=1
while getopts ":iI" opt; do
case ${opt} in
i | I) _case_insensitive=true ;;
*) fatal "Unrecognized option '${1}' passed to ${FUNCNAME[0]}. Exiting." ;;
esac
done
shift $((OPTIND - 1))
local _array_item
local _value="${1}"
shift
for arrayItem in "$@"; do
[[ ${arrayItem} == "${value}" ]] && return 0
for _array_item in "$@"; do
if [ ${_case_insensitive} = true ]; then
_value="$(echo "${_value}" | tr '[:upper:]' '[:lower:]')"
_array_item="$(echo "${_array_item}" | tr '[:upper:]' '[:lower:]')"
fi
[[ ${_array_item} == "${_value}" ]] && return 0
done
return 1
}
_join_() {
# DESC: Joins items together with a user specified separator
# ARGS: $1 (Required) - Separator
# $@ (Required) - Items to be joined
# OUTS: Prints joined terms
_isEmptyArray_() {
# DESC:
# Checks if an array is empty
# ARGS:
# $1 (Required) - Input array
# OUTS:
# 0 if empty
# 1 if not empty
# USAGE:
# _isEmptyArray_ "${array[@]}"
# CREDIT:
# https://github.com/labbots/bash-utility
declare -a _array=("$@")
if [ ${#_array[@]} -eq 0 ]; then
return 0
else
return 1
fi
}
_joinArray_() {
# DESC:
# Joins items together with a user specified separator
# ARGS:
# $1 (Required) - Separator
# $@ (Required) - Array or space separated items to be joined
# OUTS:
# stdout: Prints joined terms
# USAGE:
# _join_ , a "b c" d #a,b c,d
# _join_ / var local tmp #var/local/tmp
# _join_ , "${foo[@]}" #a,b,c
# NOTE: http://stackoverflow.com/questions/1527049/bash-join-elements-of-an-array
# CREDIT:
# http://stackoverflow.com/questions/1527049/bash-join-elements-of-an-array
# https://github.com/labbots/bash-utility
[[ $# -lt 2 ]] && fatal 'Missing required argument to _join_()!'
[[ $# -lt 2 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local IFS="${1}"
local _delimiter="${1}"
shift
echo "${*}"
printf "%s" "${1}"
shift
printf "%s" "${@/#/${_delimiter}}"
}
_setdiff_() {
# DESC: Return items that exist in ARRAY1 that are do not exist in ARRAY2
# ARGS: $1 (Required) - Array 1 in format ${ARRAY[*]}
# $2 (Required) - Array 2 in format ${ARRAY[*]}
# OUTS: Prints unique terms
# USAGE: _setdiff_ "${array1[*]}" "${array2[*]}"
# NOTE: http://stackoverflow.com/a/1617303/142339
_mergeArrays_() {
# DESC:
# Merges two arrays together
# ARGS:
# $1 (Required) - Array 1
# $2 (Required) - Array 2
# OUTS:
# stdout: Prints result
# USAGE:
# newarray=($(_mergeArrays_ "array1[@]" "array2[@]"))
# NOTE:
# Note that the arrays must be passed in as strings
# CREDIT:
# https://github.com/labbots/bash-utility
[[ $# -lt 2 ]] && fatal 'Missing required argument to _setdiff_()!'
local debug skip a b
if [[ $1 == 1 ]]; then
debug=1
shift
fi
if [[ "$1" ]]; then
local setdiffA setdiffB setdiffC
# shellcheck disable=SC2206
setdiffA=($1)
# shellcheck disable=SC2206
setdiffB=($2)
fi
setdiffC=()
for a in "${setdiffA[@]}"; do
skip=
for b in "${setdiffB[@]}"; do
[[ $a == "$b" ]] && skip=1 && break
done
[[ "$skip" ]] || setdiffC=("${setdiffC[@]}" "$a")
done
[[ "$debug" ]] && for a in setdiffA setdiffB setdiffC; do
#shellcheck disable=SC1087
echo "$a ($(eval echo "\${#$a[*]}")) $(eval echo "\${$a[*]}")" 1>&2
done
[[ "$1" ]] && echo "${setdiffC[@]}"
[[ $# -ne 2 ]] && fatal 'Missing required argument to _mergeArrays_'
declare -a _arr1=("${!1}")
declare -a _arr2=("${!2}")
declare _outputArray=("${_arr1[@]}" "${_arr2[@]}")
printf "%s\n" "${_outputArray[@]}"
}
_removeDupes_() {
# DESC: Removes duplicate array elements.
# ARGS: $1 (Required) - Input array
# OUTS: Prints de-duped elements to standard out
# USAGE: _removeDups_ "${array[@]}"
# NOTE: List order may not stay the same.
# https://github.com/dylanaraps/pure-bash-bible
declare -A tmp_array
_reverseSortArray_() {
# DESC:
# Sorts an array from lowest to highest (z-a9-0)
# ARGS:
# $1 (Required) - Input array
# OUTS:
# stdout: Prints result
# USAGE:
# _reverseSortArray_ "${array[@]}"
# NOTE:
# input=("c" "b" "4" "1" "2" "3" "a")
# _reverseSortArray_ "${input[@]}"
# c b a 4 3 2 1
# CREDIT:
# https://github.com/labbots/bash-utility
for i in "$@"; do
[[ $i ]] && IFS=" " tmp_array["${i:- }"]=1
done
printf '%s\n' "${!tmp_array[@]}"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
declare -a _array=("$@")
declare -a _sortedArray
mapfile -t _sortedArray < <(printf '%s\n' "${_array[@]}" | sort -r)
printf "%s\n" "${_sortedArray[@]}"
}
_randomArrayElement_() {
# DESC: Selects a random item from an array
# ARGS: $1 (Required) - Input array
# OUTS: Prints result
# USAGE: _randomArrayElement_ "${array[@]}"
# NOTE: https://github.com/dylanaraps/pure-bash-bible
# Usage: random_array_element "array"
local arr=("$@")
printf '%s\n' "${arr[RANDOM % $#]}"
# DESC:
# Selects a random item from an array
# ARGS:
# $1 (Required) - Input array
# OUTS:
# stdout: Prints one random element
# USAGE:
# _randomArrayElement_ "${array[@]}"
# CREDIT:
# https://github.com/dylanaraps/pure-bash-bible
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
declare -a _array
local _array=("$@")
printf '%s\n' "${_array[RANDOM % $#]}"
}
_setDiff_() {
# DESC:
# Return items that exist in ARRAY1 that are do not exist in ARRAY2
# ARGS:
# $1 (Required) - Array 1 (in format ARRAY[@])
# $2 (Required) - Array 2 (in format ARRAY[@])
# OUTS:
# 0 if unique elements found
# 1 if arrays are the same
# stdout: Prints unique elements
# USAGE:
# _setDiff_ "array1[@]" "array2[@]"
# mapfile -t NEW_ARRAY < <(_setDiff_ "array1[@]" "array2[@]")
# NOTE:
# Note that the arrays must be passed in as strings
# CREDIT:
# http://stackoverflow.com/a/1617303/142339
[[ $# -lt 2 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _skip
local _a
local _b
declare -a _setdiffA=("${!1}")
declare -a _setdiffB=("${!2}")
declare -a _setdiffC=()
for _a in "${_setdiffA[@]}"; do
_skip=
for _b in "${_setdiffB[@]}"; do
if [[ ${_a} == "${_b}" ]]; then
_skip=1
break
fi
done
[[ "${_skip}" ]] || _setdiffC=("${_setdiffC[@]}" "${_a}")
done
if [[ ${#_setdiffC[@]} == 0 ]]; then
return 1
else
printf "%s\n" "${_setdiffC[@]}"
fi
}
_sortArray_() {
# DESC:
# Sorts an array from lowest to highest (0-9 a-z)
# ARGS:
# $1 (Required) - Input array
# OUTS:
# stdout: Prints result
# USAGE:
# _sortArray_ "${array[@]}"
# NOTE:
# input=("c" "b" "4" "1" "2" "3" "a")
# _sortArray_ "${input[@]}"
# 1 2 3 4 a b c
# CREDIT:
# https://github.com/labbots/bash-utility
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
declare -a _array=("$@")
declare -a _sortedArray
mapfile -t _sortedArray < <(printf '%s\n' "${_array[@]}" | sort)
printf "%s\n" "${_sortedArray[@]}"
}

View File

@@ -1,373 +0,0 @@
_execute_() {
# DESC: Executes commands with safety and logging options
# ARGS: $1 (Required) - The command to be executed. Quotation marks MUST be escaped.
# $2 (Optional) - String to display after command is executed
# OPTS: -v Always print debug output from the execute function
# -n Use NOTICE level alerting (default is INFO)
# -p Pass a failed command with 'return 0'. This effectively bypasses set -e.
# -e Bypass _alert_ functions and use 'echo RESULT'
# -s Use '_alert_ success' for successful output. (default is 'info')
# -q Do not print output (QUIET mode)
# OUTS: None
# USE : _execute_ "cp -R \"~/dir/somefile.txt\" \"someNewFile.txt\"" "Optional message"
# _execute_ -sv "mkdir \"some/dir\""
# NOTE:
# If $DRYRUN=true no commands are executed
# If $VERBOSE=true the command's native output is printed to
# stderr and stdin. This can be forced with `_execute_ -v`
local LOCAL_VERBOSE=false
local PASS_FAILURES=false
local ECHO_RESULT=false
local SUCCESS_RESULT=false
local QUIET_RESULT=false
local NOTICE_RESULT=false
local opt
local OPTIND=1
while getopts ":vVpPeEsSqQnN" opt; do
case $opt in
v | V) LOCAL_VERBOSE=true ;;
p | P) PASS_FAILURES=true ;;
e | E) ECHO_RESULT=true ;;
s | S) SUCCESS_RESULT=true ;;
q | Q) QUIET_RESULT=true ;;
n | N) NOTICE_RESULT=true ;;
*)
{
error "Unrecognized option '$1' passed to _execute_. Exiting."
_safeExit_
}
;;
esac
done
shift $((OPTIND - 1))
local CMD="${1:?_execute_ needs a command}"
local EXECUTE_MESSAGE="${2:-$1}"
local SAVE_VERBOSE=${VERBOSE}
if "${LOCAL_VERBOSE}"; then
VERBOSE=true
fi
if "${DRYRUN}"; then
if "${QUIET_RESULT}"; then
VERBOSE=$SAVE_VERBOSE
return 0
fi
if [ -n "${2:-}" ]; then
dryrun "${1} (${2})" "$(caller)"
else
dryrun "${1}" "$(caller)"
fi
elif ${VERBOSE}; then
if eval "${CMD}"; then
if "${QUIET_RESULT}"; then
VERBOSE=${SAVE_VERBOSE}
return 0
elif "${ECHO_RESULT}"; then
echo "${EXECUTE_MESSAGE}"
elif "${SUCCESS_RESULT}"; then
success "${EXECUTE_MESSAGE}"
elif "${NOTICE_RESULT}"; then
notice "${EXECUTE_MESSAGE}"
else
info "${EXECUTE_MESSAGE}"
fi
VERBOSE=${SAVE_VERBOSE}
return 0
else
if "${ECHO_RESULT}"; then
echo "warning: ${EXECUTE_MESSAGE}"
else
warning "${EXECUTE_MESSAGE}"
fi
VERBOSE=${SAVE_VERBOSE}
"${PASS_FAILURES}" && return 0 || return 1
fi
else
if eval "${CMD}" &>/dev/null; then
if "${QUIET_RESULT}"; then
VERBOSE=${SAVE_VERBOSE}
return 0
elif "${ECHO_RESULT}"; then
echo "${EXECUTE_MESSAGE}"
elif "${SUCCESS_RESULT}"; then
success "${EXECUTE_MESSAGE}"
elif "${NOTICE_RESULT}"; then
notice "${EXECUTE_MESSAGE}"
else
info "${EXECUTE_MESSAGE}"
fi
VERBOSE=${SAVE_VERBOSE}
return 0
else
if "${ECHO_RESULT}"; then
echo "error: ${EXECUTE_MESSAGE}"
else
warning "${EXECUTE_MESSAGE}"
fi
VERBOSE=${SAVE_VERBOSE}
"${PASS_FAILURES}" && return 0 || return 1
fi
fi
}
_findBaseDir_() {
# DESC: Locates the real directory of the script being run. Similar to GNU readlink -n
# ARGS: None
# OUTS: Echo result to STDOUT
# USE : baseDir="$(_findBaseDir_)"
# cp "$(_findBaseDir_ "somefile.txt")" "other_file.txt"
local SOURCE
local DIR
# Is file sourced?
[[ $_ != "$0" ]] \
&& SOURCE="${BASH_SOURCE[1]}" \
|| SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="${DIR}/${SOURCE}" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
echo "$(cd -P "$(dirname "${SOURCE}")" && pwd)"
}
_checkBinary_() {
# DESC: Check if a binary exists in the search PATH
# ARGS: $1 (Required) - Name of the binary to check for existence
# OUTS: true/false
# USAGE: (_checkBinary_ ffmpeg ) && [SUCCESS] || [FAILURE]
if [[ $# -lt 1 ]]; then
error 'Missing required argument to _checkBinary_()!'
return 1
fi
if ! command -v "$1" >/dev/null 2>&1; then
debug "Did not find dependency: '$1'"
return 1
fi
return 0
}
_haveFunction_() {
# DESC: Tests if a function exists.
# ARGS: $1 (Required) - Function name
# OUTS: true/false
local f
f="$1"
if declare -f "${f}" &>/dev/null 2>&1; then
return 0
else
return 1
fi
}
_pauseScript_() {
# DESC: Pause a script at any point and continue after user input
# ARGS: $1 (Optional) - String for customized message
# OUTS: None
local pauseMessage
pauseMessage="${1:-Paused}. Ready to continue?"
if _seekConfirmation_ "${pauseMessage}"; then
info "Continuing..."
else
notice "Exiting Script"
_safeExit_
fi
}
_progressBar_() {
# DESC: Prints a progress bar within a for/while loop
# ARGS: $1 (Required) - The total number of items counted
# $2 (Optional) - The optional title of the progress bar
# OUTS: None
# USAGE:
# for number in $(seq 0 100); do
# sleep 1
# _progressBar_ "100" "Counting numbers"
# done
($QUIET) && return
($VERBOSE) && return
[ ! -t 1 ] && return # Do nothing if the output is not a terminal
[ $1 == 1 ] && return # Do nothing with a single element
local width bar_char perc num bar progressBarLine barTitle n
n="${1:?_progressBar_ needs input}"
((n = n - 1))
barTitle="${2:-Running Process}"
width=30
bar_char="#"
# Reset the count
[ -z "${progressBarProgress}" ] && progressBarProgress=0
tput civis # Hide the cursor
trap 'tput cnorm; exit 1' SIGINT
if [ ! "${progressBarProgress}" -eq $n ]; then
#echo "progressBarProgress: $progressBarProgress"
# Compute the percentage.
perc=$((progressBarProgress * 100 / $1))
# Compute the number of blocks to represent the percentage.
num=$((progressBarProgress * width / $1))
# Create the progress bar string.
bar=""
if [ ${num} -gt 0 ]; then
bar=$(printf "%0.s${bar_char}" $(seq 1 ${num}))
fi
# Print the progress bar.
progressBarLine=$(printf "%s [%-${width}s] (%d%%)" " ${barTitle}" "${bar}" "${perc}")
echo -ne "${progressBarLine}\r"
progressBarProgress=$((progressBarProgress + 1))
else
# Clear the progress bar when complete
# echo -ne "\033[0K\r"
tput el # Clear the line
unset progressBarProgress
fi
tput cnorm
}
_rootAvailable_() {
# DESC: Validate we have superuser access as root (via sudo if requested)
# ARGS: $1 (optional): Set to any value to not attempt root access via sudo
# OUTS: None
# NOTE: https://github.com/ralish/bash-script-template
local superuser
if [[ ${EUID} -eq 0 ]]; then
superuser=true
elif [[ -z ${1:-} ]]; then
if command -v sudo &>/dev/null; then
debug 'Sudo: Updating cached credentials ...'
if ! sudo -v; then
warning "Sudo: Couldn't acquire credentials ..."
else
local test_euid
test_euid="$(sudo -H -- "$BASH" -c 'printf "%s" "$EUID"')"
if [[ ${test_euid} -eq 0 ]]; then
superuser=true
fi
fi
fi
fi
if [[ -z ${superuser:-} ]]; then
debug 'Unable to acquire superuser credentials.'
return 1
fi
debug 'Successfully acquired superuser credentials.'
return 0
}
_runAsRoot_() {
# DESC: Run the requested command as root (via sudo if requested)
# ARGS: $1 (optional): Set to zero to not attempt execution via sudo
# $@ (required): Passed through for execution as root user
# OUTS: None
# NOTE: https://github.com/ralish/bash-script-template
if [[ $# -eq 0 ]]; then
fatal 'Missing required argument to _runAsRoot_()!'
fi
if [[ ${1:-} =~ ^0$ ]]; then
local skip_sudo=true
shift
fi
if [[ ${EUID} -eq 0 ]]; then
"$@"
elif [[ -z ${skip_sudo:-} ]]; then
sudo -H -- "$@"
else
fatal "Unable to run requested command as root: $*"
fi
}
_seekConfirmation_() {
# DESC: Seek user input for yes/no question
# ARGS: $1 (Optional) - Question being asked
# OUTS: true/false
# USAGE: _seekConfirmation_ "Do something?" && echo "okay" || echo "not okay"
# OR
# if _seekConfirmation_ "Answer this question"; then
# something
# fi
local yn
input "${1:-}"
if "${FORCE}"; then
debug "Forcing confirmation with '--force' flag set"
echo -e ""
return 0
else
while true; do
read -r -p " (y/n) " yn
case $yn in
[Yy]*) return 0 ;;
[Nn]*) return 1 ;;
*) input "Please answer yes or no." ;;
esac
done
fi
}
_setPATH_() {
# DESC: Add directories to $PATH so script can find executables
# ARGS: $@ - One or more paths
# OUTS: $PATH
# USAGE: _setPATH_ "/usr/local/bin" "${HOME}/bin" "$(npm bin)"
local NEWPATH NEWPATHS USERPATH
for USERPATH in "$@"; do
NEWPATHS+=("$USERPATH")
done
for NEWPATH in "${NEWPATHS[@]}"; do
if [ -d "${NEWPATH}" ]; then
if ! echo "$PATH" | grep -Eq "(^|:)${NEWPATH}($|:)"; then
PATH="${NEWPATH}:${PATH}"
debug "Added '${NEWPATH}' to PATH"
fi
fi
done
}
_safeExit_() {
# DESC: Cleanup and exit from a script
# ARGS: $1 (optional) - Exit code (defaults to 0)
# OUTS: None
if [[ -d ${SCRIPT_LOCK:-} ]]; then
if command rm -rf "${SCRIPT_LOCK}"; then
debug "Removing script lock"
else
warning "Script lock could not be removed. Try manually deleting ${tan}'${LOCK_DIR}'${red}"
fi
fi
if [[ -n ${TMP_DIR:-} && -d ${TMP_DIR:-} ]]; then
if [[ ${1:-} == 1 && -n "$(ls "${TMP_DIR}")" ]]; then
command rm -r "${TMP_DIR}"
else
command rm -r "${TMP_DIR}"
debug "Removing temp directory"
fi
fi
trap - INT TERM EXIT
exit ${1:-0}
}

352
utilities/checks.bash Normal file
View File

@@ -0,0 +1,352 @@
# Functions for validating common use-cases
_binaryExists_() {
# DESC:
# Check if a binary exists in the search PATH
# ARGS:
# $1 (Required) - Name of the binary to check for existence
# OUTS:
# 0 if true
# 1 if false
# USAGE:
# (_binaryExists_ ffmpeg ) && [SUCCESS] || [FAILURE]
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
if ! command -v "$1" >/dev/null 2>&1; then
debug "Did not find dependency: '${1}'"
return 1
fi
return 0
}
_functionExists_() {
# DESC:
# Tests if a function exists in the current scope
# ARGS:
# $1 (Required) - Function name
# OUTS:
# 0 if function exists
# 1 if function does not exist
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _testFunction
_testFunction="${1}"
if declare -f "${_testFunction}" &>/dev/null 2>&1; then
return 0
else
return 1
fi
}
_isAlpha_() {
# DESC:
# Validate that a given input is entirely alphabetic characters
# ARGS:
# $1 (required): Input to check
# OUTS:
# 0 - Input is only alphabetic characters
# 1 - Input contains non-alphabetic characters
# USAGE:
# _isAlpha_ "${var}"
# NOTES:
#
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _re='^[[:alpha:]]+$'
if [[ ${1} =~ ${_re} ]]; then
return 0
fi
return 1
}
_isAlphaNum_() {
# DESC:
# Validate that a given input is entirely alpha-numeric characters
# ARGS:
# $1 (required): Input to check
# OUTS:
# 0 - Input is only alpha-numeric characters
# 1 - Input contains alpha-numeric characters
# USAGE:
# _isAlphaNum_ "${var}"
# NOTES:
#
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _re='^[[:alnum:]]+$'
if [[ ${1} =~ ${_re} ]]; then
return 0
fi
return 1
}
_isAlphaDash_() {
# DESC:
# Validate that a given input contains only alpha-numeric characters, as well as dashes and underscores.
# ARGS:
# $1 (required): Input to check
# OUTS:
# 0 - Input is only alpha-numeric or dash or underscore characters
# 1 - Input is not only alpha-numeric or dash or underscore characters
# USAGE:
# _isAlphaDash_ "${var}"
# NOTES:
#
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _re='^[[:alnum:]_-]+$'
if [[ ${1} =~ ${_re} ]]; then
return 0
fi
return 1
}
_isEmail_() {
# DESC:
# Validates that input is a valid email address
# ARGS:
# $1 (required): Input to check
# OUTS:
# 0 - Is valid email
# 1 - Is not valid email
# USAGE:
# _isEmail_ "somename+test@gmail.com"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _emailRegex
_emailRegex="^[a-z0-9!#\$%&'*+/=?^_\`{|}~-]+(\.[a-z0-9!#$%&'*+/=?^_\`{|}~-]+)*@([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?\$"
[[ ${1} =~ ${_emailRegex} ]] && return 0 || return 1
}
_isInternetAvailable_() {
# DESC:
# Check if internet connection is available
# ARGS:
# None
# OUTS:
# 0 - Success: Internet connection is available
# 1 - Failure: Internet connection is not available
# stdout:
# USAGE:
# _isInternetAvailable_
# NOTES:
#
local _checkInternet
if [[ -t 1 || -z ${TERM} ]]; then
_checkInternet="$(sh -ic 'exec 3>&1 2>/dev/null; { curl --compressed -Is google.com 1>&3; kill 0; } | { sleep 10; kill 0; }' || :)"
else
_checkInternet="$(curl --compressed -Is google.com -m 10)"
fi
if [[ -z ${_checkInternet:-} ]]; then
return 1
fi
}
_isIPv4_() {
# DESC:
# Validates that input is a valid IP version 4 address
# ARGS:
# $1 (required): Input to check
# OUTS:
# 0 - Is valid IPv4 address
# 1 - Is not valid IPv4 address
# USAGE:
# _isIPv4_ "192.168.1.1"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _ip="${1}"
local IFS=.
# shellcheck disable=SC2206
declare -a _a=(${_ip})
[[ ${_ip} =~ ^[0-9]+(\.[0-9]+){3}$ ]] || return 1
# Test values of quads
local _quad
for _quad in {0..3}; do
[[ ${_a[${_quad}]} -gt 255 ]] && return 1
done
return 0
}
_isFile_() {
# DESC:
# Validate that a given input points to a valid file
# ARGS:
# $1 (required): Input to check
# OUTS:
# 0 - Input is a valid file
# 1 - Input is not a valid file
# USAGE:
# _varIsFile_ "${var}"
# NOTES:
#
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
[[ -f ${1} ]] && return 0 || return 1
}
_isDir_() {
# DESC:
# Validate that a given input points to a valid directory
# ARGS:
# $1 (required): Input to check
# OUTS:
# 0 - Input is a directory
# 1 - Input is not a directory
# USAGE:
# _varIsDir_ "${var}"
# NOTES:
#
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
[[ -d ${1} ]] && return 0 || return 1
}
_isNum_() {
# DESC:
# Validate that a given input is entirely numeric characters
# ARGS:
# $1 (required): Input to check
# OUTS:
# 0 - Input is only numeric characters
# 1 - Input contains numeric characters
# USAGE:
# _isNum_ "${var}"
# NOTES:
#
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _re='^[[:digit:]]+$'
if [[ ${1} =~ ${_re} ]]; then
return 0
fi
return 1
}
_isTerminal_() {
# DESC:
# Check is script is run in an interactive terminal
# ARGS:
# None
# OUTS:
# 0 - Script is run in a terminal
# 1 - Script is not run in a terminal
# USAGE:
# _isTerminal_
[[ -t 1 || -z ${TERM} ]] && return 0 || return 1
}
_rootAvailable_() {
# DESC:
# Validate we have superuser access as root (via sudo if requested)
# ARGS:
# $1 (optional): Set to any value to not attempt root access via sudo
# OUTS:
# 0 if true
# 1 if false
# CREDIT:
# https://github.com/ralish/bash-script-template
local _superuser
local _testEUID
if [[ ${EUID} -eq 0 ]]; then
_superuser=true
elif [[ -z ${1:-} ]]; then
if command -v sudo >/dev/null 2>&1; then
debug 'Sudo: Updating cached credentials ...'
if ! sudo -v; then
warning "Sudo: Couldn't acquire credentials ..."
else
_testEUID="$(sudo -H -- "$BASH" -c 'printf "%s" "$EUID"')"
if [[ ${_testEUID} -eq 0 ]]; then
_superuser=true
fi
fi
fi
fi
if [[ -z ${superuser:-} ]]; then
debug 'Unable to acquire superuser credentials.'
return 1
fi
debug 'Successfully acquired superuser credentials.'
return 0
}
_varIsTrue_() {
# DESC:
# Check if a given variable is true
# ARGS:
# $1 (required): Variable to check
# OUTS:
# 0 - Variable is true
# 1 - Variable is false
# USAGE
# _varIsTrue_ "${var}"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
[[ ${1} == true || ${1} -eq 0 ]] && return 0 || return 1
}
_varIsFalse_() {
# DESC:
# Check if a given variable is false
# ARGS:
# $1 (required): Variable to check
# OUTS:
# 0 - Variable is false
# 1 - Variable is true
# USAGE
# _varIsFalse_ "${var}"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
[[ ${1} == false || ${1} -eq 1 ]] && return 0 || return 1
}
_varIsEmpty_() {
# DESC:
# Check if given variable is empty or null.
# ARGS:
# $1 (required): Variable to check
# OUTS:
# 0 - Variable is empty or null
# 1 - Variable is not empty or null
# USAGE
# _varIsEmpty_ "${var}"
[[ -z ${1} || ${1} == "null" ]] && return 0 || return 1
}
_isIPv6_() {
# DESC:
# Validates that input is a valid IP version 46address
# ARGS:
# $1 (required): Input to check
# OUTS:
# 0 - Is valid IPv6 address
# 1 - Is not valid IPv6 address
# USAGE:
# _isIPv6_ "2001:db8:85a3:8d3:1319:8a2e:370:7348"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _ip="${1}"
local _re="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|\
([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|\
([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|\
([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|\
:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|\
::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|\
(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|\
(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"
[[ ${_ip} =~ ${_re} ]] && return 0 || return 1
}

View File

@@ -1,34 +0,0 @@
_makeCSV_() {
# Creates a new CSV file if one does not already exist
# Takes passed arguments and writes them as a header line to the CSV
# Usage '_makeCSV_ column1 column2 column3'
# Set the location and name of the CSV File
if [ -z "${csvLocation}" ]; then
csvLocation="${HOME}/Desktop"
fi
if [ -z "${csvName}" ]; then
csvName="$(LC_ALL=C date +%Y-%m-%d)-${FUNCNAME[1]}.csv"
fi
csvFile="${csvLocation}/${csvName}"
# Overwrite existing file? If not overwritten, new content is added
# to the bottom of the existing file
if [ -f "${csvFile}" ]; then
if _seekConfirmation_ "${csvFile} already exists. Overwrite?"; then
rm "${csvFile}"
fi
fi
_writeCSV_ "$@"
}
_writeCSV_() {
# Takes passed arguments and writes them as a comma separated line
# Usage '_writeCSV_ column1 column2 column3'
local csvInput=("$@")
saveIFS=$IFS
IFS=','
echo "${csvInput[*]}" >>"${csvFile}"
IFS=${saveIFS}
}

View File

@@ -1,12 +1,142 @@
# Functions to help work with dates and time
_convertToUnixTimestamp_() {
# DESC:
# Convert date string to unix timestamp
# ARGS:
# $1 (Required) - Date to be converted
# OUTS:
# 0 If successful
# 1 If failed to convert
# stdout: timestamp for specified date/time
# USAGE:
# printf "%s\n" "$(_convertToUnixTimestamp_ "Jan 10, 2019")"
# NOTES:
#
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _date
_date=$(date -d "${1}" +"%s") || return 1
printf "%s\n" "${_date}"
}
_countdown_() {
# DESC:
# Sleep for a specified amount of time
# ARGS:
# $1 (Optional) - Total seconds to sleep for(Default is 10)
# $2 (Optional) - Increment to count down
# $3 (Optional) - Message to print at each increment (default is ...)
# OUTS:
# stdout: Prints the message at each increment
# USAGE:
# _countdown_ 10 1 "Waiting for cache to invalidate"
local i ii t
local _n=${1:-10}
local _sleepTime=${2:-1}
local _message="${3:-...}"
((t = _n + 1))
for ((i = 1; i <= _n; i++)); do
((ii = t - i))
if declare -f "info" &>/dev/null 2>&1; then
info "${_message} ${ii}"
else
echo "${_message} ${ii}"
fi
sleep ${_sleepTime}
done
}
_dateUnixTimestamp_() {
# DESC:
# Get the current time in unix timestamp
# ARGS:
# None
# OUTS:
# stdout: Prints result ~ 1591554426
# 0 If successful
# 1 If failed to get timestamp
# USAGE:
# _dateUnixTimestamp_
local _now
_now="$(date --universal +%s)" || return 1
printf "%s\n" "${_now}"
}
_formatDate_() {
# DESC:
# Reformats dates into user specified formats
# ARGS:
# $1 (Required) - Date to be formatted
# $2 (Optional) - Format in any format accepted by bash's date command.
# Examples:
# %F - YYYY-MM-DD
# %D - MM/DD/YY
# %a - Name of weekday in short (like Sun, Mon, Tue, Wed, Thu, Fri, Sat)
# %A - Name of weekday in full (like Sunday, Monday, Tuesday)
# '+%m %d, %Y' - 12 27, 2019
# OUTS:
# stdout: Prints result
# USAGE:
# _formatDate_ "Jan 10, 2019" "%D"
# NOTE:
# Defaults to YYYY-MM-DD or $(date +%F)
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _d="${1}"
local _format="${2:-%F}"
_format="${_format//+/}"
date -d "${_d}" "+${_format}"
}
_fromSeconds_() {
# DESC:
# Convert seconds to HH:MM:SS
# ARGS:
# $1 (Required) - Time in seconds
# OUTS:
# stdout: HH:MM:SS
# USAGE:
# _fromSeconds_ "SECONDS"
# EXAMPLE:
# To compute the time it takes a script to run:
# STARTTIME=$(date +"%s")
# ENDTIME=$(date +"%s")
# TOTALTIME=$(($ENDTIME-$STARTTIME)) # human readable time
# _fromSeconds_ "$TOTALTIME"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _h
local _m
local _s
((_h = ${1} / 3600))
((_m = (${1} % 3600) / 60))
((_s = ${1} % 60))
printf "%02d:%02d:%02d\n" ${_h} ${_m} ${_s}
}
_monthToNumber_() {
# DESC: Convert a month name to a number
# ARGS: None
# OUTS: Prints the number of the month to stdout
# USAGE: _monthToNumber_ "January"
# DESC:
# Convert a month name to a number
# ARGS:
# $1 (Required) - Month name
# OUTS:
# stdout: Prints the number of the month (1-12)
# USAGE:
# _monthToNumber_ "January"
local mon="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
case "$mon" in
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _mon="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
case "${_mon}" in
january | jan | ja) echo 1 ;;
february | feb | fe) echo 2 ;;
march | mar | ma) echo 3 ;;
@@ -16,24 +146,30 @@ _monthToNumber_() {
july | jul) echo 7 ;;
august | aug | au) echo 8 ;;
september | sep | se) echo 9 ;;
october | oct) echo 10 ;;
october | oct | oc) echo 10 ;;
november | nov | no) echo 11 ;;
december | dec | de) echo 12 ;;
*)
warning "month_monthToNumber_: Bad monthname: $1"
warning "_monthToNumber_: Bad month name: ${_mon}"
return 1
;;
esac
}
_numberToMonth_() {
# DESC: Convert a month number to its name
# ARGS: None
# OUTS: Prints the name of the month to stdout
# USAGE: _numberToMonth_ 1
# DESC:
# Convert a month number to its name
# ARGS:
# $1 (Required) - Month number (1-12)
# OUTS:
# stdout: Prints the name of the month
# USAGE:
# _numberToMonth_ 11
local mon="$1"
case "$mon" in
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _mon="$1"
case "${_mon}" in
1 | 01) echo January ;;
2 | 02) echo February ;;
3 | 03) echo March ;;
@@ -47,27 +183,34 @@ _numberToMonth_() {
11) echo November ;;
12) echo December ;;
*)
warning "_numberToMonth_: Bad month number: $1"
warning "_numberToMonth_: Bad month number: ${_mon}"
return 1
;;
esac
}
_parseDate_() {
# DESC: Takes a string as input and attempts to find a date within it
# to parse into component parts (day, month, year)
# ARGS: $1 (required) - A string
# OUTS: Returns error if no date found
# $_parseDate_found - The date found in the string
# $_parseDate_year - The year
# $_parseDate_month - The number month
# $_parseDate_monthName - The name of the month
# $_parseDate_day - The day
# $_parseDate_hour - The hour (if avail)
# $_parseDate_minute - The minute (if avail)
# USAGE: if _parseDate_ "[STRING]"; then ...
# NOTE: This function only recognizes dates from the year 2000 to 2029
# NOTE: Will recognize dates in the following formats separated by '-_ ./'
# DESC:
# Takes a string as input and attempts to find a date within it to parse
# into component parts (day, month, year)
# ARGS:
# $1 (required) - A string
# OUTS:
# 0 if date is found
# 1 if date is NOT found
# If a date was found, the following variables are set:
# $PARSE_DATE_FOUND - The date found in the string
# $PARSE_DATE_YEAR - The year
# $PARSE_DATE_MONTH - The number month
# $PARSE_DATE_MONTH_NAME - The name of the month
# $PARSE_DATE_DAY - The day
# $PARSE_DATE_HOUR - The hour (if avail)
# $PARSE_DATE_MINUTE - The minute (if avail)
# USAGE:
# if _parseDate_ "[STRING]"; then ...
# NOTE:
# - This function only recognizes dates from the year 2000 to 202
# - Will recognize dates in the following formats separated by '-_ ./'
# * YYYY-MM-DD * Month DD, YYYY * DD Month, YYYY
# * Month, YYYY * Month, DD YY * MM-DD-YYYY
# * MMDDYYYY * YYYYMMDD * DDMMYYYY
@@ -77,124 +220,122 @@ _parseDate_() {
# * MMDDYY * YYMMDD * mon-DD-YY
# TODO: Simplify and reduce the number of regex checks
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
[[ $# -eq 0 ]] && {
error 'Missing required argument to _parseDate_()!'
return 1
}
local _stringToTest="${1}"
local _pat
local date="${1:-$(date +%F)}"
_parseDate_found="" _parseDate_year="" _parseDate_month="" _parseDate_monthName=""
_parseDate_day="" _parseDate_hour="" _parseDate_minute=""
PARSE_DATE_FOUND="" PARSE_DATE_YEAR="" PARSE_DATE_MONTH="" PARSE_DATE_MONTH_NAME=""
PARSE_DATE_DAY="" PARSE_DATE_HOUR="" PARSE_DATE_MINUTE=""
shopt -s nocasematch #Use case-insensitive regex
debug "_parseDate_() input ${tan}$date${purple}"
# YYYY MM DD or YYYY-MM-DD
pat="(.*[^0-9]|^)((20[0-2][0-9])[-\.\/_ ]+([0-9]{1,2})[-\.\/_ ]+([0-9]{1,2}))([^0-9].*|$)"
if [[ ${date} =~ $pat ]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year=$((10#${BASH_REMATCH[3]}))
_parseDate_month=$((10#${BASH_REMATCH[4]}))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$((10#${BASH_REMATCH[5]}))
_pat="(.*[^0-9]|^)((20[0-2][0-9])[-\.\/_ ]+([0-9]{1,2})[-\.\/_ ]+([0-9]{1,2}))([^0-9].*|$)"
if [[ ${_stringToTest} =~ ${_pat} ]]; then
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_YEAR=$((10#${BASH_REMATCH[3]}))
PARSE_DATE_MONTH=$((10#${BASH_REMATCH[4]}))
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_DAY=$((10#${BASH_REMATCH[5]}))
debug "regex match: ${tan}YYYY-MM-DD${purple}"
# Month DD, YYYY
elif [[ ${date} =~ ((january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec)[-\./_ ]+([0-9]{1,2})(nd|rd|th|st)?,?[-\./_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[1]:-}"
_parseDate_month=$(_monthToNumber_ ${BASH_REMATCH[2]:-})
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month:-}")"
_parseDate_day=$((10#${BASH_REMATCH[3]:-}))
_parseDate_year=$((10#${BASH_REMATCH[5]:-}))
elif [[ ${_stringToTest} =~ ((january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec)[-\./_ ]+([0-9]{1,2})(nd|rd|th|st)?,?[-\./_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
PARSE_DATE_FOUND="${BASH_REMATCH[1]:-}"
PARSE_DATE_MONTH=$(_monthToNumber_ ${BASH_REMATCH[2]:-})
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH:-}")"
PARSE_DATE_DAY=$((10#${BASH_REMATCH[3]:-}))
PARSE_DATE_YEAR=$((10#${BASH_REMATCH[5]:-}))
debug "regex match: ${tan}Month DD, YYYY${purple}"
# Month DD, YY
elif [[ ${date} =~ ((january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec)[-\./_ ]+([0-9]{1,2})(nd|rd|th|st)?,?[-\./_ ]+([0-9]{2}))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[1]}"
_parseDate_month=$(_monthToNumber_ ${BASH_REMATCH[2]})
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$((10#${BASH_REMATCH[3]}))
_parseDate_year="20$((10#${BASH_REMATCH[5]}))"
elif [[ ${_stringToTest} =~ ((january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec)[-\./_ ]+([0-9]{1,2})(nd|rd|th|st)?,?[-\./_ ]+([0-9]{2}))([^0-9].*|$) ]]; then
PARSE_DATE_FOUND="${BASH_REMATCH[1]}"
PARSE_DATE_MONTH=$(_monthToNumber_ ${BASH_REMATCH[2]})
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_DAY=$((10#${BASH_REMATCH[3]}))
PARSE_DATE_YEAR="20$((10#${BASH_REMATCH[5]}))"
debug "regex match: ${tan}Month DD, YY${purple}"
# DD Month YYYY
elif [[ ${date} =~ (.*[^0-9]|^)(([0-9]{2})[-\./_ ]+(january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec),?[-\./_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day=$((10#"${BASH_REMATCH[3]}"))
_parseDate_month="$(_monthToNumber_ "${BASH_REMATCH[4]}")"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year=$((10#"${BASH_REMATCH[5]}"))
elif [[ ${_stringToTest} =~ (.*[^0-9]|^)(([0-9]{2})[-\./_ ]+(january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec),?[-\./_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_DAY=$((10#"${BASH_REMATCH[3]}"))
PARSE_DATE_MONTH="$(_monthToNumber_ "${BASH_REMATCH[4]}")"
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_YEAR=$((10#"${BASH_REMATCH[5]}"))
debug "regex match: ${tan}DD Month, YYYY${purple}"
# MM-DD-YYYY or DD-MM-YYYY
elif [[ ${date} =~ (.*[^0-9]|^)(([0-9]{1,2})[-\.\/_ ]+([0-9]{1,2})[-\.\/_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
elif [[ ${_stringToTest} =~ (.*[^0-9]|^)(([0-9]{1,2})[-\.\/_ ]+([0-9]{1,2})[-\.\/_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
if [[ $((10#${BASH_REMATCH[3]})) -lt 13 &&
$((10#${BASH_REMATCH[4]})) -gt 12 &&
$((10#${BASH_REMATCH[4]})) -lt 32 ]] \
; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year=$((10#${BASH_REMATCH[5]}))
_parseDate_month=$((10#${BASH_REMATCH[3]}))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$((10#${BASH_REMATCH[4]}))
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_YEAR=$((10#${BASH_REMATCH[5]}))
PARSE_DATE_MONTH=$((10#${BASH_REMATCH[3]}))
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_DAY=$((10#${BASH_REMATCH[4]}))
debug "regex match: ${tan}MM-DD-YYYY${purple}"
elif [[ $((10#${BASH_REMATCH[3]})) -gt 12 &&
$((10#${BASH_REMATCH[3]})) -lt 32 &&
$((10#${BASH_REMATCH[4]})) -lt 13 ]] \
; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year=$((10#${BASH_REMATCH[5]}))
_parseDate_month=$((10#${BASH_REMATCH[4]}))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$((10#${BASH_REMATCH[3]}))
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_YEAR=$((10#${BASH_REMATCH[5]}))
PARSE_DATE_MONTH=$((10#${BASH_REMATCH[4]}))
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_DAY=$((10#${BASH_REMATCH[3]}))
debug "regex match: ${tan}DD-MM-YYYY${purple}"
elif [[ $((10#${BASH_REMATCH[3]})) -lt 32 &&
$((10#${BASH_REMATCH[4]})) -lt 13 ]] \
; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year=$((10#${BASH_REMATCH[5]}))
_parseDate_month=$((10#${BASH_REMATCH[3]}))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$((10#${BASH_REMATCH[4]}))
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_YEAR=$((10#${BASH_REMATCH[5]}))
PARSE_DATE_MONTH=$((10#${BASH_REMATCH[3]}))
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_DAY=$((10#${BASH_REMATCH[4]}))
debug "regex match: ${tan}MM-DD-YYYY${purple}"
else
shopt -u nocasematch
return 1
fi
elif [[ ${date} =~ (.*[^0-9]|^)(([0-9]{1,2})[-\.\/_ ]+([0-9]{1,2})[-\.\/_ ]+([0-9]{2}))([^0-9].*|$) ]]; then
elif [[ ${_stringToTest} =~ (.*[^0-9]|^)(([0-9]{1,2})[-\.\/_ ]+([0-9]{1,2})[-\.\/_ ]+([0-9]{2}))([^0-9].*|$) ]]; then
if [[ $((10#${BASH_REMATCH[3]})) -lt 13 &&
$((10#${BASH_REMATCH[4]})) -gt 12 &&
$((10#${BASH_REMATCH[4]})) -lt 32 ]] \
; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year="20$((10#${BASH_REMATCH[5]}))"
_parseDate_month=$((10#${BASH_REMATCH[3]}))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$((10#${BASH_REMATCH[4]}))
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_YEAR="20$((10#${BASH_REMATCH[5]}))"
PARSE_DATE_MONTH=$((10#${BASH_REMATCH[3]}))
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_DAY=$((10#${BASH_REMATCH[4]}))
debug "regex match: ${tan}MM-DD-YYYY${purple}"
elif [[ $((10#${BASH_REMATCH[3]})) -gt 12 &&
$((10#${BASH_REMATCH[3]})) -lt 32 &&
$((10#${BASH_REMATCH[4]})) -lt 13 ]] \
; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year="20$((10#${BASH_REMATCH[5]}))"
_parseDate_month=$((10#${BASH_REMATCH[4]}))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$((10#${BASH_REMATCH[3]}))
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_YEAR="20$((10#${BASH_REMATCH[5]}))"
PARSE_DATE_MONTH=$((10#${BASH_REMATCH[4]}))
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_DAY=$((10#${BASH_REMATCH[3]}))
debug "regex match: ${tan}DD-MM-YYYY${purple}"
elif [[ $((10#${BASH_REMATCH[3]})) -lt 32 &&
$((10#${BASH_REMATCH[4]})) -lt 13 ]] \
; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_year="20$((10#${BASH_REMATCH[5]}))"
_parseDate_month=$((10#${BASH_REMATCH[3]}))
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_day=$((10#${BASH_REMATCH[4]}))
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_YEAR="20$((10#${BASH_REMATCH[5]}))"
PARSE_DATE_MONTH=$((10#${BASH_REMATCH[3]}))
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_DAY=$((10#${BASH_REMATCH[4]}))
debug "regex match: ${tan}MM-DD-YYYY${purple}"
else
shopt -u nocasematch
@@ -202,50 +343,50 @@ _parseDate_() {
fi
# Month, YYYY
elif [[ ${date} =~ ((january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec),?[-\./_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[1]}"
_parseDate_day="1"
_parseDate_month="$(_monthToNumber_ "${BASH_REMATCH[2]}")"
_parseDate_monthName="$(_numberToMonth_ $_parseDate_month)"
_parseDate_year="$((10#${BASH_REMATCH[3]}))"
elif [[ ${_stringToTest} =~ ((january|jan|ja|february|feb|fe|march|mar|ma|april|apr|ap|may|june|jun|july|jul|ju|august|aug|september|sep|october|oct|november|nov|december|dec),?[-\./_ ]+(20[0-2][0-9]))([^0-9].*|$) ]]; then
PARSE_DATE_FOUND="${BASH_REMATCH[1]}"
PARSE_DATE_DAY="1"
PARSE_DATE_MONTH="$(_monthToNumber_ "${BASH_REMATCH[2]}")"
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ $PARSE_DATE_MONTH)"
PARSE_DATE_YEAR="$((10#${BASH_REMATCH[3]}))"
debug "regex match: ${tan}Month, YYYY${purple}"
# YYYYMMDDHHMM
elif [[ ${date} =~ (.*[^0-9]|^)((20[0-2][0-9])([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2}))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$((10#${BASH_REMATCH[5]}))"
_parseDate_month="$((10#${BASH_REMATCH[4]}))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="$((10#${BASH_REMATCH[3]}))"
_parseDate_hour="$((10#${BASH_REMATCH[6]}))"
_parseDate_minute="$((10#${BASH_REMATCH[7]}))"
elif [[ ${_stringToTest} =~ (.*[^0-9]|^)((20[0-2][0-9])([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2}))([^0-9].*|$) ]]; then
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_DAY="$((10#${BASH_REMATCH[5]}))"
PARSE_DATE_MONTH="$((10#${BASH_REMATCH[4]}))"
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_YEAR="$((10#${BASH_REMATCH[3]}))"
PARSE_DATE_HOUR="$((10#${BASH_REMATCH[6]}))"
PARSE_DATE_MINUTE="$((10#${BASH_REMATCH[7]}))"
debug "regex match: ${tan}YYYYMMDDHHMM${purple}"
# YYYYMMDDHH 1 2 3 4 5 6
elif [[ ${date} =~ (.*[^0-9]|^)((20[0-2][0-9])([0-9]{2})([0-9]{2})([0-9]{2}))([^0-9].*|$) ]]; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$((10#${BASH_REMATCH[5]}))"
_parseDate_month="$((10#${BASH_REMATCH[4]}))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="$((10#${BASH_REMATCH[3]}))"
_parseDate_hour="${BASH_REMATCH[6]}"
_parseDate_minute="00"
elif [[ ${_stringToTest} =~ (.*[^0-9]|^)((20[0-2][0-9])([0-9]{2})([0-9]{2})([0-9]{2}))([^0-9].*|$) ]]; then
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_DAY="$((10#${BASH_REMATCH[5]}))"
PARSE_DATE_MONTH="$((10#${BASH_REMATCH[4]}))"
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_YEAR="$((10#${BASH_REMATCH[3]}))"
PARSE_DATE_HOUR="${BASH_REMATCH[6]}"
PARSE_DATE_MINUTE="00"
debug "regex match: ${tan}YYYYMMDDHHMM${purple}"
# MMDDYYYY or YYYYMMDD or DDMMYYYY
# 1 2 3 4 5 6
elif [[ ${date} =~ (.*[^0-9]|^)(([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2}))([^0-9].*|$) ]]; then
elif [[ ${_stringToTest} =~ (.*[^0-9]|^)(([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2}))([^0-9].*|$) ]]; then
# MMDDYYYY
if [[ $((10#${BASH_REMATCH[5]})) -eq 20 &&
$((10#${BASH_REMATCH[3]})) -lt 13 &&
$((10#${BASH_REMATCH[4]})) -lt 32 ]] \
; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$((10#${BASH_REMATCH[4]}))"
_parseDate_month="$((10#${BASH_REMATCH[3]}))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="${BASH_REMATCH[5]}${BASH_REMATCH[6]}"
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_DAY="$((10#${BASH_REMATCH[4]}))"
PARSE_DATE_MONTH="$((10#${BASH_REMATCH[3]}))"
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_YEAR="${BASH_REMATCH[5]}${BASH_REMATCH[6]}"
debug "regex match: ${tan}MMDDYYYY${purple}"
# DDMMYYYY
elif [[ $((10#${BASH_REMATCH[5]})) -eq 20 &&
@@ -253,11 +394,11 @@ _parseDate_() {
$((10#${BASH_REMATCH[3]})) -lt 32 &&
$((10#${BASH_REMATCH[4]})) -lt 13 ]] \
; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$((10#${BASH_REMATCH[3]}))"
_parseDate_month="$((10#${BASH_REMATCH[4]}))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="${BASH_REMATCH[5]}${BASH_REMATCH[6]}"
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_DAY="$((10#${BASH_REMATCH[3]}))"
PARSE_DATE_MONTH="$((10#${BASH_REMATCH[4]}))"
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_YEAR="${BASH_REMATCH[5]}${BASH_REMATCH[6]}"
debug "regex match: ${tan}DDMMYYYY${purple}"
# YYYYMMDD
elif [[ $((10#${BASH_REMATCH[3]})) -eq 20 &&
@@ -265,11 +406,11 @@ _parseDate_() {
$((10#${BASH_REMATCH[6]})) -lt 32 &&
$((10#${BASH_REMATCH[5]})) -lt 13 ]] \
; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$((10#${BASH_REMATCH[6]}))"
_parseDate_month="$((10#${BASH_REMATCH[5]}))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="${BASH_REMATCH[3]}${BASH_REMATCH[4]}"
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_DAY="$((10#${BASH_REMATCH[6]}))"
PARSE_DATE_MONTH="$((10#${BASH_REMATCH[5]}))"
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_YEAR="${BASH_REMATCH[3]}${BASH_REMATCH[4]}"
debug "regex match: ${tan}YYYYMMDD${purple}"
# YYYYDDMM
elif [[ $((10#${BASH_REMATCH[3]})) -eq 20 &&
@@ -277,22 +418,22 @@ _parseDate_() {
$((10#${BASH_REMATCH[5]})) -lt 32 &&
$((10#${BASH_REMATCH[6]})) -lt 13 ]] \
; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$((10#${BASH_REMATCH[5]}))"
_parseDate_month="$((10#${BASH_REMATCH[6]}))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="${BASH_REMATCH[3]}${BASH_REMATCH[4]}"
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_DAY="$((10#${BASH_REMATCH[5]}))"
PARSE_DATE_MONTH="$((10#${BASH_REMATCH[6]}))"
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_YEAR="${BASH_REMATCH[3]}${BASH_REMATCH[4]}"
debug "regex match: ${tan}YYYYMMDD${purple}"
# Assume YYYMMDD
elif [[ $((10#${BASH_REMATCH[3]})) -eq 20 &&
$((10#${BASH_REMATCH[6]})) -lt 32 &&
$((10#${BASH_REMATCH[5]})) -lt 13 ]] \
; then
_parseDate_found="${BASH_REMATCH[2]}"
_parseDate_day="$((10#${BASH_REMATCH[6]}))"
_parseDate_month="$((10#${BASH_REMATCH[5]}))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="${BASH_REMATCH[3]}${BASH_REMATCH[4]}"
PARSE_DATE_FOUND="${BASH_REMATCH[2]}"
PARSE_DATE_DAY="$((10#${BASH_REMATCH[6]}))"
PARSE_DATE_MONTH="$((10#${BASH_REMATCH[5]}))"
PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
PARSE_DATE_YEAR="${BASH_REMATCH[3]}${BASH_REMATCH[4]}"
debug "regex match: ${tan}YYYYMMDD${purple}"
else
shopt -u nocasematch
@@ -300,26 +441,26 @@ _parseDate_() {
fi
# # MMDD or DDYY
# elif [[ "$date" =~ .*(([0-9]{2})([0-9]{2})).* ]]; then
# elif [[ "${_stringToTest}" =~ .*(([0-9]{2})([0-9]{2})).* ]]; then
# debug "regex match: ${tan}MMDD or DDMM${purple}"
# _parseDate_found="${BASH_REMATCH[1]}"
# PARSE_DATE_FOUND="${BASH_REMATCH[1]}"
# # Figure out if days are months or vice versa
# if [[ $(( 10#${BASH_REMATCH[2]} )) -gt 12 \
# && $(( 10#${BASH_REMATCH[2]} )) -lt 32 \
# && $(( 10#${BASH_REMATCH[3]} )) -lt 13 \
# ]]; then
# _parseDate_day="$(( 10#${BASH_REMATCH[2]} ))"
# _parseDate_month="$(( 10#${BASH_REMATCH[3]} ))"
# _parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
# _parseDate_year="$(date +%Y )"
# PARSE_DATE_DAY="$(( 10#${BASH_REMATCH[2]} ))"
# PARSE_DATE_MONTH="$(( 10#${BASH_REMATCH[3]} ))"
# PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
# PARSE_DATE_YEAR="$(date +%Y )"
# elif [[ $(( 10#${BASH_REMATCH[2]} )) -lt 13 \
# && $(( 10#${BASH_REMATCH[3]} )) -lt 32 \
# ]]; then
# _parseDate_day="$(( 10#${BASH_REMATCH[3]} ))"
# _parseDate_month="$(( 10#${BASH_REMATCH[2]} ))"
# _parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
# _parseDate_year="$(date +%Y )"
# PARSE_DATE_DAY="$(( 10#${BASH_REMATCH[3]} ))"
# PARSE_DATE_MONTH="$(( 10#${BASH_REMATCH[2]} ))"
# PARSE_DATE_MONTH_NAME="$(_numberToMonth_ "${PARSE_DATE_MONTH}")"
# PARSE_DATE_YEAR="$(date +%Y )"
# else
# shopt -u nocasematch
# return 1
@@ -327,69 +468,102 @@ _parseDate_() {
else
shopt -u nocasematch
return 1
fi
[[ -z ${_parseDate_year:-} ]] && {
[[ -z ${PARSE_DATE_YEAR:-} ]] && {
shopt -u nocasematch
return 1
}
((_parseDate_month >= 1 && _parseDate_month <= 12)) || {
((PARSE_DATE_MONTH >= 1 && PARSE_DATE_MONTH <= 12)) || {
shopt -u nocasematch
return 1
}
((_parseDate_day >= 1 && _parseDate_day <= 31)) || {
((PARSE_DATE_DAY >= 1 && PARSE_DATE_DAY <= 31)) || {
shopt -u nocasematch
return 1
}
debug "${tan}\$_parseDate_found: ${_parseDate_found}${purple}"
debug "${tan}\$_parseDate_year: ${_parseDate_year}${purple}"
debug "${tan}\$_parseDate_month: ${_parseDate_month}${purple}"
debug "${tan}\$_parseDate_monthName: ${_parseDate_monthName}${purple}"
debug "${tan}\$_parseDate_day: ${_parseDate_day}${purple}"
[[ -z ${_parseDate_hour:-} ]] || debug "${tan}\$_parseDate_hour: ${_parseDate_hour}${purple}"
[[ -z ${_parseDate_minute:-} ]] || debug "${tan}\$_parseDate_minute: ${_parseDate_minute}${purple}"
debug "${tan}\$PARSE_DATE_FOUND: ${PARSE_DATE_FOUND}${purple}"
debug "${tan}\$PARSE_DATE_YEAR: ${PARSE_DATE_YEAR}${purple}"
debug "${tan}\$PARSE_DATE_MONTH: ${PARSE_DATE_MONTH}${purple}"
debug "${tan}\$PARSE_DATE_MONTH_NAME: ${PARSE_DATE_MONTH_NAME}${purple}"
debug "${tan}\$PARSE_DATE_DAY: ${PARSE_DATE_DAY}${purple}"
[[ -z ${PARSE_DATE_HOUR:-} ]] || debug "${tan}\$PARSE_DATE_HOUR: ${PARSE_DATE_HOUR}${purple}"
[[ -z ${PARSE_DATE_MINUTE:-} ]] || debug "${tan}\$PARSE_DATE_MINUTE: ${PARSE_DATE_MINUTE}${purple}"
shopt -u nocasematch
# Output results for BATS tests
if [ "${automated_test_in_progress:-}" ]; then
echo "_parseDate_found: ${_parseDate_found}"
echo "_parseDate_year: ${_parseDate_year}"
echo "_parseDate_month: ${_parseDate_month}"
echo "_parseDate_monthName: ${_parseDate_monthName}"
echo "_parseDate_day: ${_parseDate_day}"
echo "_parseDate_hour: ${_parseDate_hour}"
echo "_parseDate_minute: ${_parseDate_minute}"
echo "PARSE_DATE_FOUND: ${PARSE_DATE_FOUND}"
echo "PARSE_DATE_YEAR: ${PARSE_DATE_YEAR}"
echo "PARSE_DATE_MONTH: ${PARSE_DATE_MONTH}"
echo "PARSE_DATE_MONTH_NAME: ${PARSE_DATE_MONTH_NAME}"
echo "PARSE_DATE_DAY: ${PARSE_DATE_DAY}"
echo "PARSE_DATE_HOUR: ${PARSE_DATE_HOUR}"
echo "PARSE_DATE_MINUTE: ${PARSE_DATE_MINUTE}"
fi
}
_formatDate_() {
# DESC: Reformats dates into user specified formats
# ARGS: $1 (Required) - Date to be formatted
# $2 (Optional) - Format in any format accepted by bash's date command. Examples listed below.
# %F - YYYY-MM-DD
# %D - MM/DD/YY
# %a - Name of weekday in short (like Sun, Mon, Tue, Wed, Thu, Fri, Sat)
# %A - Name of weekday in full (like Sunday, Monday, Tuesday)
# '+%m %d, %Y' - 12 27, 2019
# OUTS: Echo result to STDOUT
# USAGE: _formatDate_ "Jan 10, 2019" "%D"
# NOTE: Defaults to YYYY-MM-DD or $(date +%F)
_readableUnixTimestamp_() {
# DESC:
# Format unix timestamp to human readable format. If format string is not specified then
# default to "yyyy-mm-dd hh:mm:ss"
# ARGS:
# $1 (Required) - Unix timestamp to be formatted
# $2 (Optional) - Format string
# OUTS:
# 0 If successful
# 1 If failed to convert
# stdout: Human readable format of unix timestamp
# USAGE:
# _readableUnixTimestamp_ "1591554426"
# _readableUnixTimestamp_ "1591554426" "%Y-%m-%d"
# CREDIT:
# https://github.com/labbots/bash-utility/blob/master/src/date.sh
[[ $# -eq 0 ]] && {
error 'Missing required argument to _formatDate_()'
return 1
}
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _timestamp="${1}"
local _format="${2:-"%F %T"}"
local _out="$(date -d "@${_timestamp}" +"${_format}")" || return 1
printf "%s\n" "${_out}"
}
local d="${1}"
local format="${2:-%F}"
format="${format//+/}"
_toSeconds_() {
# DESC:
# Converts HH:MM:SS to seconds
# ARGS:
# $1 (Required) - Time in HH:MM:SS
# OUTS:
# stdout: Print seconds
# USAGE:
# _toSeconds_ "01:00:00"
# NOTE:
# Acceptable Input Formats
# 24 12 09
# 12,12,09
# 12;12;09
# 12:12:09
# 12-12-09
# 12H12M09S
# 12h12m09s
if command -v gdate >/dev/null 2>&1; then
gdate -d "${d}" "+${format}"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _saveIFS
local _h
local _m
local _s
if [[ $1 =~ [0-9]{1,2}(:|,|-|_|,| |[hHmMsS])[0-9]{1,2}(:|,|-|_|,| |[hHmMsS])[0-9]{1,2} ]]; then
_saveIFS="${IFS}"
IFS=":,;-_, HhMmSs" read -r h m s <<<"$1"
IFS="${_saveIFS}"
else
date -d "${d}" "+${format}"
_h="$1"
_m="$2"
_s="$3"
fi
printf "%s\n" "$((10#$_h * 3600 + 10#$_m * 60 + 10#$_s))"
}

58
utilities/debug.bash Normal file
View File

@@ -0,0 +1,58 @@
# Functions to aid in debugging bash scripts
_pauseScript_() {
# DESC:
# Pause a script at any point and continue after user input
# ARGS:
# $1 (Optional) - String for customized message
local _pauseMessage
_pauseMessage="${1:-Paused. Ready to continue?}"
if _seekConfirmation_ "${_pauseMessage}"; then
info "Continuing..."
else
notice "Exiting Script"
_safeExit_
fi
}
_printAnsi_() {
# DESC:
# Helps debug ansi escape sequence in text by displaying the escape codes
# ARGS:
# $1 (Required) String input with ansi escape sequence.
# OUTS:
# stdout: Ansi escape sequence printed in output as is.
# USAGE:
# _printAnsi_ "$(tput bold)$(tput setaf 9)Some Text"
# CREDIT:
# https://github.com/labbots/bash-utility/blob/master/src/debug.sh
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
#echo $(tr -dc '[:print:]'<<<$1)
printf "%s\n" "${1//$'\e'/\\e}"
}
_printArray_() {
# DESC:
# Prints the content of array as key value pairs for easier debugging
# ARGS:
# $1 (Required) - String variable name of the array
# OUTS:
# stdout: Formatted key value of array.one
# USAGE:
# testArray=("1" "2" "3" "4")
# _printArray_ "testArray"
# CREDIT:
# https://github.com/labbots/bash-utility/blob/master/src/debug.sh
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
declare -n _arr="${1}"
for _k in "${!_arr[@]}"; do
printf "%s = %s\n" "$_k" "${_arr[$_k]}"
done
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,18 @@
# Functions for use on computers running MacOS
_haveScriptableFinder_() {
# DESC: Determine whether we can script the Finder or not
# ARGS: None
# OUTS: true/false
# DESC:
# Determine whether we can script the Finder or not
# ARGS:
# None
# OUTS:
# 0 if we can script the Finder
# 1 if not
local finder_pid
finder_pid="$(pgrep -f /System/Library/CoreServices/Finder.app | head -n 1)"
local _finder_pid
_finder_pid="$(pgrep -f /System/Library/CoreServices/Finder.app | head -n 1)"
if [[ (${finder_pid} -gt 1) && (${STY-} == "") ]]; then
if [[ (${_finder_pid} -gt 1) && (${STY-} == "") ]]; then
return 0
else
return 1
@@ -16,24 +20,56 @@ _haveScriptableFinder_() {
}
_guiInput_() {
# DESC: Ask for user input using a Mac dialog box
# ARGS: $1 (Optional) - Text in dialogue box (Default: Password)
# OUTS: None
# NOTE: https://github.com/herrbischoff/awesome-osx-command-line/blob/master/functions.md
# DESC:
# Ask for user input using a Mac dialog box
# ARGS:
# $1 (Optional) - Text in dialogue box (Default: Password)
# OUTS:
# MacOS dialog box output
# 1 if no output
# CREDIT
# https://github.com/herrbischoff/awesome-osx-command-line/blob/master/functions.md
if _haveScriptableFinder_; then
guiPrompt="${1:-Password:}"
guiInput=$(
osascript &>/dev/null <<EOF
local _guiPrompt="${1:-Password:}"
local _guiInput=$(
osascript &>/dev/null <<GUI_INPUT_MESSAGE
tell application "System Events"
activate
text returned of (display dialog "${guiPrompt}" default answer "" with hidden answer)
text returned of (display dialog "${_guiPrompt}" default answer "" with hidden answer)
end tell
EOF
GUI_INPUT_MESSAGE
)
echo -n "${guiInput}"
echo -n "${_guiInput}"
else
error "No GUI input without macOS"
return 1
fi
}
_useGNUutils_() {
# DESC:
# Add GNU utilities to PATH to allow consistent use of sed/grep/tar/etc. on MacOS
# ARGS:
# None
# OUTS:
# 0 if successful
# 1 if unsuccessful
# PATH: Adds GNU utilities to the path
# USAGE:
# # if ! _useGNUUtils_; then exit 1; fi
# NOTES:
# GNU utilities can be added to MacOS using Homebrew
[ ! "$(declare -f "_setPATH_")" ] && fatal "${FUNCNAME[0]} needs function _setPATH_"
if _setPATH_ \
"/usr/local/opt/gnu-tar/libexec/gnubin" \
"/usr/local/opt/coreutils/libexec/gnubin" \
"/usr/local/opt/gnu-sed/libexec/gnubin" \
"/usr/local/opt/grep/libexec/gnubin"; then
return 0
else
return 1
fi
}

482
utilities/misc.bash Normal file
View File

@@ -0,0 +1,482 @@
# Functions which provide base functionality for other scripts
_checkTerminalSize_() {
# DESC:
# Checks the size of the terminal window. Updates LINES/COLUMNS if necessary
# ARGS:
# NONE
# OUTS:
# NONE
# USAGE:
# _updateTerminalSize_
# CREDIT:
# https://github.com/labbots/bash-utility
shopt -s checkwinsize && (: && :)
trap 'shopt -s checkwinsize; (:;:)' SIGWINCH
}
_detectOS_() {
# DESC:
# Identify the OS the script is run on
# ARGS:
# None
# OUTS:
# 0 - Success
# 1 - Failed to detect OS
# stdout: One of 'mac', 'linux', 'windows'
# USAGE:
# _detectOS_
# CREDIT:
# https://github.com/labbots/bash-utility
local _uname
local _os
if _uname=$(command -v uname); then
case $("${_uname}" | tr '[:upper:]' '[:lower:]') in
linux*)
_os="linux"
;;
darwin*)
_os="mac"
;;
msys* | cygwin* | mingw* | nt | win*)
# or possible 'bash on windows'
_os="windows"
;;
*)
return 1
;;
esac
else
return 1
fi
printf "%s" "${_os}"
}
_detectLinuxDistro_() {
# DESC:
# Detects the Linux distribution of the host the script is run on
# ARGS:
# None
# OUTS:
# 0 - If Linux distro is successfully detected
# 1 - If unable to detect OS distro or not on Linux
# stdout: Prints name of Linux distro in lower case (ex: 'raspbian' or 'debian')
# USAGE:
# _detectLinuxDistro_
# CREDIT:
# https://github.com/labbots/bash-utility
local _distro
if [[ -f /etc/os-release ]]; then
# shellcheck disable=SC1091
. "/etc/os-release"
_distro="${NAME}"
elif type lsb_release >/dev/null 2>&1; then
# linuxbase.org
_distro=$(lsb_release -si)
elif [[ -f /etc/lsb-release ]]; then
# For some versions of Debian/Ubuntu without lsb_release command
# shellcheck disable=SC1091
. /etc/lsb-release
_distro="${DISTRIB_ID}"
elif [[ -f /etc/debian_version ]]; then
# Older Debian/Ubuntu/etc.
_distro="debian"
elif [[ -f /etc/SuSe-release ]]; then
# Older SuSE/etc.
_distro="suse"
elif [[ -f /etc/redhat-release ]]; then
# Older Red Hat, CentOS, etc.
_distro="redhat"
else
return 1
fi
printf "%s" "${_distro}" | tr '[:upper:]' '[:lower:]'
}
_detectMacOSVersion_() {
# DESC:
# Detects the host's version of MacOS
# ARGS:
# None
# OUTS:
# 0 - Success
# 1 - Can not find macOS version number or not on a mac
# stdout: Prints the version number of macOS (ex: 11.6.1)
# USAGE:
# _detectMacOSVersion_
# CREDIT:
# https://github.com/labbots/bash-utility
[ ! "$(declare -f "_detectOS_")" ] && fatal "${FUNCNAME[0]} needs function _detectOS_"
if [[ "$(_detectOS_)" == "mac" ]]; then
local _mac_version
_mac_version="$(sw_vers -productVersion)"
printf "%s" "${_mac_version}"
else
return 1
fi
}
_detectLinuxDistro_() {
# DESC:
# Detects the Linux distribution of the host the script is run on
# ARGS:
# None
# OUTS:
# 0 - If Linux distro is successfully detected
# 1 - If unable to detect OS distro or not on Linux
# stdout: Prints name of Linux distro in lower case (ex: 'raspbian' or 'debian')
# USAGE:
# _detectLinuxDistro_
# CREDIT:
# https://github.com/labbots/bash-utility
local _distro
if [[ -f /etc/os-release ]]; then
# shellcheck disable=SC1091
. "/etc/os-release"
_distro="${NAME}"
elif type lsb_release >/dev/null 2>&1; then
# linuxbase.org
_distro=$(lsb_release -si)
elif [[ -f /etc/lsb-release ]]; then
# For some versions of Debian/Ubuntu without lsb_release command
# shellcheck disable=SC1091
. /etc/lsb-release
_distro="${DISTRIB_ID}"
elif [[ -f /etc/debian_version ]]; then
# Older Debian/Ubuntu/etc.
_distro="debian"
elif [[ -f /etc/SuSe-release ]]; then
# Older SuSE/etc.
_distro="suse"
elif [[ -f /etc/redhat-release ]]; then
# Older Red Hat, CentOS, etc.
_distro="redhat"
else
return 1
fi
printf "%s" "${_distro}" | tr '[:upper:]' '[:lower:]'
}
_execute_() {
# DESC:
# Executes commands while respecting global DRYRUN, VERBOSE, LOGGING, and QUIET flags
# ARGS:
# $1 (Required) - The command to be executed. Quotation marks MUST be escaped.
# $2 (Optional) - String to display after command is executed
# OPTS:
# -v Always print output from the execute function to STDOUT
# -n Use NOTICE level alerting (default is INFO)
# -p Pass a failed command with 'return 0'. This effectively bypasses set -e.
# -e Bypass _alert_ functions and use 'echo RESULT'
# -s Use '_alert_ success' for successful output. (default is 'info')
# -q Do not print output (QUIET mode)
# OUTS:
# stdout: Configurable output
# USE :
# _execute_ "cp -R \"~/dir/somefile.txt\" \"someNewFile.txt\"" "Optional message"
# _execute_ -sv "mkdir \"some/dir\""
# NOTE:
# If $DRYRUN=true, no commands are executed and the command that would have been executed
# is printed to STDOUT using dryrun level alerting
# If $VERBOSE=true, the command's native output is printed to stdout. This can be forced
# with '_execute_ -v'
local _localVerbose=false
local _passFailures=false
local _echoResult=false
local _echoSuccessResult=false
local _quietMode=false
local _echoNoticeResult=false
local opt
local OPTIND=1
while getopts ":vVpPeEsSqQnN" opt; do
case $opt in
v | V) _localVerbose=true ;;
p | P) _passFailures=true ;;
e | E) _echoResult=true ;;
s | S) _echoSuccessResult=true ;;
q | Q) _quietMode=true ;;
n | N) _echoNoticeResult=true ;;
*)
{
error "Unrecognized option '$1' passed to _execute_. Exiting."
_safeExit_
}
;;
esac
done
shift $((OPTIND - 1))
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _command="${1}"
local _executeMessage="${2:-$1}"
local _saveVerbose=${VERBOSE}
if "${_localVerbose}"; then
VERBOSE=true
fi
if "${DRYRUN}"; then
if "${_quietMode}"; then
VERBOSE=${_saveVerbose}
return 0
fi
if [ -n "${2:-}" ]; then
dryrun "${1} (${2})" "$(caller)"
else
dryrun "${1}" "$(caller)"
fi
elif ${VERBOSE}; then
if eval "${_command}"; then
if "${_quietMode}"; then
VERBOSE=${_saveVerbose}
elif "${_echoResult}"; then
printf "%s\n" "${_executeMessage}"
elif "${_echoSuccessResult}"; then
success "${_executeMessage}"
elif "${_echoNoticeResult}"; then
notice "${_executeMessage}"
else
info "${_executeMessage}"
fi
else
if "${_quietMode}"; then
VERBOSE=${_saveVerbose}
elif "${_echoResult}"; then
printf "%s\n" "warning: ${_executeMessage}"
else
warning "${_executeMessage}"
fi
VERBOSE=${_saveVerbose}
"${_passFailures}" && return 0 || return 1
fi
else
if eval "${_command}" >/dev/null 2>&1; then
if "${_quietMode}"; then
VERBOSE=${_saveVerbose}
elif "${_echoResult}"; then
printf "%s\n" "${_executeMessage}"
elif "${_echoSuccessResult}"; then
success "${_executeMessage}"
elif "${_echoNoticeResult}"; then
notice "${_executeMessage}"
else
info "${_executeMessage}"
fi
else
if "${_quietMode}"; then
VERBOSE=$_saveVerbose
elif "${_echoResult}"; then
printf "%s\n" "error: ${_executeMessage}"
else
warning "${_executeMessage}"
fi
VERBOSE=${_saveVerbose}
"${_passFailures}" && return 0 || return 1
fi
fi
VERBOSE=${_saveVerbose}
return 0
}
_findBaseDir_() {
# DESC:
# Locates the real directory of the script being run. Similar to GNU readlink -n
# ARGS:
# None
# OUTS:
# stdout: prints result
# USAGE:
# baseDir="$(_findBaseDir_)"
# cp "$(_findBaseDir_ "somefile.txt")" "other_file.txt"
local _source
local _dir
# Is file sourced?
[[ $_ != "$0" ]] \
&& _source="${BASH_SOURCE[1]}" \
|| _source="${BASH_SOURCE[0]}"
while [ -h "${_source}" ]; do # Resolve $SOURCE until the file is no longer a symlink
_dir="$(cd -P "$(dirname "${_source}")" && pwd)"
_source="$(readlink "${_source}")"
[[ ${_source} != /* ]] && _source="${_dir}/${_source}" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
printf "%s/n" "$(cd -P "$(dirname "${_source}")" && pwd)"
}
_generateUUID_() {
# DESC:
# Generates a UUID
# ARGS:
# None
# OUTS:
# 0 - Success
# 1 - Failure
# stdout: UUID
# USAGE:
# _generateUUID_
# CREDIT:
# https://github.com/labbots/bash-utility
local _c
local n
local _b
_c="89ab"
for ((n = 0; n < 16; ++n)); do
_b="$((RANDOM % 256))"
case "$n" in
6) printf '4%x' "$((_b % 16))" ;;
8) printf '%c%x' "${_c:$RANDOM%${#_c}:1}" "$((_b % 16))" ;;
3 | 5 | 7 | 9)
printf '%02x-' "${_b}"
;;
*)
printf '%02x' "${_b}"
;;
esac
done
printf '\n'
}
_makeProgressBar_() {
# DESC:
# Prints a progress bar within a for/while loop
# ARGS:
# $1 (Required) - The total number of items counted
# $2 (Optional) - The optional title of the progress bar
# OUTS: stdout: progress bar
# USAGE:
# for number in $(seq 0 100); do
# sleep 1
# _makeProgressBar_ "100" "Counting numbers"
# done
[[ $# == 0 ]] && return # Do nothing if no arguments are passed
(${QUIET}) && return
(${VERBOSE}) && return
[ ! -t 1 ] && return # Do nothing if the output is not a terminal
[ ${1} == 1 ] && return # Do nothing with a single element
local n="${1}"
local _width=30
local _barCharacter="#"
local _percentage
local _num
local _bar
local _progressBarLine
local _barTitle="${2:-Running Process}"
local n
((n = n - 1))
# Reset the count
[ -z "${progressBarProgress:-}" ] && progressBarProgress=0
tput civis # Hide the cursor
trap 'tput cnorm; exit 1' SIGINT
if [ ! "${progressBarProgress}" -eq $n ]; then
#echo "progressBarProgress: $progressBarProgress"
# Compute the percentage.
_percentage=$((progressBarProgress * 100 / $1))
# Compute the number of blocks to represent the percentage.
_num=$((progressBarProgress * _width / $1))
# Create the progress bar string.
_bar=""
if [ ${_num} -gt 0 ]; then
_bar=$(printf "%0.s${_barCharacter}" $(seq 1 ${_num}))
fi
# Print the progress bar.
_progressBarLine=$(printf "%s [%-${_width}s] (%d%%)" " ${_barTitle}" "${_bar}" "${_percentage}")
printf "%s\r" "${_progressBarLine}"
# echo -ne "${_progressBarLine}\r"
progressBarProgress=$((progressBarProgress + 1))
else
# Clear the progress bar when complete
# echo -ne "\033[0K\r"
tput el # Clear the line
unset progressBarProgress
fi
tput cnorm
}
_runAsRoot_() {
# DESC:
# Run the requested command as root (via sudo if requested)
# ARGS:
# $1 (optional): Set to zero to not attempt execution via sudo
# $@ (required): Passed through for execution as root user
# OUTS:
# Runs the requested command as root
# CREDIT:
# https://github.com/ralish/bash-script-template
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _skip_sudo=false
if [[ ${1} =~ ^0$ ]]; then
_skip_sudo=true
shift
fi
if [[ ${EUID} -eq 0 ]]; then
"$@"
elif [[ -z ${_skip_sudo} ]]; then
sudo -H -- "$@"
else
fatal "Unable to run requested command as root: $*"
fi
}
_seekConfirmation_() {
# DESC:
# Seek user input for yes/no question
# ARGS:
# $1 (Required) - Question being asked
# OUTS:
# 0 if answer is "yes"
# 1 if answer is "no"
# USAGE:
# _seekConfirmation_ "Do something?" && echo "okay" || echo "not okay"
# OR
# if _seekConfirmation_ "Answer this question"; then
# something
# fi
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _yesNo
input "${1}"
if "${FORCE}"; then
debug "Forcing confirmation with '--force' flag set"
echo -e ""
return 0
else
while true; do
read -r -p " (y/n) " _yesNo
case ${_yesNo} in
[Yy]*) return 0 ;;
[Nn]*) return 1 ;;
*) input "Please answer yes or no." ;;
esac
done
fi
}

View File

@@ -1,73 +0,0 @@
_fromSeconds_() {
# DESC: Convert seconds to HH:MM:SS
# ARGS: $1 (Required) - Time in seconds
# OUTS: Print HH:MM:SS to STDOUT
# USAGE: _convertSecs_ "SECONDS"
# To compute the time it takes a script to run:
# STARTTIME=$(date +"%s")
# ENDTIME=$(date +"%s")
# TOTALTIME=$(($ENDTIME-$STARTTIME)) # human readable time
# _convertSecs_ "$TOTALTIME"
((h = ${1} / 3600))
((m = (${1} % 3600) / 60))
((s = ${1} % 60))
printf "%02d:%02d:%02d\n" $h $m $s
}
_toSeconds_() {
# DESC: Converts HH:MM:SS to seconds
# ARGS: $1 (Required) - Time in HH:MM:SS
# OUTS: Print seconds to STDOUT
# USAGE: _toSeconds_ "01:00:00"
# NOTE: Acceptable Input Formats
# 24 12 09
# 12,12,09
# 12;12;09
# 12:12:09
# 12-12-09
# 12H12M09S
# 12h12m09s
local saveIFS
local h
local m
local s
if [[ $1 =~ [0-9]{1,2}(:|,|-|_|,| |[hHmMsS])[0-9]{1,2}(:|,|-|_|,| |[hHmMsS])[0-9]{1,2} ]]; then
saveIFS="$IFS"
IFS=":,;-_, HhMmSs" read -r h m s <<<"$1"
IFS="$saveIFS"
else
h="$1"
m="$2"
s="$3"
fi
echo $((10#$h * 3600 + 10#$m * 60 + 10#$s))
}
_countdown_() {
# DESC: Sleep for a specified amount of time
# ARGS: $1 (Optional) - Total seconds to sleep for(Default is 10)
# $2 (Optional) - Increment to count down
# $3 (Optional) - Message to print at each increment (default is ...)
# OUTS: None
# USAGE: _countdown_ 10 1 "Waiting for cache to invalidate"
local i ii t
local n=${1:-10}
local stime=${2:-1}
local message="${3:-...}"
((t = n + 1))
for ((i = 1; i <= n; i++)); do
((ii = t - i))
if declare -f "info" &>/dev/null 2>&1; then
info "${message} ${ii}"
else
echo "${message} ${ii}"
fi
sleep $stime
done
}

View File

@@ -1,148 +1,136 @@
_haveInternet_() {
# DESC: Tests to see if there is an active Internet connection
# ARGS: None
# OUTS: None
# USAGE: _haveInternet_ && [SOMETHING]
# NOTE: https://stackoverflow.com/questions/929368/
if command -v fping &>/dev/null; then
fping 1.1.1.1 &>/dev/null \
&& return 0 \
|| return 1
elif ping -t 2 -c 1 1 1.1.1.1 &>/dev/null; then
return 0
elif command -v route &>/dev/null; then
local GATEWAY="$(route -n get default | grep gateway)"
ping -t 2 -c 1 "$(echo "${GATEWAY}" | cut -d ':' -f 2)" &>/dev/null \
&& return 0 \
|| return 1
elif command -v ip &>/dev/null; then
ping -t 2 -c 1 "$(ip r | grep default | cut -d ' ' -f 3)" &>/dev/null \
&& return 0 \
|| return 1
else
return 1
fi
}
# Functions for working with network services
_httpStatus_() {
# DESC: Report the HTTP status of a specified URL
# ARGS: $1 (Required) - URL (will work fine without https:// prefix)
# DESC:
# Report the HTTP status of a specified URL
# ARGS:
# $1 (Required) - URL (will work fine without https:// prefix)
# $2 (Optional) - Seconds to wait until timeout (Default is 3)
# $3 (Optional) - either '--code' or '--status' (default)
# $4 (optional) - CURL opts separated by spaces (Use -L to follow redirects)
# OUTS: Prints output to STDOUT
# USAGE: _httpStatus_ URL [timeout] [--code or --status] [curl opts]
# NOTE: https://gist.github.com/rsvp/1171304
#
# Example: $ _httpStatus_ bit.ly
# OUTS:
# stdout: Prints the HTTP status code or status message
# USAGE:
# _httpStatus_ URL [timeout] [--code or --status] [curl opts]
# NOTE:
# https://gist.github.com/rsvp/1171304
# EXAMPLES
# $ _httpStatus_ bit.ly
# 301 Redirection: Moved Permanently
#
# Example: $ _httpStatus_ www.google.com 100 --code
local code
local status
# $ _httpStatus_ www.google.com 100 --code
local saveIFS=${IFS}
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _saveIFS=${IFS}
IFS=$' \n\t'
local url=${1:?_httpStatus_ needs an url}
local timeout=${2:-'3'}
local flag=${3:-'--status'}
local arg4=${4:-''}
local arg5=${5:-''}
local arg6=${6:-''}
local arg7=${7:-''}
local curlops="${arg4} ${arg5} ${arg6} ${arg7}"
local _url=${1}
local _timeout=${2:-3}
local _flag=${3:---status}
local _arg4=${4:-}
local _arg5=${5:-}
local _arg6=${6:-}
local _arg7=${7:-}
local _curlops="${_arg4} ${_arg5} ${_arg6} ${_arg7}"
local _code
local _status
# __________ get the CODE which is numeric:
code=$(echo "$(curl --write-out %{http_code} --silent --connect-timeout "${timeout}" \
--no-keepalive "${curlops}" --output /dev/null "${url}")")
_code=$(curl --write-out %{http_code} --silent --connect-timeout "${_timeout}" \
--no-keepalive ${_curlops} --output /dev/null ${_url})
# __________ get the STATUS (from code) which is human interpretable:
case $code in
000) status="Not responding within ${timeout} seconds" ;;
100) status="Informational: Continue" ;;
101) status="Informational: Switching Protocols" ;;
200) status="Successful: OK within ${timeout} seconds" ;;
201) status="Successful: Created" ;;
202) status="Successful: Accepted" ;;
203) status="Successful: Non-Authoritative Information" ;;
204) status="Successful: No Content" ;;
205) status="Successful: Reset Content" ;;
206) status="Successful: Partial Content" ;;
300) status="Redirection: Multiple Choices" ;;
301) status="Redirection: Moved Permanently" ;;
302) status="Redirection: Found residing temporarily under different URI" ;;
303) status="Redirection: See Other" ;;
304) status="Redirection: Not Modified" ;;
305) status="Redirection: Use Proxy" ;;
306) status="Redirection: status not defined" ;;
307) status="Redirection: Temporary Redirect" ;;
400) status="Client Error: Bad Request" ;;
401) status="Client Error: Unauthorized" ;;
402) status="Client Error: Payment Required" ;;
403) status="Client Error: Forbidden" ;;
404) status="Client Error: Not Found" ;;
405) status="Client Error: Method Not Allowed" ;;
406) status="Client Error: Not Acceptable" ;;
407) status="Client Error: Proxy Authentication Required" ;;
408) status="Client Error: Request Timeout within ${timeout} seconds" ;;
409) status="Client Error: Conflict" ;;
410) status="Client Error: Gone" ;;
411) status="Client Error: Length Required" ;;
412) status="Client Error: Precondition Failed" ;;
413) status="Client Error: Request Entity Too Large" ;;
414) status="Client Error: Request-URI Too Long" ;;
415) status="Client Error: Unsupported Media Type" ;;
416) status="Client Error: Requested Range Not Satisfiable" ;;
417) status="Client Error: Expectation Failed" ;;
500) status="Server Error: Internal Server Error" ;;
501) status="Server Error: Not Implemented" ;;
502) status="Server Error: Bad Gateway" ;;
503) status="Server Error: Service Unavailable" ;;
504) status="Server Error: Gateway Timeout within ${timeout} seconds" ;;
505) status="Server Error: HTTP Version Not Supported" ;;
*) die "httpstatus: status not defined." ;;
case $_code in
000) _status="Not responding within ${_timeout} seconds" ;;
100) _status="Informational: Continue" ;;
101) _status="Informational: Switching Protocols" ;;
200) _status="Successful: OK within ${_timeout} seconds" ;;
201) _status="Successful: Created" ;;
202) _status="Successful: Accepted" ;;
203) _status="Successful: Non-Authoritative Information" ;;
204) _status="Successful: No Content" ;;
205) _status="Successful: Reset Content" ;;
206) _status="Successful: Partial Content" ;;
300) _status="Redirection: Multiple Choices" ;;
301) _status="Redirection: Moved Permanently" ;;
302) _status="Redirection: Found residing temporarily under different URI" ;;
303) _status="Redirection: See Other" ;;
304) _status="Redirection: Not Modified" ;;
305) _status="Redirection: Use Proxy" ;;
306) _status="Redirection: status not defined" ;;
307) _status="Redirection: Temporary Redirect" ;;
400) _status="Client Error: Bad Request" ;;
401) _status="Client Error: Unauthorized" ;;
402) _status="Client Error: Payment Required" ;;
403) _status="Client Error: Forbidden" ;;
404) _status="Client Error: Not Found" ;;
405) _status="Client Error: Method Not Allowed" ;;
406) _status="Client Error: Not Acceptable" ;;
407) _status="Client Error: Proxy Authentication Required" ;;
408) _status="Client Error: Request Timeout within ${_timeout} seconds" ;;
409) _status="Client Error: Conflict" ;;
410) _status="Client Error: Gone" ;;
411) _status="Client Error: Length Required" ;;
412) _status="Client Error: Precondition Failed" ;;
413) _status="Client Error: Request Entity Too Large" ;;
414) _status="Client Error: Request-URI Too Long" ;;
415) _status="Client Error: Unsupported Media Type" ;;
416) _status="Client Error: Requested Range Not Satisfiable" ;;
417) _status="Client Error: Expectation Failed" ;;
500) _status="Server Error: Internal Server Error" ;;
501) _status="Server Error: Not Implemented" ;;
502) _status="Server Error: Bad Gateway" ;;
503) _status="Server Error: Service Unavailable" ;;
504) _status="Server Error: Gateway Timeout within ${_timeout} seconds" ;;
505) _status="Server Error: HTTP Version Not Supported" ;;
*) fatal "httpstatus: status not defined." ;;
esac
case ${flag} in
--status) echo "${code} ${status}" ;;
-s) echo "${code} ${status}" ;;
--code) echo "${code}" ;;
-c) echo "${code}" ;;
*) echo " httpstatus: bad flag" && _safeExit_ ;;
case ${_flag} in
--status) echo "${_code} ${_status}" ;;
-s) echo "${_code} ${_status}" ;;
--code) echo "${_code}" ;;
-c) echo "${_code}" ;;
*) echo " _httpStatus_: bad flag" && _safeExit_ ;;
esac
IFS="${saveIFS}"
IFS="${_saveIFS}"
}
_pushover_() {
# DESC: Sends a notification via Pushover
# ARGS: $1 (Required) - Title of notification
# DESC:
# Sends a notification via Pushover
# ARGS:
# $1 (Required) - Title of notification
# $2 (Required) - Body of notification
# OUTS: None
# $3 (Required) - User Token
# $4 (Required) - API Key
# $5 (Optional) - Device
# OUTS:
# 0 if success
# 1 if failure
# USAGE: _pushover_ "Title Goes Here" "Message Goes Here"
# NOTE: The variables for the two API Keys must have valid values
# Credit: http://ryonsherman.blogspot.com/2012/10/shell-script-to-send-pushover.html
local PUSHOVERURL
local API_KEY
local USER_KEY
local DEVICE
local TITLE
local MESSAGE
[[ $# -lt 4 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
PUSHOVERURL="https://api.pushover.net/1/messages.json"
API_KEY="${PUSHOVER_API_KEY}"
USER_KEY="${PUSHOVER_USER_KEY}"
DEVICE=""
TITLE="${1}"
MESSAGE="${2}"
curl \
-F "token=${API_KEY}" \
-F "user=${USER_KEY}" \
-F "device=${DEVICE}" \
-F "title=${TITLE}" \
-F "message=${MESSAGE}" \
"${PUSHOVERURL}" >/dev/null 2>&1
local _pushoverURL="https://api.pushover.net/1/messages.json"
local _message="${2}"
local _apiKey="${3}"
local _userKey="${4}"
local _device="${5:-}"
if curl \
-F "token=${_apiKey}" \
-F "user=${_userKey}" \
-F "device=${_device}" \
-F "title=${_messageTitle}" \
-F "message=${_message}" \
"${_pushoverURL}" >/dev/null 2>&1; then
return 0
else
return 1
fi
}

438
utilities/strings.bash Normal file
View File

@@ -0,0 +1,438 @@
# Transform text using these functions
# Some were adapted from https://github.com/jmcantrell/bashful
_cleanString_() {
# DESC:
# Cleans a string of text
# ARGS:
# $1 (Required) - String to be cleaned
# $2 (optional) - Specific characters to be removed (separated by commas,
# escape regex special chars)
# OPTS:
# -l: Forces all text to lowercase
# -u: Forces all text to uppercase
# -a: Removes all non-alphanumeric characters except for spaces and dashes
# -p: Replace one character with another (separated by commas) (escape regex characters)
# -s: In combination with -a, replaces characters with a space
# OUTS:
# stdout: Prints cleaned string
# USAGE:
# _cleanString_ [OPT] [STRING] [CHARS TO REMOVE]
# _cleanString_ -lp " ,-" [STRING] [CHARS TO REMOVE]
# NOTES:
# Always cleaned:
# - leading white space
# - trailing white space
# - multiple spaces become a single space
# - remove spaces before and after -_
local opt
local _lc=false
local _uc=false
local _alphanumeric=false
local _replace=false
local _us=false
local OPTIND=1
while getopts ":lLuUaAsSpP" opt; do
case $opt in
l | L) _lc=true ;;
u | U) _uc=true ;;
a | A) _alphanumeric=true ;;
s | S) _us=true ;;
p | P)
shift
declare -a _pairs=()
IFS=',' read -r -a _pairs <<<"$1"
_replace=true
;;
*)
{
error "Unrecognized option '$1' passed to _execute. Exiting."
return 1
}
;;
esac
done
shift $((OPTIND - 1))
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _string="${1}"
local _userChars="${2:-}"
declare -a _arrayToClean=()
IFS=',' read -r -a _arrayToClean <<<"${_userChars}"
# trim trailing/leading white space and duplicate spaces/tabs
_string="$(echo "${_string}" | awk '{$1=$1};1')"
local i
for i in "${_arrayToClean[@]}"; do
debug "cleaning: $i"
_string="$(echo "${_string}" | sed "s/$i//g")"
done
("${_lc}") \
&& _string="$(echo "${_string}" | tr '[:upper:]' '[:lower:]')"
("${_uc}") \
&& _string="$(echo "${_string}" | tr '[:lower:]' '[:upper:]')"
if "${_alphanumeric}" && "${_us}"; then
_string="$(echo "${_string}" | tr -c '[:alnum:]_ -' ' ')"
elif "${_alphanumeric}"; then
_string="$(echo "${_string}" | sed "s/[^a-zA-Z0-9_ \-]//g")"
fi
if "${_replace}"; then
_string="$(echo "${_string}" | sed -E "s/${_pairs[0]}/${_pairs[1]}/g")"
fi
# trim trailing/leading white space and duplicate dashes
_string="$(echo "${_string}" | tr -s '-' | tr -s '_')"
_string="$(echo "${_string}" | sed -E 's/([_\-]) /\1/g' | sed -E 's/ ([_\-])/\1/g')"
_string="$(echo "${_string}" | awk '{$1=$1};1')"
echo "${_string}"
}
_decodeHTML_() {
# DESC:
# Decode HTML characters with sed. Utilizes a sed file for speed.
# ARGS:
# $1 (Required) - String to be decoded
# OUTS:
# 0 - Success
# 1 - Error
# stdout: Prints decoded output
# USAGE:
# _decodeHTML_ <string>
# NOTE:
# Must have a sed file containing replacements. See: ../sedfiles/htmlDecode.sed
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _sedFile
_sedFile="${HOME}/.sed/htmlDecode.sed"
[ -f "${_sedFile}" ] \
&& { printf "%s\n" "${1}" | sed -f "${_sedFile}"; } \
|| return 1
}
_decodeURL_() {
# DESC:
# Decode a URL encoded string
# ARGS:
# $1 (Required) - String to be decoded
# OUTS:
# Prints output to STDOUT
# USAGE:
# _decodeURL_ <string>
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _url_encoded="${1//+/ }"
printf '%b' "${_url_encoded//%/\\x}"
}
_encodeHTML_() {
# DESC:
# Encode HTML characters with sed
# ARGS:
# $1 (Required) - String to be encoded
# OUTS:
# 0 - Success
# 1 - Error
# stdout: Prints encoded output
# USAGE:
# _encodeHTML_ <string>
# NOTE:
# Must have a sed file containing replacements. See: ../sedfiles/htmlEncode.sed
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _sedFile
_sedFile="${HOME}/.sed/htmlEncode.sed"
[ -f "${_sedFile}" ] \
&& { echo "${1}" | sed -f "${_sedFile}"; } \
|| return 1
}
_encodeURL_() {
# DESC:
# URL encode a string
# ARGS:
# $1 (Required) - String to be encoded
# OUTS:
# Prints output to STDOUT
# USAGE:
# _encodeURL_ <string>
# CREDIT:
# https://gist.github.com/cdown/1163649
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local LANG=C
local i
for ((i = 0; i < ${#1}; i++)); do
if [[ ${1:i:1} =~ ^[a-zA-Z0-9\.\~_-]$ ]]; then
printf "${1:i:1}"
else
printf '%%%02X' "'${1:i:1}"
fi
done
}
_escapeString_() {
# DESC:
# Escapes a string by adding \ before special chars
# ARGS:
# $@ (Required) - String to be escaped
# OUTS:
# stdout: Prints escaped output
# USAGE:
# _escapeString_ "Some text here"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
printf "%s\n" "${@}" | sed 's/[]\.|$[ (){}?+*^]/\\&/g'
}
_lower_() {
# DESC:
# Convert a string to lowercase. Used through a pipe or here string.
# ARGS:
# None
# OUTS:
# None
# USAGE:
# text=$(_lower_ <<<"$1")
# echo "STRING" | _lower_
tr '[:upper:]' '[:lower:]'
}
_ltrim_() {
# DESC:
# Removes all leading whitespace (from the left). Used through a pipe or here string.
# ARGS:
# $1 (Optional) - Character to trim. Defaults to [:space:]
# OUTS:
# None
# USAGE:
# text=$(_ltrim_ <<<"$1")
# echo "STRING" | _ltrim_
local _char=${1:-[:space:]}
sed "s%^[${_char//%/\\%}]*%%"
}
_regexCapture_() {
# DESC:
# Use regex to validate and parse strings
# ARGS:
# $1 (Required) - Input String
# $2 (Required) - Regex pattern
# OUTS:
# 0 - Regex matched
# 1 - Regex did not match
# stdout: Prints string matching regex
# USAGE:
# _regex_ "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
# NOTE:
# This example only prints the first matching group. When using multiple capture
# groups some modification is needed.
# CREDIT:
# https://github.com/dylanaraps/pure-bash-bible
[[ $# -lt 2 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
if [[ $1 =~ $2 ]]; then
printf '%s\n' "${BASH_REMATCH[1]}"
return 0
else
return 1
fi
}
_rtrim_() {
# DESC:
# Removes all leading whitespace (from the right). Used through a pipe or here string.
# ARGS:
# $1 (Optional) - Character to trim. Defaults to [:space:]
# OUTS:
# None
# USAGE:
# text=$(_rtrim_ <<<"$1")
# echo "STRING" | _rtrim_
local _char=${1:-[:space:]}
sed "s%[${_char//%/\\%}]*$%%"
}
_splitString_() {
# DESC:
# Splat a string into an array based on a given delimiter
# ARGS:
# $1 (Required) - String to be split
# $2 (Required) - Delimiter
# OUTS:
# 0 - Success
# 1 - Failure
# stdout: Values split by delimiter separated by newline
# USAGE:
# ARRAY=( $(_splitString_ "string1,string2,string3" ",") )
# CREDIT:
# https://github.com/labbots/bash-utility/blob/master/src/misc.sh
[[ $# -lt 2 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
declare -a _arr=()
IFS=$'\n' read -d "" -ra _arr <<<"${1//$2/$'\n'}"
printf '%s\n' "${_arr[@]}"
}
_stringContains_() {
# DESC:
# Tests whether a string contains a substring
# ARGS:
# $1 (Required) - String to be tested
# $2 (Required) - Substring to be tested for
# OUTS:
# 0 - Search pattern found
# 1 - Pattern not found
# USAGE:
# _stringContains_ "Hello World!" "lo"
[[ $# -lt 2 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
if [[ ${1} == *${2}* ]]; then
return 0
else
return 1
fi
}
_stringRegex_() {
# DESC:
# Tests whether a string matches a regex pattern
# ARGS:
# $1 (Required) - String to be tested
# $2 (Required) - Regex pattern to be tested for
# OUTS:
# 0 - Search pattern found
# 1 - Pattern not found
# USAGE:
# _stringContains_ "HELLO" "^[A-Z]*$"
[[ $# -lt 2 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
if [[ ${1} =~ ${2} ]]; then
return 0
else
return 1
fi
}
_stripStopwords_() {
# DESC:
# Removes common stopwords from a string using a list of sed replacements located
# in an external file. Additional stopwords can be added in arg2
# ARGS:
# $1 (Required) - String to parse
# $2 (Optional) - Additional stopwords (comma separated)
# OUTS:
# 0 - Success
# 1 - Error
# stdout: Prints string cleaned of stopwords
# USAGE:
# CLEAN_WORD="$(_stripStopwords_ "[STRING]" "[MORE,STOP,WORDS]")"
# NOTE:
# Must have a sed file containing replacements. See: ../sedfiles/stopwords.sed
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
if ! sed --version | grep GNU &>/dev/null; then
fatal "_stripStopwords_: Required GNU sed not found. Exiting."
fi
local _string="${1}"
local _sedFile="${HOME}/.sed/stopwords.sed"
local _w
if [ -f "${_sedFile}" ]; then
_string="$(echo "${_string}" | sed -f "${_sedFile}")"
else
fatal "_stripStopwords_: Missing sedfile expected at: ${_sedFile}"
fi
declare -a _localStopWords=()
IFS=',' read -r -a _localStopWords <<<"${2:-}"
if [[ ${#_localStopWords[@]} -gt 0 ]]; then
for _w in "${_localStopWords[@]}"; do
_string="$(echo "${_string}" | sed -E "s/\b${_w}\b//gI")"
done
fi
# Remove double spaces and trim left/right
_string="$(echo "${_string}" | sed -E 's/[ ]{2,}/ /g' | _trim_)"
printf "%s\n" "${_string}"
}
_stripANSI_() {
# DESC:
# Strips ANSI escape sequences from a string
# ARGS:
# $1 (Required) - String to be cleaned
# OUTS:
# 0 - Success
# 1 - Failure
# stdout: Prints string with ANSI escape sequences removed
# USAGE:
# _stripANSI_ "\e[1m\e[91mThis is bold red text\e(B\e[m.\e[92mThis is green text.\e(B\e[m"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _tmp
local _esc
local _tpa
local _re
_tmp="${1}"
_esc=$(printf "\x1b")
_tpa=$(printf "\x28")
_re="(.*)${_esc}[\[${_tpa}][0-9]*;*[mKB](.*)"
while [[ ${_tmp} =~ ${_re} ]]; do
_tmp="${BASH_REMATCH[1]}${BASH_REMATCH[2]}"
done
printf "%s" "${_tmp}"
}
_trim_() {
# DESC:
# Removes all leading/trailing whitespace. Used through a pipe or here string.
# ARGS:
# None
# OUTS:
# None
# USAGE:
# text=$(_trim_ <<<"$1")
# echo "STRING" | _trim_
awk '{$1=$1;print}'
}
_upper_() {
# DESC:
# Convert a string to uppercase. Used through a pipe or here string.
# ARGS:
# None
# OUTS:
# None
# USAGE:
# text=$(_upper_ <<<"$1")
# echo "STRING" | _upper_
tr '[:lower:]' '[:upper:]'
}

View File

@@ -0,0 +1,119 @@
# Functions required to allow the script template and alert functions to be used
_acquireScriptLock_() {
# DESC:
# Acquire script lock to prevent running the same script a second time before the
# first instance exits
# ARGS:
# $1 (optional) - Scope of script execution lock (system or user)
# OUTS:
# exports $SCRIPT_LOCK - Path to the directory indicating we have the script lock
# Exits script if lock cannot be acquired
# NOTE:
# If the lock was acquired it's automatically released in _safeExit_()
local _lockDir
if [[ ${1:-} == 'system' ]]; then
_lockDir="${TMPDIR:-/tmp/}$(basename "$0").lock"
else
_lockDir="${TMPDIR:-/tmp/}$(basename "$0").$UID.lock"
fi
if command mkdir "${LOCK_DIR}" 2>/dev/null; then
readonly SCRIPT_LOCK="${_lockDir}"
debug "Acquired script lock: ${tan}${SCRIPT_LOCK}${purple}"
else
if [ "$(declare -f "_safeExit_")" ]; then
error "Unable to acquire script lock: ${yellow}${LOCK_DIR}${red}"
fatal "If you trust the script isn't running, delete the lock dir"
else
printf "%s\n" "ERROR: Could not acquire script lock. If you trust the script isn't running, delete: ${LOCK_DIR}"
exit 1
fi
fi
}
_makeTempDir_() {
# DESC:
# Creates a temp directory to house temporary files
# ARGS:
# $1 (Optional) - First characters/word of directory name
# OUTS:
# Sets $TMP_DIR variable to the path of the temp directory
# USAGE:
# _makeTempDir_ "$(basename "$0")"
[ -d "${TMP_DIR:-}" ] && return 0
if [ -n "${1:-}" ]; then
TMP_DIR="${TMPDIR:-/tmp/}${1}.${RANDOM}.${RANDOM}.$$"
else
TMP_DIR="${TMPDIR:-/tmp/}$(basename "$0").${RANDOM}.${RANDOM}.${RANDOM}.$$"
fi
(umask 077 && mkdir "${TMP_DIR}") || {
fatal "Could not create temporary directory! Exiting."
}
debug "\$TMP_DIR=${TMP_DIR}"
}
_safeExit_() {
# DESC:
# Cleanup and exit from a script
# ARGS:
# $1 (optional) - Exit code (defaults to 0)
# OUTS:
# None
if [[ -d ${SCRIPT_LOCK:-} ]]; then
if command rm -rf "${SCRIPT_LOCK}"; then
debug "Removing script lock"
else
warning "Script lock could not be removed. Try manually deleting ${yellow}'${LOCK_DIR}'"
fi
fi
if [[ -n ${TMP_DIR:-} && -d ${TMP_DIR:-} ]]; then
if [[ ${1:-} == 1 && -n "$(ls "${TMP_DIR}")" ]]; then
command rm -r "${TMP_DIR}"
else
command rm -r "${TMP_DIR}"
debug "Removing temp directory"
fi
fi
trap - INT TERM EXIT
exit ${1:-0}
}
_setPATH_() {
# DESC:
# Add directories to $PATH so script can find executables
# ARGS:
# $@ - One or more paths
# OUTS: Adds items to $PATH
# USAGE:
# _setPATH_ "/usr/local/bin" "${HOME}/bin" "$(npm bin)"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _newPath
for _newPath in "$@"; do
if [ -d "${_newPath}" ]; then
if ! echo "${PATH}" | grep -Eq "(^|:)${_newPath}($|:)"; then
if PATH="${_newPath}:${PATH}"; then
debug "Added '${_newPath}' to PATH"
else
return 1
fi
else
debug "_setPATH_: '${_newPath}' already exists in PATH"
fi
else
debug "_setPATH_: can not find: ${_newPath}"
return 1
fi
done
return 0
}

View File

@@ -1,294 +0,0 @@
# Transform text using these functions
# Some were adapted from https://github.com/jmcantrell/bashful
_cleanString_() {
# DESC: Cleans a string of text
# ARGS: $1 (Required) - String to be cleaned
# $2 (optional) - Specific characters to be removed (separated by commas,
# escape regex special chars)
# OPTS: -l Forces all text to lowercase
# -u Forces all text to uppercase
# -a Removes all non-alphanumeric characters except for spaces and dashes
# -p Replace one character with another (separated by commas) (escape regex characters)
# -s In combination with -a, replaces characters with a space
# OUTS: Prints result to STDOUT
# USAGE: _cleanString_ [OPT] [STRING] [CHARS TO REMOVE]
# _cleanString_ -lp " ,-" [STRING] [CHARS TO REMOVE]
# NOTES: Always cleaned:
# - leading white space
# - trailing white space
# - multiple spaces become a single space
# - remove spaces before and after -_
local opt
local lc=false
local uc=false
local alphanumeric=false
local replace=false
local us=false
local OPTIND=1
while getopts ":lLuUaAsSpP" opt; do
case $opt in
l | L) lc=true ;;
u | U) uc=true ;;
a | A) alphanumeric=true ;;
s | S) us=true ;;
p | P)
shift
local pairs=()
IFS=',' read -r -a pairs <<<"$1"
replace=true
;;
*)
{
error "Unrecognized option '$1' passed to _execute. Exiting."
return 1
}
;;
esac
done
shift $((OPTIND - 1))
[[ $# -lt 1 ]] && fatal 'Missing required argument to _cleanString_()!'
local string="${1}"
local userChars="${2:-}"
local arrayToClean=()
IFS=',' read -r -a arrayToClean <<<"${userChars}"
# trim trailing/leading white space and duplicate spaces/tabs
string="$(echo "${string}" | awk '{$1=$1};1')"
local i
for i in "${arrayToClean[@]}"; do
debug "cleaning: $i"
string="$(echo "${string}" | sed "s/$i//g")"
done
("${lc}") \
&& string="$(echo "${string}" | tr '[:upper:]' '[:lower:]')"
("${uc}") \
&& string="$(echo "${string}" | tr '[:lower:]' '[:upper:]')"
if "${alphanumeric}" && "${us}"; then
string="$(echo "${string}" | tr -c '[:alnum:]_ -' ' ')"
elif "${alphanumeric}"; then
string="$(echo "${string}" | sed "s/[^a-zA-Z0-9_ \-]//g")"
fi
if "${replace}"; then
string="$(echo "${string}" | sed -E "s/${pairs[0]}/${pairs[1]}/g")"
fi
# trim trailing/leading white space and duplicate dashes
string="$(echo "${string}" | tr -s '-' | tr -s '_')"
string="$(echo "${string}" | sed -E 's/([_\-]) /\1/g' | sed -E 's/ ([_\-])/\1/g')"
string="$(echo "${string}" | awk '{$1=$1};1')"
echo "${string}"
}
_stopWords_() {
# DESC: Removes common stopwords from a string
# ARGS: $1 (Required) - String to parse
# $2 (Optional) - Additional stopwords (comma separated)
# OUTS: Prints cleaned string to STDOUT
# USAGE: cleanName="$(_stopWords_ "[STRING]" "[MORE,STOP,WORDS]")"
# NOTE: Requires a stopwords file in sed format (expected at: ~/.sed/stopwords.sed)
[[ $# -lt 1 ]] && {
warning 'Missing required argument to _stripCommonWords_!'
return 1
}
if command -v gsed &>/dev/null; then
local SED_COMMAND="gsed"
elif sed --version | grep GNU &>/dev/null; then
local SED_COMMAND="sed"
else
error "Can not continue without gnu sed. Use '${yellow}brew install gnu-sed${reset} on a Mac or install with your package manager'"
return 1
fi
local string="${1}"
local sedFile="${HOME}/.sed/stopwords.sed"
if [ -f "${sedFile}" ]; then
string="$(echo "${string}" | ${SED_COMMAND} -f "${sedFile}")"
else
error "Missing sedfile in _stopWords_()"
return 1
fi
declare -a localStopWords=()
IFS=',' read -r -a localStopWords <<<"${2:-}"
if [[ ${#localStopWords[@]} -gt 0 ]]; then
for w in "${localStopWords[@]}"; do
string="$(echo "${string}" | ${SED_COMMAND} -E "s/\b${w}\b//gI")"
done
fi
# Remove double spaces and trim left/right
string="$(echo "${string}" | ${SED_COMMAND} -E 's/[ ]{2,}/ /g' | _ltrim_ | _rtrim_)"
echo "${string}"
}
_escape_() {
# DESC: Escapes a string by adding \ before special chars
# ARGS: $@ (Required) - String to be escaped
# OUTS: Prints output to STDOUT
# USAGE: _escape_ "Some text here"
# shellcheck disable=2001
echo "${@}" | sed 's/[]\.|$[ (){}?+*^]/\\&/g'
}
_htmlDecode_() {
# DESC: Decode HTML characters with sed
# ARGS: $1 (Required) - String to be decoded
# OUTS: Prints output to STDOUT
# USAGE: _htmlDecode_ <string>
# NOTE: Must have a sed file containing replacements
[[ $# -lt 1 ]] && {
error 'Missing required argument to _htmlDecode_()!'
return 1
}
local sedFile
sedFile="${HOME}/.sed/htmlDecode.sed"
[ -f "${sedFile}" ] \
&& { echo "${1}" | sed -f "${sedFile}"; } \
|| return 1
}
_htmlEncode_() {
# DESC: Encode HTML characters with sed
# ARGS: $1 (Required) - String to be encoded
# OUTS: Prints output to STDOUT
# USAGE: _htmlEncode_ <string>
# NOTE: Must have a sed file containing replacements
[[ $# -lt 1 ]] && {
error 'Missing required argument to _htmlEncode_()!'
return 1
}
local sedFile
sedFile="${HOME}/.sed/htmlEncode.sed"
[ -f "${sedFile}" ] \
&& { echo "${1}" | sed -f "${sedFile}"; } \
|| return 1
}
_lower_() {
# DESC: Convert a string to lowercase
# ARGS: None
# OUTS: None
# USAGE: text=$(_lower_ <<<"$1")
# echo "STRING" | _lower_
tr '[:upper:]' '[:lower:]'
}
_upper_() {
# DESC: Convert a string to uppercase
# ARGS: None
# OUTS: None
# USAGE: text=$(_upper_ <<<"$1")
# echo "STRING" | _upper_
tr '[:lower:]' '[:upper:]'
}
_ltrim_() {
# DESC: Removes all leading whitespace (from the left)
# ARGS: None
# OUTS: None
# USAGE: text=$(_ltrim_ <<<"$1")
# echo "STRING" | _ltrim_
local char=${1:-[:space:]}
sed "s%^[${char//%/\\%}]*%%"
}
_regex_() {
# DESC: Use regex to validate and parse strings
# ARGS: $1 (Required) - Input String
# $2 (Required) - Regex pattern
# OUTS: Prints string matching regex
# Returns error if no part of string did not match regex
# USAGE: _regex_ "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$'
# NOTE: This example only prints the first matching group. When using multiple capture
# groups some modification is needed.
# https://github.com/dylanaraps/pure-bash-bible
if [[ $1 =~ $2 ]]; then
printf '%s\n' "${BASH_REMATCH[1]}"
return 0
else
return 1
fi
}
_rtrim_() {
# DESC: Removes all leading whitespace (from the right)
# ARGS: None
# OUTS: None
# USAGE: text=$(_rtrim_ <<<"$1")
# echo "STRING" | _rtrim_
local char=${1:-[:space:]}
sed "s%[${char//%/\\%}]*$%%"
}
_trim_() {
# DESC: Removes all leading/trailing whitespace
# ARGS: None
# OUTS: None
# USAGE: text=$(_trim_ <<<"$1")
# echo "STRING" | _trim_
awk '{$1=$1;print}'
}
_urlEncode_() {
# DESC: URL encode a string
# ARGS: $1 (Required) - String to be encoded
# OUTS: Prints output to STDOUT
# USAGE: _urlEncode_ <string>
# NOTE: https://gist.github.com/cdown/1163649
[[ $# -lt 1 ]] && {
error 'Missing required argument to _urlEncode_()!'
return 1
}
local LANG=C
local i
for ((i = 0; i < ${#1}; i++)); do
if [[ ${1:i:1} =~ ^[a-zA-Z0-9\.\~_-]$ ]]; then
printf "${1:i:1}"
else
printf '%%%02X' "'${1:i:1}"
fi
done
}
_urlDecode_() {
# DESC: Decode a URL encoded string
# ARGS: $1 (Required) - String to be decoded
# OUTS: Prints output to STDOUT
# USAGE: _urlDecode_ <string>
[[ $# -lt 1 ]] && {
error 'Missing required argument to _urlDecode_()!'
return 1
}
local url_encoded="${1//+/ }"
printf '%b' "${url_encoded//%/\\x}"
}