Major overhaul

After working for ~6 years in private repositories, bringing my
updated BASH scripting templates back into the world.
This commit is contained in:
Nathaniel Landau
2021-07-13 17:03:27 -04:00
parent e428d2d1fd
commit b227cf6330
38 changed files with 4003 additions and 6188 deletions

159
utilities/alerts.bash Normal file
View File

@@ -0,0 +1,159 @@
# Colors
if tput setaf 1 &>/dev/null; then
bold=$(tput bold)
white=$(tput setaf 7)
reset=$(tput sgr0)
purple=$(tput setaf 171)
red=$(tput setaf 1)
green=$(tput setaf 76)
tan=$(tput setaf 3)
yellow=$(tput setaf 3)
blue=$(tput setaf 38)
underline=$(tput sgr 0 1)
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
_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: - Requires the variable LOGFILE to be set prior to
# calling this function.
# - The colors of each alert type are set in this function
# - For specified alert types, the funcstac will be printed
local scriptName logLocation logName function_name color
local alertType="${1}"
local message="${2}"
local line="${3-}"
[ -z "${LOGFILE-}" ] && fatal "\$LOGFILE must be set"
[ ! -d "$(dirname "${LOGFILE}")" ] && mkdir -p "$(dirname "${LOGFILE}")"
if [ -z "${line}" ]; then
[[ "$1" =~ ^(fatal|error|debug|warning) && "${FUNCNAME[2]}" != "_trapCleanup_" ]] \
&& message="${message} $(_functionStack_)"
else
[[ "$1" =~ ^(fatal|error|debug) && "${FUNCNAME[2]}" != "_trapCleanup_" ]] \
&& message="${message} (line: $line) $(_functionStack_)"
fi
if [ -n "${line}" ]; then
[[ "$1" =~ ^(warning|info|notice|dryrun) && "${FUNCNAME[2]}" != "_trapCleanup_" ]] \
&& message="${message} (line: $line)"
fi
if [[ "${alertType}" =~ ^(error|fatal) ]]; then
color="${bold}${red}"
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}" =~ ^(input|notice) ]]; then
color="${bold}"
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
[[ ! -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
;;
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-}"; }
die() { _alert_ fatal "${1}" "${2-}"; _safeExit_ "1" ; }
fatal() { _alert_ fatal "${1}" "${2-}"; _safeExit_ "1" ; }
debug() { _alert_ debug "${1}" "${2-}"; }
verbose() { _alert_ debug "${1}" "${2-}"; }

98
utilities/arrays.bash Normal file
View File

@@ -0,0 +1,98 @@
_inArray_() {
# DESC: Determine if a value is in an array
# ARGS: $1 (Required) - Value to search for
# $2 (Required) - Array written as ${ARRAY[@]}
# OUTS: true/false
# USAGE: if _inArray_ "VALUE" "${ARRAY[@]}"; then ...
[[ $# -lt 2 ]] && fatal 'Missing required argument to _inArray_()!'
local value="$1"
shift
for arrayItem in "$@"; do
[[ "${arrayItem}" == "${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
# 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
[[ $# -lt 2 ]] && fatal 'Missing required argument to _join_()!'
local IFS="${1}"
shift
echo "${*}"
}
_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
[[ $# -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[@]}"
}
_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
for i in "$@"; do
[[ $i ]] && IFS=" " tmp_array["${i:- }"]=1
done
printf '%s\n' "${!tmp_array[@]}"
}
_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 % $#]}"
}

277
utilities/baseHelpers.bash Normal file
View File

@@ -0,0 +1,277 @@
_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
# -p Pass a failed command with 'return 0'. This effecively 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 localVerbose=false
local passFailures=false
local echoResult=false
local successResult=false
local quietResult=false
local opt
local OPTIND=1
while getopts ":vVpPeEsSqQ" opt; do
case $opt in
v | V) localVerbose=true ;;
p | P) passFailures=true ;;
e | E) echoResult=true ;;
s | S) successResult=true ;;
q | Q) quietResult=true ;;
*)
{
error "Unrecognized option '$1' passed to _execute_. Exiting."
_safeExit_
}
;;
esac
done
shift $((OPTIND - 1))
local cmd="${1:?_execute_ needs a command}"
local message="${2:-$1}"
local saveVerbose=$VERBOSE
if "${localVerbose}"; then
VERBOSE=true
fi
if "${DRYRUN}"; then
if "$quietResult"; then
VERBOSE=$saveVerbose
return 0
fi
if [ -n "${2-}" ]; then
dryrun "${1} (${2})" "$(caller)"
else
dryrun "${1}" "$(caller)"
fi
elif ${VERBOSE}; then
if eval "${cmd}"; then
if "$echoResult"; then
echo "${message}"
elif "${successResult}"; then
success "${message}"
else
info "${message}"
fi
VERBOSE=$saveVerbose
return 0
else
if "$echoResult"; then
echo "warning: ${message}"
else
warning "${message}"
fi
VERBOSE=$saveVerbose
"${passFailures}" && return 0 || return 1
fi
else
if eval "${cmd}" &>/dev/null; then
if "$quietResult"; then
VERBOSE=$saveVerbose
return 0
elif "$echoResult"; then
echo "${message}"
elif "${successResult}"; then
success "${message}"
else
info "${message}"
fi
VERBOSE=$saveVerbose
return 0
else
if "$echoResult"; then
echo "error: ${message}"
else
warning "${message}"
fi
VERBOSE=$saveVerbose
"${passFailures}" && 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
}
_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
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 ! echo "$PATH" | grep -Eq "(^|:)${NEWPATH}($|:)"; then
PATH="${NEWPATH}:${PATH}"
debug "Added '${tan}${NEWPATH}${purple}' to PATH"
fi
done
}

