Files
shell-scripting-templates/utilities/misc.bash
Nathaniel Landau 8bb89541e8 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
2021-10-21 16:03:27 -04:00

483 lines
14 KiB
Bash

# 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
}