mirror of
https://github.com/natelandau/shell-scripting-templates.git
synced 2025-11-08 05:03:46 -05:00
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
483 lines
14 KiB
Bash
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
|
|
}
|