34
utilities/csv.bash Normal file
View File

@@ -0,0 +1,34 @@
_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}
}

381
utilities/dates.bash Normal file
View File

@@ -0,0 +1,381 @@
_monthToNumber_() {
# DESC: Convert a month name to a number
# ARGS: None
# OUTS: Prints the number of the month to stdout
# USAGE: _monthToNumber_ "January"
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 ;;
april|apr|ap) echo 4 ;;
may) echo 5 ;;
june|jun|ju) echo 6 ;;
july|jul) echo 7 ;;
august|aug|au) echo 8 ;;
september|sep|se) echo 9 ;;
october|oct) echo 10 ;;
november|nov|no) echo 11 ;;
december|dec|de) echo 12 ;;
*)
warning "month_monthToNumber_: Bad monthname: $1"
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
local mon="$1"
case "$mon" in
1|01) echo January ;;
2|02) echo February ;;
3|03) echo March ;;
4|04) echo April ;;
5|05) echo May ;;
6|06) echo June ;;
7|07) echo July ;;
8|08) echo August ;;
9|09) echo September ;;
10) echo October ;;
11) echo November ;;
12) echo December ;;
*)
warning "_numberToMonth_: Bad month number: $1"
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 '-_ ./'
# * YYYY-MM-DD * Month DD, YYYY * DD Month, YYYY
# * Month, YYYY * Month, DD YY * MM-DD-YYYY
# * MMDDYYYY * YYYYMMDD * DDMMYYYY
# * YYYYMMDDHHMM * YYYYMMDDHH * DD-MM-YYYY
# * DD MM YY * MM DD YY
# TODO: Impelemt the following date formats
# * MMDDYY * YYMMDD * mon-DD-YY
[[ $# -eq 0 ]] && {
error 'Missing required argument to _parseDate_()!'
return 1
}
local date="${1:-$(date +%F)}"
_parseDate_found="" _parseDate_year="" _parseDate_month="" _parseDate_monthName=""
_parseDate_day="" _parseDate_hour="" _parseDate_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]} ))
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]:-} ))
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]} ))"
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]}" ))
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
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]} ))
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]} ))
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]} ))
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
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]} ))
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]} ))
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]} ))
debug "regex match: ${tan}MM-DD-YYYY${purple}"
else
shopt -u nocasematch
return 1
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]} ))"
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]} ))"
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"
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
# 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]}"
debug "regex match: ${tan}MMDDYYYY${purple}"
# DDMMYYYY
elif [[ $(( 10#${BASH_REMATCH[5]} )) -eq 20 && \
$(( 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_day="$(( 10#${BASH_REMATCH[3]} ))"
_parseDate_month="$(( 10#${BASH_REMATCH[4]} ))"
_parseDate_monthName="$(_numberToMonth_ "${_parseDate_month}")"
_parseDate_year="${BASH_REMATCH[5]}${BASH_REMATCH[6]}"
debug "regex match: ${tan}DDMMYYYY${purple}"
# YYYYMMDD
elif [[ $(( 10#${BASH_REMATCH[3]} )) -eq 20 \
&& $(( 10#${BASH_REMATCH[6]} )) -gt 12 \
&& $(( 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]}"
debug "regex match: ${tan}YYYYMMDD${purple}"
# YYYYDDMM
elif [[ $(( 10#${BASH_REMATCH[3]} )) -eq 20 \
&& $(( 10#${BASH_REMATCH[5]} )) -gt 12 \
&& $(( 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]}"
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]}"
debug "regex match: ${tan}YYYYMMDD${purple}"
else
shopt -u nocasematch
return 1
fi
# # MMDD or DDYY
# elif [[ "$date" =~ .*(([0-9]{2})([0-9]{2})).* ]]; then
# debug "regex match: ${tan}MMDD or DDMM${purple}"
# _parseDate_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 )"
# 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 )"
# else
# shopt -u nocasematch
# return 1
# fi
else
shopt -u nocasematch
return 1
fi
[[ -z ${_parseDate_year:-} ]] && { shopt -u nocasematch; return 1 ; }
(( _parseDate_month >= 1 && _parseDate_month <= 12 )) || { shopt -u nocasematch; return 1 ; }
(( _parseDate_day >= 1 && _parseDate_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_inute:-} ]] || debug "${tan}\$_parseDate_minute: ${_parseDate_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}"
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)
[[ $# -eq 0 ]] && {
error 'Missing required argument to _formatDate_()'
return 1
}
local d="${1}"
local format="${2:-%F}"
format="${format//+/}"
if command -v gdate >/dev/null 2>&1; then
gdate -d "${d}" "+${format}"
else
date -d "${d}" "+${format}"
fi
}

651
utilities/files.bash Normal file
View File

@@ -0,0 +1,651 @@
_listFiles_() {
# DESC: Find files in a directory. Use either glob or regex
# ARGS: $1 (Required) - 'g|glob' or 'r|regex'
# $2 (Required) - pattern to match
# $3 (Optional) - directory
# OUTS: Prints files to STDOUT
# NOTE: Searches are NOT case sensitive and MUST be quoted
# USAGE: _listFiles_ glob "*.txt" "some/backup/dir"
# _listFiles_ regex ".*\.txt" "some/backup/dir"
# readarry -t array < <(_listFiles_ g "*.txt")
[[ $# -lt 2 ]] && {
error 'Missing required argument to _listFiles_()!'
return 1
}
local t="${1}"
local p="${2}"
local d="${3:-.}"
local fileMatch e
case "$t" in
glob | Glob | g | G)
while read -r fileMatch; do
echo "${e}"
done < <(find "${d}" -iname "${p}" -type f -maxdepth 1 | sort)
;;
regex | Regex | r | R)
while read -r fileMatch; do
e="$(realpath "${fileMatch}")"
echo "${e}"
done < <(find "${d}" -iregex "${p}" -type f -maxdepth 1 | sort)
;;
*)
echo "Could not determine if search was glob or regex"
return 1
;;
esac
}
_backupFile_() {
# DESC: Creates a backup of a specified file with .bak extension or
# optionally to a specified directory
# ARGS: $1 (Required) - Source file
# $2 (Optional) - Destination dir name used only with -d flag (defaults to ./backup)
# OPTS: -d - Move files to a backup direcory
# -m - Replaces copy (default) with move, effectively removing the
# OUTS: None
# USAGE: _backupFile_ "sourcefile.txt" "some/backup/dir"
# NOTE: dotfiles have their leading '.' removed in their backup
local opt
local OPTIND=1
local useDirectory=false
local moveFile=false
while getopts ":dDmM" opt; do
case ${opt} in
d | D) useDirectory=true ;;
m | M) moveFile=true ;;
*)
{
error "Unrecognized option '$1' passed to _makeSymlink_" "${LINENO}"
return 1
}
;;
esac
done
shift $((OPTIND - 1))
[[ $# -lt 1 ]] && fatal 'Missing required argument to _backupFile_()!'
local s="${1}"
local d="${2:-backup}"
local n # New filename (created by _uniqueFilename_)
# Error handling
[ ! "$(declare -f "_execute_")" ] \
&& {
warning "need function _execute_"
return 1
}
[ ! "$(declare -f "_uniqueFileName_")" ] \
&& {
warning "need function _uniqueFileName_"
return 1
}
[ ! -e "$s" ] \
&& {
warning "Source '${s}' not found"
return 1
}
if [ ${useDirectory} == true ]; then
[ ! -d "${d}" ] \
&& _execute_ "mkdir -p \"${d}\"" "Creating backup directory"
if [ -e "$s" ]; then
n="$(basename "${s}")"
n="$(_uniqueFileName_ "${d}/${s#.}")"
if [ ${moveFile} == true ]; then
_execute_ "mv \"${s}\" \"${d}/${n##*/}\"" "Moving: '${s}' to '${d}/${n##*/}'"
else
_execute_ "cp -R \"${s}\" \"${d}/${n##*/}\"" "Backing up: '${s}' to '${d}/${n##*/}'"
fi
fi
else
n="$(_uniqueFileName_ "${s}.bak")"
if [ ${moveFile} == true ]; then
_execute_ "mv \"${s}\" \"${n}\"" "Moving '${s}' to '${n}'"
else
_execute_ "cp -R \"${s}\" \"${n}\"" "Backing up '${s}' to '${n}'"
fi
fi
}
_cleanFilename_() {
# DESC: Cleans a filename of all non-alphanumeric (or user specified)
# characters and overwrites original
# ARGS: $1 (Required) - File to be cleaned
# $2 (optional) - Additional characters to be cleaned separated by commas
# OUTS: Overwrites file with new new and prints name of new file
# USAGE: _cleanFilename_ "FILENAME.TXT" "^,&,*"
# NOTE: IMPORTANT - This will overwrite the original file
# IMPORTANT - All spaces and underscores will be replaced by dashes (-)
[[ $# -lt 1 ]] && fatal 'Missing required argument to _cleanFilename_()!'
local arrayToClean
local fileToClean="$(realpath "$1")"
local optionalUserInput="${2-}"
IFS=',' read -r -a arrayToClean <<<"$optionalUserInput"
[ ! -f "${fileToClean}" ] \
&& {
warning "_cleanFileName_ ${fileToClean}: File doesn't exist"
return 1
}
local dir="$(realpath -d "${fileToClean}")"
local extension="${fileToClean##*.}"
local baseFileName="$(basename "${fileToClean%.*}")"
for i in "${arrayToClean[@]}"; do
baseFileName="$(echo "${baseFileName}" | sed "s/$i//g")"
done
baseFileName="$(echo "${baseFileName}" | tr -dc '[:alnum:]-_ ' | sed 's/ /-/g')"
local final="${dir}/${baseFileName}.${extension}"
if [ "${fileToClean}" != "${final}" ]; then
final="$(_uniqueFileName_ "${final}")"
if ${VERBOSE}; then
_execute_ "mv \"${fileToClean}\" \"${final}\""
else
_execute_ -q "mv \"${fileToClean}\" \"${final}\""
fi
echo "${final}"
else
echo "${fileToClean}"
fi
}
_parseFilename_() {
# DESC: Break a filename into its component parts which and place them into prefixed
# variables (dir, basename, extension, full path, etc.)
# with _parseFile...
# ARGS: $1 (Required) - A file
# OUTS: $_parsedFileFull - File and its real path (ie, resolve symlinks)
# $_parseFilePath - Path to the file
# $_parseFileName - Name of the file WITH extension
# $_parseFileBase - Name of file WITHOUT extension
# $_parseFileExt - The extension of the file (from _ext_())
[[ $# -lt 1 ]] && fatal 'Missing required argument to _parseFilename_()!'
local fileToParse="${1}"
[[ -f "${fileToParse}" ]] || {
error "Can't locate good file to parse at: ${fileToParse}"
return 1
}
# Ensure we are working with a real file, not a symlink
_parsedFileFull="$(realpath "${fileToParse}")" \
&& debug "${tan}\${_parsedFileFull}: ${_parsedFileFull-}${purple}"
# use the basename of the userFile going forward since the path is now in $filePath
_parseFileName=$(basename "${fileToParse}") \
&& debug "${tan}\$_parseFileName: ${_parseFileName}${purple}"
# Grab the filename without the extension
_parseFileBase="${_parseFileName%.*}" \
&& debug "${tan}\$_parseFileBase: ${_parseFileBase}${purple}"
# Grab the extension
if [[ "${fileToParse}" =~ .*\.[a-zA-Z]{2,4}$ ]]; then
_parseFileExt="$(_ext_ "${_parseFileName}")"
else
_parseFileExt=".${_parseFileName##*.}"
fi
debug "${tan}\$_parseFileExt: ${_parseFileExt}${purple}"
# Grab the directory
_parseFilePath="${_parsedFileFull%/*}" \
&& debug "${tan}\${_parseFilePath}: ${_parseFilePath}${purple}"
}
_decryptFile_() {
# DESC: Decrypts a file with openSSL
# ARGS: $1 (Required) - File to be decrypted
# $2 (Optional) - Name of output file (defaults to $1.decrypt)
# OUTS: None
# USAGE: _decryptFile_ "somefile.txt.enc" "decrypted_somefile.txt"
# NOTE: If a variable '$PASS' has a value, we will use that as the password
# to decrypt the file. Otherwise we will ask
[[ $# -lt 1 ]] && fatal 'Missing required argument to _decryptFile_()!'
local fileToDecrypt decryptedFile defaultName
fileToDecrypt="${1:?_decryptFile_ needs a file}"
defaultName="${fileToDecrypt%.enc}"
decryptedFile="${2:-$defaultName.decrypt}"
[ ! "$(declare -f "_execute_")" ] \
&& {
echo "need function _execute_"
return 1
}
[ ! -f "$fileToDecrypt" ] && return 1
if [ -z "${PASS}" ]; then
_execute_ "openssl enc -aes-256-cbc -d -in \"${fileToDecrypt}\" -out \"${decryptedFile}\"" "Decrypt ${fileToDecrypt}"
else
_execute_ "openssl enc -aes-256-cbc -d -in \"${fileToDecrypt}\" -out \"${decryptedFile}\" -k \"${PASS}\"" "Decrypt ${fileToDecrypt}"
fi
}
_encryptFile_() {
# DESC: Encrypts a file using openSSL
# ARGS: $1 (Required) - Input file
# $2 (Optional) - Name of output file (defaults to $1.enc)
# OUTS: None
# USAGE: _encryptFile_ "somefile.txt" "encrypted_somefile.txt"
# NOTE: If a variable '$PASS' has a value, we will use that as the password
# for the encrypted file. Otherwise we will ask.
local fileToEncrypt encryptedFile defaultName
fileToEncrypt="${1:?_encodeFile_ needs a file}"
defaultName="${fileToEncrypt%.decrypt}"
encryptedFile="${2:-$defaultName.enc}"
[ ! -f "$fileToEncrypt" ] && return 1
[ ! "$(declare -f "_execute_")" ] \
&& {
echo "need function _execute_"
return 1
}
if [ -z "${PASS}" ]; then
_execute_ "openssl enc -aes-256-cbc -salt -in \"${fileToEncrypt}\" -out \"${encryptedFile}\"" "Encrypt ${fileToEncrypt}"
else
_execute_ "openssl enc -aes-256-cbc -salt -in \"${fileToEncrypt}\" -out \"${encryptedFile}\" -k \"${PASS}\"" "Encrypt ${fileToEncrypt}"
fi
}
_ext_() {
# DESC: Extract the extension from a filename
# ARGS: $1 (Required) - Input file
# OPTS: -n - optional flag for number of extension levels (Ex: -n2)
# OUTS: Print extension to STDOUT
# USAGE:
# _ext_ foo.txt #==> txt
# _ext_ -n2 foo.tar.gz #==> tar.gz
# _ext_ foo.tar.gz #==> tar.gz
# _ext_ -n1 foo.tar.gz #==> gz
[[ $# -lt 1 ]] && fatal 'Missing required argument to _ext_()!'
local levels
local option
local filename
local exts
local ext
local fn
local i
unset OPTIND
while getopts ":n:" option; do
case $option in
n) levels=$OPTARG ;;
*) continue ;;
esac
done && shift $((OPTIND - 1))
filename=${1##*/}
[[ $filename == *.* ]] || return
fn=$filename
# Detect some common multi-extensions
if [[ ! ${levels-} ]]; then
case $(tr '[:upper:]' '[:lower:]' <<<"${filename}") in
*.tar.gz | *.tar.bz2) levels=2 ;;
esac
fi
levels=${levels:-1}
for ((i = 0; i < levels; i++)); do
ext=${fn##*.}
exts=${ext}${exts-}
fn=${fn%$ext}
[[ "$exts" == "${filename}" ]] && return
done
echo "$exts"
}
_extract_() {
# DESC: Extract a compressed file
# ARGS: $1 (Required) - Input file
# $2 (optional) - Input 'v' to show verbose output
# OUTS: None
local filename
local foldername
local fullpath
local didfolderexist
local vv
[[ $# -lt 1 ]] && fatal 'Missing required argument to _extract_()!'
[[ "${2-}" == "v" ]] && vv="v"
if [ -f "$1" ]; then
case "$1" in
*.tar.bz2 | *.tbz | *.tbz2) tar "x${vv}jf" "$1" ;;
*.tar.gz | *.tgz) tar "x${vv}zf" "$1" ;;
*.tar.xz)
xz --decompress "$1"
set -- "$@" "${1:0:-3}"
;;
*.tar.Z)
uncompress "$1"
set -- "$@" "${1:0:-2}"
;;
*.bz2) bunzip2 "$1" ;;
*.deb) dpkg-deb -x${vv} "$1" "${1:0:-4}" ;;
*.pax.gz)
gunzip "$1"
set -- "$@" "${1:0:-3}"
;;
*.gz) gunzip "$1" ;;
*.pax) pax -r -f "$1" ;;
*.pkg) pkgutil --expand "$1" "${1:0:-4}" ;;
*.rar) unrar x "$1" ;;
*.rpm) rpm2cpio "$1" | cpio -idm${vv} ;;
*.tar) tar "x${vv}f" "$1" ;;
*.txz)
mv "$1" "${1:0:-4}.tar.xz"
set -- "$@" "${1:0:-4}.tar.xz"
;;
*.xz) xz --decompress "$1" ;;
*.zip | *.war | *.jar) unzip "$1" ;;
*.Z) uncompress "$1" ;;
*.7z) 7za x "$1" ;;
*) return 1 ;;
esac
else
return 1
fi
shift
}
_json2yaml_() {
# DESC: Convert JSON to YAML
# ARGS: $1 (Required) - JSON file
# OUTS: None
python -c 'import sys, yaml, json; yaml.safe_dump(json.load(sys.stdin), sys.stdout, default_flow_style=False)' <"${1:?_json2yaml_ needs a file}"
}
_makeSymlink_() {
# DESC: 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.
# Default behavior will create a backup of a file to be overwritten
# ARGS: $1 (Required) - Source file
# $2 (Required) - Destination
# $3 (Optional) - Backup directory for files which may be overwritten (defaults to 'backup')
# OPTS: -n - Do not create a backup if target already exists
# -s - Use sudo when removing old files to make way for new symlinks
# OUTS: None
# USAGE: _makeSymlink_ "/dir/someExistingFile" "/dir/aNewSymLink" "/dir/backup/location"
# NOTE: This function makes use of the _execute_ function
local opt
local OPTIND=1
local backupOriginal=true
local useSudo=false
while getopts ":nNsS" opt; do
case $opt in
n | N) backupOriginal=false ;;
s | S) useSudo=true ;;
*)
{
error "Unrecognized option '$1' passed to _makeSymlink_" "$LINENO"
return 1
}
;;
esac
done
shift $((OPTIND - 1))
if ! command -v realpath >/dev/null 2>&1; then
error "We must have 'realpath' installed and available in \$PATH to run."
if [[ "$OSTYPE" == "darwin"* ]]; then
notice "Install coreutils using homebrew and rerun this script."
info "\t$ brew install coreutils"
fi
_safeExit_ 1
fi
[[ $# -lt 2 ]] && fatal 'Missing required argument to _makeSymlink_()!'
local s="$1"
local d="$2"
local b="${3-}"
local o
# Fix files where $HOME is written as '~'
d="${d/\~/$HOME}"
s="${s/\~/$HOME}"
b="${b/\~/$HOME}"
[ ! -e "$s" ] \
&& {
error "'$s' not found"
return 1
}
[ -z "$d" ] \
&& {
error "'${d}' not specified"
return 1
}
[ ! "$(declare -f "_execute_")" ] \
&& {
echo "need function _execute_"
return 1
}
[ ! "$(declare -f "_backupFile_")" ] \
&& {
echo "need function _backupFile_"
return 1
}
# Create destination directory if needed
[ ! -d "${d%/*}" ] \
&& _execute_ "mkdir -p \"${d%/*}\""
if [ ! -e "${d}" ]; then
_execute_ "ln -fs \"${s}\" \"${d}\"" "symlink ${s}${d}"
elif [ -h "${d}" ]; then
o="$(realpath "${d}")"
[[ "${o}" == "${s}" ]] && {
if [ "${DRYRUN}" == true ]; then
dryrun "Symlink already exists: ${s}${d}"
else
info "Symlink already exists: ${s}${d}"
fi
return 0
}
if [[ "${backupOriginal}" == true ]]; then
_backupFile_ "${d}" "${b:-backup}"
fi
if [[ "${DRYRUN}" == false ]]; then
if [[ "${useSudo}" == true ]]; then
command rm -rf "${d}"
else
command rm -rf "${d}"
fi
fi
_execute_ "ln -fs \"${s}\" \"${d}\"" "symlink ${s}${d}"
elif [ -e "${d}" ]; then
if [[ "${backupOriginal}" == true ]]; then
_backupFile_ "${d}" "${b:-backup}"
fi
if [[ "${DRYRUN}" == false ]]; then
if [[ "${useSudo}" == true ]]; then
sudo command rm -rf "${d}"
else
command rm -rf "${d}"
fi
fi
_execute_ "ln -fs \"${s}\" \"${d}\"" "symlink ${s}${d}"
else
warning "Error linking: ${s}${d}"
return 1
fi
return 0
}
_parseYAML_() {
# DESC: Convert a YANML file into BASH variables for use in a shell script
# ARGS: $1 (Required) - Source YAML file
# $2 (Required) - Prefix for the variables to avoid namespace collisions
# OUTS: Prints variables and arrays derived from YAML File
# USAGE: To source into a script
# _parseYAML_ "sample.yml" "CONF_" > tmp/variables.txt
# source "tmp/variables.txt"
#
# NOTE: https://gist.github.com/DinoChiesa/3e3c3866b51290f31243
# https://gist.github.com/epiloque/8cf512c6d64641bde388
local yamlFile="${1:?_parseYAML_ needs a file}"
local prefix="${2-}"
[ ! -s "${yamlFile}" ] && return 1
local s='[[:space:]]*'
local w='[a-zA-Z0-9_]*'
local fs="$(echo @ | tr @ '\034')"
sed -ne "s|^\(${s}\)\(${w}\)${s}:${s}\"\(.*\)\"${s}\$|\1${fs}\2${fs}\3|p" \
-e "s|^\(${s}\)\(${w}\)${s}[:-]${s}\(.*\)${s}\$|\1${fs}\2${fs}\3|p" "${yamlFile}" \
| awk -F"${fs}" '{
indent = length($1)/2;
if (length($2) == 0) { conj[indent]="+";} else {conj[indent]="";}
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]}}
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
printf("%s%s%s%s=(\"%s\")\n", "'"${prefix}"'",vn, $2, conj[indent-1],$3);
}
}' | sed 's/_=/+=/g' | sed 's/[[:space:]]*#.*"/"/g'
}
_readFile_() {
# DESC: Prints each line of a file
# ARGS: $1 (Required) - Input file
# OUTS: Prints contents of file
[[ $# -lt 1 ]] && fatal 'Missing required argument to _readFile_()!'
local result
local c="$1"
[ ! -f "$c" ] \
&& {
echo "'$c' not found"
return 1
}
while read -r result; do
echo "${result}"
done <"${c}"
}
_sourceFile_() {
# DESC: Source a file into a script
# ARGS: $1 (Required) - File to be sourced
# OUTS: None
[[ $# -lt 1 ]] && fatal 'Missing required argument to _sourceFile_()!'
local c="$1"
[ ! -f "$c" ] \
&& {
fatal "Attempted to source '$c' Not found"
return 1
}
source "$c"
return 0
}
_uniqueFileName_() {
# DESC: Ensure a file to be created has a unique filename to avoid overwriting other files
# ARGS: $1 (Required) - Name of file to be created
# $2 (Optional) - Separation characted (Defaults to a period '.')
# OUTS: Prints unique filename to STDOUT
# USAGE: _uniqueFileName_ "/some/dir/file.txt" "-"
local fullfile="${1:?_uniqueFileName_ needs a file}"
local spacer="${2:-.}"
local directory
local filename
local extension
local newfile
local n
if ! command -v realpath >/dev/null 2>&1; then
error "We must have 'realpath' installed and available in \$PATH to run."
if [[ "$OSTYPE" == "darwin"* ]]; then
notice "Install coreutils using homebrew and rerun this script."
info "\t$ brew install coreutils"
fi
_safeExit_ 1
fi
# Find directories with realpath if input is an actual file
if [ -e "${fullfile}" ]; then
fullfile="$(realpath "${fullfile}")"
fi
directory="$(dirname "${fullfile}")"
filename="$(basename "${fullfile}")"
# Extract extensions only when they exist
if [[ "${filename}" =~ \.[a-zA-Z]{2,4}$ ]]; then
extension=".${filename##*.}"
filename="${filename%.*}"
fi
newfile="${directory}/${filename}${extension-}"
if [ -e "${newfile}" ]; then
n=1
while [[ -e "${directory}/${filename}${extension-}${spacer}${n}" ]]; do
((n++))
done
newfile="${directory}/${filename}${extension-}${spacer}${n}"
fi
echo "${newfile}"
return 0
}
_yaml2json_() {
# DESC: Convert a YAML file to JSON
# ARGS: $1 (Required) - Input YAML file
# OUTS: None
python -c 'import sys, yaml, json; json.dump(yaml.load(sys.stdin), sys.stdout, indent=4)' <"${1:?_yaml2json_ needs a file}"
}

39
utilities/macOS.bash Normal file
View File

@@ -0,0 +1,39 @@
# Functions for use on computers running MacOS
_haveScriptableFinder_() {
# DESC: Determine whether we can script the Finder or not
# ARGS: None
# OUTS: true/false
local finder_pid
finder_pid="$(pgrep -f /System/Library/CoreServices/Finder.app | head -n 1)"
if [[ (${finder_pid} -gt 1) && ("${STY-}" == "") ]]; then
return 0
else
return 1
fi
}
_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
if _haveScriptableFinder_; then
guiPrompt="${1:-Password:}"
guiInput=$(
osascript &>/dev/null <<EOF
tell application "System Events"
activate
text returned of (display dialog "${guiPrompt}" default answer "" with hidden answer)
end tell
EOF
)
echo -n "${guiInput}"
else
error "No GUI input without macOS"
return 1
fi
}

71
utilities/numbers.bash Normal file
View File

@@ -0,0 +1,71 @@
_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
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
}

148
utilities/services.bash Normal file
View File

@@ -0,0 +1,148 @@
_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
}
_httpStatus_() {
# 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
# 301 Redirection: Moved Permanently
#
# Example: $ _httpStatus_ www.google.com 100 --code
local code
local status
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}"
# __________ get the CODE which is numeric:
code=$(echo "$(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." ;;
esac
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}"
}
_pushover_() {
# DESC: Sends a notification via Pushover
# ARGS: $1 (Required) - Title of notification
# $2 (Required) - Body of notification
# OUTS: None
# 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
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
}

View File

@@ -0,0 +1,289 @@
# 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 cleaned (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 REPLACE]
# _cleanString_ -p " ,-" [STRING] [CHARS TO REPLACE]
# NOTES: Always cleaned:
# - leading white space
# - trailing white space
# - multiple spaces become a single space
# - remove spaces before and aftrer -_
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 "s/${pairs[0]}/${pairs[1]}/g")"
fi
# trim trailing/leading white space and duplicate dashes
string="$(echo "${string}" | tr -s '-')"
string="$(echo "${string}" | sed -E 's/([-_]) /\1/g' | sed -E 's/ ([-_])/\1/g')"
string="$(echo "${string}" | awk '{$1=$1};1')"
printf "%s\n" "${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
}
[ "$(command -v gsed)" ] || {
error "Can not continue without gsed. Use '${YELLOW}brew install gnu-sed${reset}'"
return 1
}
local string="${1}"
local sedFile="${HOME}/.sed/stopwords.sed"
if [ -f "${sedFile}" ]; then
string="$(echo "${string}" | gsed -f "${sedFile}")"
else
debug "Missing sedfile in _stopWords_()"
fi
declare -a localStopWords=()
IFS=',' read -r -a localStopWords <<<"${2-}"
if [[ ${#localStopWords[@]} -gt 0 ]]; then
for w in "${localStopWords[@]}"; do
string="$(echo "$string" | gsed -E "s/$w//gI")"
done
fi
# Remove double spaces and trim left/right
string="$(echo "$string" | sed -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}))$' || echo "no match found"
# 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}"
}