1613 lines
54 KiB
Bash
1613 lines
54 KiB
Bash
#! /usr/bin/env bash
|
|
# ___________________________________________________________________________ #
|
|
# #
|
|
# BashLIB -- A library for Bash scripting convenience. #
|
|
# #
|
|
# #
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); #
|
|
# you may not use this file except in compliance with the License. #
|
|
# You may obtain a copy of the License at #
|
|
# #
|
|
# http://www.apache.org/licenses/LICENSE-2.0 #
|
|
# #
|
|
# Unless required by applicable law or agreed to in writing, software #
|
|
# distributed under the License is distributed on an "AS IS" BASIS, #
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
|
# See the License for the specific language governing permissions and #
|
|
# limitations under the License. #
|
|
# ___________________________________________________________________________ #
|
|
# #
|
|
# #
|
|
# Copyright 2007-2013, lhunath #
|
|
# * http://www.lhunath.com #
|
|
# * Maarten Billemont #
|
|
# #
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# | |
|
|
# | .:: TABLE OF CONTENTS ::. |
|
|
# |______________________________________________________________________|
|
|
#
|
|
# chr decimal
|
|
# Outputs the character that has the given decimal ASCII value.
|
|
#
|
|
# ord character
|
|
# Outputs the decimal ASCII value of the given character.
|
|
#
|
|
# hex character
|
|
# Outputs the hexadecimal ASCII value of the given character.
|
|
#
|
|
# unhex character
|
|
# Outputs the character that has the given decimal ASCII value.
|
|
#
|
|
# max numbers...
|
|
# Outputs the highest of the given numbers.
|
|
#
|
|
# min numbers...
|
|
# Outputs the lowest of the given numbers.
|
|
#
|
|
# totime "YYYY-MM-DD HH:MM:SS.mmm"...
|
|
# Outputs the number of milliseconds in the given date string(s).
|
|
#
|
|
# exists application
|
|
# Succeeds if the application is in PATH and is executable.
|
|
#
|
|
# eol message
|
|
# Return termination punctuation for a message, if necessary.
|
|
#
|
|
# hr pattern [length]
|
|
# Outputs a horizontal ruler of the given length in characters or the terminal column length otherwise.
|
|
#
|
|
# cloc
|
|
# Outputs the current cursor location as two space-separated numbers: row column.
|
|
#
|
|
# readwhile command [args]
|
|
# Outputs the characters typed by the user into the terminal's input buffer while running the given command.
|
|
#
|
|
# pushqueue element ...
|
|
# Pushes the given arguments as elements onto the queue.
|
|
#
|
|
# popqueue
|
|
# Pops one element off the queue.
|
|
#
|
|
# log [format] [arguments...]
|
|
# Log an event at a certain importance level.
|
|
# The event is expressed as a printf(1) format argument.
|
|
#
|
|
# ask [-c optionchars|-d default] [-s|-S maskchar] message...
|
|
# Ask a question and read the user's reply to it. Then output the result on stdout.
|
|
#
|
|
# trim lines ...
|
|
# Trim the whitespace off of the beginning and end of the given lines.
|
|
#
|
|
# reverse [-0|-d delimitor] [elements ...] [<<< elements]
|
|
# Reverse the order of the given elements.
|
|
#
|
|
# order [-0|-d char] [-[cC] isAscending|-n] [-t number] [elements ...] [<<< elements]
|
|
# Orders the elements in ascending order.
|
|
#
|
|
# mutex file
|
|
# Open a mutual exclusion lock on the file, unless another process already owns one.
|
|
#
|
|
# pushjob [poolsize] command
|
|
# Start an asynchronous command within a pool, waiting for space in the pool if it is full.
|
|
#
|
|
# fsleep time
|
|
# Wait for the given (fractional) amount of seconds.
|
|
#
|
|
# showHelp name description author [option description]...
|
|
# Generate a prettily formatted usage description of the application.
|
|
#
|
|
# shquote [-e] [argument...]
|
|
# Shell-quote the arguments to make them safe for injection into bash code.
|
|
#
|
|
# requote [string]
|
|
# Escape the argument string to make it safe for injection into a regex.
|
|
#
|
|
# shorten [-p pwd] path [suffix]...
|
|
# Shorten an absolute path for pretty printing.
|
|
#
|
|
# up .../path|num
|
|
# Walk the current working directory up towards root num times or until path is found.
|
|
#
|
|
# buildarray name terms... -- elements...
|
|
# Create an array by adding all the terms to it for each element, replacing {} terms by the element.
|
|
#
|
|
# inArray element array
|
|
# Checks whether a certain element is in the given array.
|
|
#
|
|
# xpathNodes query [files...]
|
|
# Outputs every xpath node that matches the query on a separate line.
|
|
#
|
|
# hideDebug [on|off]
|
|
# Toggle Bash's debugging mode off temporarily.
|
|
#
|
|
# stackTrace
|
|
# Output the current script's function execution stack.
|
|
#
|
|
_tocHash=71e13f42e1ea82c1c7019b27a3bc71f3
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# | |
|
|
# | .:: GLOBAL CONFIGURATION ::. |
|
|
# |______________________________________________________________________|
|
|
|
|
# Unset all exported functions. Exported functions are evil.
|
|
while read _ _ func; do
|
|
command unset -f "$func"
|
|
done < <(command declare -Fx)
|
|
|
|
{
|
|
shopt -s extglob
|
|
shopt -s globstar
|
|
} 2>/dev/null ||:
|
|
|
|
# Generate Table Of Contents
|
|
genToc() {
|
|
local line= comments=() usage= whatis= lineno=0 out= outhash= outline=
|
|
while read -r line; do
|
|
(( ++lineno ))
|
|
|
|
[[ $line = '#'* ]] && comments+=("$line") && continue
|
|
[[ $line = +([[:alnum:]])'() {' ]] && IFS='()' read func _ <<< "$line" && [[ $func != $FUNCNAME ]] && {
|
|
usage=${comments[3]##'#'+( )}
|
|
whatis=${comments[5]##'#'+( )}
|
|
[[ $usage = $func* && $whatis = *. ]] || err "Malformed docs for %s (line %d)." "$func" "$lineno"
|
|
|
|
printf -v outline '# %s\n# %s\n#\n' "$usage" "$whatis"
|
|
out+=$outline
|
|
}
|
|
comments=()
|
|
done < ~/.bin/bashlib
|
|
|
|
outhash=$(openssl md5 <<< "$out")
|
|
if [[ $_tocHash = "$outhash" ]]; then
|
|
inf 'Table of contents up-to-date.'
|
|
else
|
|
printf '%s' "$out"
|
|
printf '_tocHash=%q' "$outhash"
|
|
wrn 'Table of contents outdated.'
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# | |
|
|
# | .:: GLOBAL DECLARATIONS ::. |
|
|
# |______________________________________________________________________|
|
|
|
|
# Variables for convenience sequences.
|
|
bobber=( '.' 'o' 'O' 'o' )
|
|
spinner=( '-' \\ '|' '/' )
|
|
crosser=( '+' 'x' '+' 'x' )
|
|
runner=( '> >' \
|
|
'>> ' \
|
|
' >>' )
|
|
|
|
# Variables for terminal requests.
|
|
[[ -t 2 && $TERM != dumb ]] && {
|
|
COLUMNS=$( tput cols || tput co ) # Columns in a line
|
|
LINES=$( tput lines || tput li ) # Lines on screen
|
|
alt=$( tput smcup || tput ti ) # Start alt display
|
|
ealt=$( tput rmcup || tput te ) # End alt display
|
|
hide=$( tput civis || tput vi ) # Hide cursor
|
|
show=$( tput cnorm || tput ve ) # Show cursor
|
|
save=$( tput sc ) # Save cursor
|
|
load=$( tput rc ) # Load cursor
|
|
dim=$( tput dim || tput mh ) # Start dim
|
|
bold=$( tput bold || tput md ) # Start bold
|
|
stout=$( tput smso || tput so ) # Start stand-out
|
|
estout=$( tput rmso || tput se ) # End stand-out
|
|
under=$( tput smul || tput us ) # Start underline
|
|
eunder=$( tput rmul || tput ue ) # End underline
|
|
reset=$( tput sgr0 || tput me ) # Reset cursor
|
|
blink=$( tput blink || tput mb ) # Start blinking
|
|
italic=$( tput sitm || tput ZH ) # Start italic
|
|
eitalic=$( tput ritm || tput ZR ) # End italic
|
|
[[ $TERM != *-m ]] && {
|
|
red=$( tput setaf 1|| tput AF 1 )
|
|
green=$( tput setaf 2|| tput AF 2 )
|
|
yellow=$( tput setaf 3|| tput AF 3 )
|
|
blue=$( tput setaf 4|| tput AF 4 )
|
|
magenta=$( tput setaf 5|| tput AF 5 )
|
|
cyan=$( tput setaf 6|| tput AF 6 )
|
|
}
|
|
black=$( tput setaf 0|| tput AF 0 )
|
|
white=$( tput setaf 7|| tput AF 7 )
|
|
default=$( tput op )
|
|
eed=$( tput ed || tput cd ) # Erase to end of display
|
|
eel=$( tput el || tput ce ) # Erase to end of line
|
|
ebl=$( tput el1 || tput cb ) # Erase to beginning of line
|
|
ewl=$eel$ebl # Erase whole line
|
|
draw=$( tput -S <<< ' enacs
|
|
smacs
|
|
acsc
|
|
rmacs' || { \
|
|
tput eA; tput as;
|
|
tput ac; tput ae; } ) # Drawing characters
|
|
back=$'\b'
|
|
} 2>/dev/null ||:
|
|
|
|
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# | |
|
|
# | .:: FUNCTION DECLARATIONS ::. |
|
|
# |______________________________________________________________________|
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Chr _______________________________________________________________|
|
|
#
|
|
# chr decimal
|
|
#
|
|
# Outputs the character that has the given decimal ASCII value.
|
|
#
|
|
chr() {
|
|
printf "\\$(printf '%03o' "$1")"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Ord _______________________________________________________________|
|
|
#
|
|
# ord character
|
|
#
|
|
# Outputs the decimal ASCII value of the given character.
|
|
#
|
|
ord() {
|
|
printf '%d' "'$1"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Hex _______________________________________________________________|
|
|
#
|
|
# hex character
|
|
#
|
|
# Outputs the hexadecimal ASCII value of the given character.
|
|
#
|
|
hex() {
|
|
printf '%x' "'$1"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Unhex _______________________________________________________________|
|
|
#
|
|
# unhex character
|
|
#
|
|
# Outputs the character that has the given hexadecimal ASCII value.
|
|
#
|
|
unhex() {
|
|
printf "\\x$1"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ max _______________________________________________________________|
|
|
#
|
|
# max numbers...
|
|
#
|
|
# Outputs the highest of the given numbers.
|
|
#
|
|
max() {
|
|
local max=$1 n
|
|
for n; do
|
|
(( n > max )) && max=$n
|
|
done
|
|
printf %d "$max"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ min _______________________________________________________________|
|
|
#
|
|
# min numbers...
|
|
#
|
|
# Outputs the lowest of the given numbers.
|
|
#
|
|
min() {
|
|
local min=$1 n
|
|
for n; do
|
|
(( n < min )) && min=$n
|
|
done
|
|
printf '%d' "$min"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ totime ____________________________________________________________|
|
|
#
|
|
# totime "YYYY-MM-DD HH:MM:SS.mmm"...
|
|
#
|
|
# Outputs the number of milliseconds in the given date string(s).
|
|
#
|
|
# When multiple date string arguments are given, multiple time strings are output, one per line.
|
|
#
|
|
# The fields should be in the above defined order. The delimitor between the fields may be any one of [ -:.].
|
|
# If a date string does not follow the defined format, the result is undefined.
|
|
#
|
|
# Note that this function uses a very simplistic conversion formula which does not take any special calendar
|
|
# convenions into account. It assumes there are 12 months in evert year, 31 days in every month, 24 hours
|
|
# in every day, 60 minutes in every hour, 60 seconds in every minute and 1000 milliseconds in every second.
|
|
#
|
|
totime() {
|
|
local arg time year month day hour minute second milli
|
|
for arg; do
|
|
IFS=' -:.' read year month day hour minute second milli <<< "$arg" &&
|
|
(( time = (((((((((((10#$year * 12) + 10#$month) * 31) + 10#$day) * 24) + 10#$hour) * 60) + 10#$minute) * 60) + 10#$second) * 1000) + 10#$milli )) &&
|
|
printf '%d\n' "$time"
|
|
done
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Exists ____________________________________________________________|
|
|
#
|
|
# exists application
|
|
#
|
|
# Succeeds if the application is in PATH and is executable.
|
|
#
|
|
exists() {
|
|
[[ -x $(type -P "$1" 2>/dev/null) ]]
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ FirstExists ____________________________________________________________|
|
|
#
|
|
# firstExists file...
|
|
#
|
|
# Outputs the first of the arguments that is a file which exists.
|
|
#
|
|
firstExists() {
|
|
local file;
|
|
for file; do
|
|
[[ -e "$file" ]] && printf %s "$file" && exit
|
|
done
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Eol _______________________________________________________________|
|
|
#
|
|
# eol message
|
|
#
|
|
# Return termination punctuation for a message, if necessary.
|
|
#
|
|
eol() {
|
|
: #[[ $1 && $1 != *[\!\?.,:\;\|] ]] && printf .. ||:
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Hr _______________________________________________________________|
|
|
#
|
|
# hr pattern [length]
|
|
#
|
|
# Outputs a horizontal ruler of the given length in characters or the terminal column length otherwise.
|
|
# The ruler is a repetition of the given pattern string.
|
|
#
|
|
hr() {
|
|
local pattern=${1:--} length=${2:-$COLUMNS} ruler=
|
|
|
|
while (( ${#ruler} < length )); do
|
|
ruler+=${pattern:0:length-${#ruler}}
|
|
done
|
|
|
|
printf %s "$ruler"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ CLoc ______________________________________________________________|
|
|
#
|
|
# cloc
|
|
#
|
|
# Outputs the current cursor location as two space-separated numbers: row column.
|
|
#
|
|
cloc() {
|
|
local old=$(stty -g)
|
|
trap 'stty "$old"' RETURN
|
|
stty raw
|
|
|
|
# If the tty has input waiting then we can't read back its response. We'd only break and pollute the tty input buffer.
|
|
read -t 0 < /dev/tty 2>/dev/null && return 1
|
|
|
|
printf '\e[6n' > /dev/tty
|
|
IFS='[;' read -dR _ row col < /dev/tty
|
|
printf '%d %d' "$row" "$col"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ readwhile ______________________________________________________________|
|
|
#
|
|
# readwhile command [args]
|
|
#
|
|
# Outputs the characters typed by the user into the terminal's input buffer while running the given command.
|
|
#
|
|
readwhile() {
|
|
local old=$(stty -g) in result REPLY
|
|
trap 'stty "$old"' RETURN
|
|
stty raw
|
|
|
|
"$@"
|
|
result=$?
|
|
|
|
while read -t 0; do
|
|
IFS= read -rd '' -n1 && in+=$REPLY
|
|
done
|
|
printf %s "$in"
|
|
|
|
return $result
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ___________________________________________________________________________
|
|
# |__ pushqueue ______________________________________________________________|
|
|
#
|
|
# pushqueue element ...
|
|
#
|
|
# Pushes the given arguments as elements onto the queue.
|
|
#
|
|
pushqueue() {
|
|
[[ $_queue ]] || {
|
|
coproc _queue {
|
|
while IFS= read -r -d ''; do
|
|
printf '%s\0' "$REPLY"
|
|
done
|
|
}
|
|
}
|
|
|
|
printf '%s\0' "$@" >&"${_queue[1]}"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# __________________________________________________________________________
|
|
# |__ popqueue ______________________________________________________________|
|
|
#
|
|
# popqueue
|
|
#
|
|
# Pops one element off the queue.
|
|
# If no elements are available on the queue, this command fails with exit code 1.
|
|
#
|
|
popqueue() {
|
|
local REPLY
|
|
[[ $_queue ]] && read -t0 <&"${_queue[0]}" || return
|
|
IFS= read -r -d '' <&"${_queue[0]}"
|
|
printf %s "$REPLY"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Latest ____________________________________________________________|
|
|
#
|
|
# latest [file...]
|
|
#
|
|
# Output the argument that represents the file with the latest modification time.
|
|
#
|
|
latest() (
|
|
shopt -s nullglob
|
|
local file latest=$1
|
|
for file; do
|
|
[[ $file -nt $latest ]] && latest=$file
|
|
done
|
|
printf '%s\n' "$latest"
|
|
) # _____________________________________________________________________
|
|
|
|
|
|
|
|
# _______________________________________________________________________
|
|
# |__ Iterate ____________________________________________________________|
|
|
#
|
|
# iterate [command]
|
|
#
|
|
# All arguments to iterate make up a single command that will be executed.
|
|
#
|
|
# Any of the arguments may be of the format {x..y[..z]} which causes the command
|
|
# to be executed in a loop, each iteration substituting the argument for the
|
|
# current step the loop has reached from x to y. We step from x to y by
|
|
# walking from x's position in the ASCII character table to y's with a step of z
|
|
# or 1 if z is not specified.
|
|
#
|
|
iterate() (
|
|
local command=( "$@" ) iterationCommand=() loop= a= arg= current=() step=() target=()
|
|
for a in "${!command[@]}"; do
|
|
arg=${command[a]}
|
|
if [[ $arg = '{'*'}' ]]; then
|
|
loop=${arg#'{'} loop=${loop%'}'}
|
|
step[a]=${loop#*..*..} current[a]=${loop%%..*} target[a]=${loop#*..} target[a]=${target[a]%.."${step[a]}"}
|
|
[[ ! ${step[a]} || ${step[a]} = $loop ]] && step[a]=1
|
|
fi
|
|
done
|
|
if (( ${#current[@]} )); then
|
|
for loop in "${!current[@]}"; do
|
|
while true; do
|
|
iterationCommand=()
|
|
|
|
for a in "${!command[@]}"; do
|
|
(( a == loop )) \
|
|
&& iterationCommand+=( "${current[a]}" ) \
|
|
|| iterationCommand+=( "${command[a]}" )
|
|
done
|
|
|
|
iterate "${iterationCommand[@]}"
|
|
|
|
[[ ${current[loop]} = ${target[loop]} ]] && break
|
|
current[loop]="$(chr "$(( $(ord "${current[loop]}") + ${step[loop]} ))")"
|
|
done
|
|
done
|
|
else
|
|
"${command[@]}"
|
|
fi
|
|
) # _____________________________________________________________________
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Logging ___________________________________________________________|
|
|
#
|
|
# log format [arguments...]
|
|
#
|
|
# Log an event at a certain importance level. The event is expressed as a printf(1) format argument.
|
|
# The current exit code remains unaffected by the execution of this function.
|
|
#
|
|
# Instead of 'log', you can use a level as command name, to log at that level. Using log, messages are
|
|
# logged at level inf. The supported levels are: trc, dbg, inf, wrn, err, ftl.
|
|
#
|
|
# If you prefix the command name with a p, the log message is shown as a spinner and waits for the next
|
|
# closing statement. Eg.
|
|
#
|
|
# pinf 'Converting image'
|
|
# convert image.png image.jpg
|
|
# fnip
|
|
#
|
|
# The closing statement (here fnip) is the reverse of the opening statement and exits with the exit code
|
|
# of the last command. If the last command failed, it shows the exit code in the spinner before it is stopped.
|
|
# The closing statement also takes a format and arguments, which are displayed in the spinner.
|
|
#
|
|
log() {
|
|
local exitcode=$? level=${level:-inf} supported=0 end=$'\n' type=msg conMsg= logMsg= format= colorFormat= date= info= arg= args=() colorArgs=() ruler=
|
|
|
|
# Handle options.
|
|
local OPTIND=1
|
|
while getopts :tpuPrR:d:n arg; do
|
|
case $arg in
|
|
p)
|
|
end='.. '
|
|
type=startProgress ;;
|
|
u)
|
|
end='.. '
|
|
type=updateProgress ;;
|
|
P)
|
|
type=stopProgress ;;
|
|
r)
|
|
ruler='____' ;;
|
|
R)
|
|
ruler=$OPTARG ;;
|
|
d)
|
|
end=$OPTARG ;;
|
|
n)
|
|
end= ;;
|
|
t)
|
|
date=$(date +"${_logDate:-%H:%M}") ;;
|
|
esac
|
|
done
|
|
shift "$((OPTIND-1))"
|
|
format=$1 args=( "${@:2}" )
|
|
(( ! ${#args[@]} )) && [[ $format ]] && { args=("$format") format=%s; local bold=; }
|
|
|
|
# Level-specific settings.
|
|
local logLevelColor
|
|
case $level in
|
|
TRC) (( supported = _logVerbosity >= 4 ))
|
|
logLevelColor=$_logTrcColor ;;
|
|
DBG) (( supported = _logVerbosity >= 3 ))
|
|
logLevelColor=$_logDbgColor ;;
|
|
INF) (( supported = _logVerbosity >= 2 ))
|
|
logLevelColor=$_logInfColor ;;
|
|
WRN) (( supported = _logVerbosity >= 1 ))
|
|
logLevelColor=$_logWrnColor ;;
|
|
ERR) (( supported = _logVerbosity >= 0 ))
|
|
logLevelColor=$_logErrColor ;;
|
|
FTL) (( supported = 1 ))
|
|
logLevelColor=$_logFtlColor ;;
|
|
*)
|
|
log FTL 'Log level %s does not exist' "$level"
|
|
exit 1 ;;
|
|
esac
|
|
(( ! supported )) && return "$exitcode"
|
|
local logColor=${_logColor:+$logLevelColor}
|
|
|
|
# Generate the log message.
|
|
case $type in
|
|
msg|startProgress)
|
|
printf -v logMsg "[${date:+%s }%-3s] $format$end" ${date:+"$date"} "$level" "${args[@]}"
|
|
if (( _logColor )); then
|
|
colorFormat=$(sed ${reset:+-e "s/$(requote "$reset")/$reset$logColor/g"} -e "s/%[^a-z]*[a-z]/$reset$bold$logColor&$reset$logColor/g" <<< "$format")
|
|
colorArgs=("${args[@]//$reset/$reset$bold$logColor}")
|
|
printf -v conMsg "$reset[${date:+%s }$logColor$bold%-3s$reset] $logColor$colorFormat$reset$black\$$reset$end$save" ${date:+"$date"} "$level" "${colorArgs[@]}"
|
|
else
|
|
conMsg=$logMsg
|
|
fi
|
|
;;
|
|
|
|
updateProgress)
|
|
printf -v logMsg printf " [$format]" "${args[@]}"
|
|
if (( _logColor )); then
|
|
colorFormat=$(sed ${reset:+-e "s/$(requote "$reset")/$reset$logColor/g"} -e "s/%[^a-z]*[a-z]/$reset$bold$logColor&$reset$logColor/g" <<< "$format")
|
|
colorArgs=("${args[@]//$reset/$reset$bold$logColor}")
|
|
printf -v conMsg "$load$eel$blue$bold[$reset$logColor$colorFormat$reset$blue$bold]$reset$end" "${colorArgs[@]}"
|
|
else
|
|
conMsg=$logMsg
|
|
fi
|
|
;;
|
|
|
|
stopProgress)
|
|
case $exitcode in
|
|
0) printf -v logMsg "done${format:+ ($format)}.\n" "${args[@]}"
|
|
if (( _logColor )); then
|
|
colorFormat=$(sed ${reset:+-e "s/$(requote "$reset")/$reset$logColor/g"} -e "s/%[^a-z]*[a-z]/$reset$bold$logColor&$reset$logColor/g" <<< "$format")
|
|
colorArgs=("${args[@]//$reset/$reset$bold$logColor}")
|
|
printf -v conMsg "$load$eel$green${bold}done${colorFormat:+ ($reset$logColor$colorFormat$reset$green$bold)}$reset.\n" "${colorArgs[@]}"
|
|
else
|
|
conMsg=$logMsg
|
|
fi
|
|
;;
|
|
|
|
*) info=${format:+$(printf ": $format" "${args[@]}")}
|
|
printf -v logMsg "error(%d%s).\n" "$exitcode" "$info"
|
|
if (( _logColor )); then
|
|
printf -v conMsg "${eel}${red}error${reset}(${bold}${red}%d${reset}%s).\n" "$exitcode" "$info"
|
|
else
|
|
conMsg=$logMsg
|
|
fi
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
|
|
# Create the log file.
|
|
if [[ $_logFile && ! -e $_logFile ]]; then
|
|
[[ $_logFile = */* ]] || $_logFile=./$logFile
|
|
mkdir -p "${_logFile%/*}" && touch "$_logFile"
|
|
fi
|
|
|
|
# Stop the spinner.
|
|
if [[ $type = stopProgress && $_logSpinner ]]; then
|
|
kill "$_logSpinner"
|
|
wait "$_logSpinner" 2>/dev/null
|
|
unset _logSpinner
|
|
fi
|
|
|
|
# Output the ruler.
|
|
if [[ $ruler ]]; then
|
|
printf >&2 '%s\n' "$(hr "$ruler")"
|
|
[[ -w $_logFile ]] \
|
|
&& printf >> "$_logFile" '%s' "$ruler"
|
|
fi
|
|
|
|
# Output the log message.
|
|
printf >&2 '%s' "$conMsg"
|
|
[[ -w $_logFile ]] \
|
|
&& printf >> "$_logFile" '%s' "$logMsg"
|
|
|
|
# Start the spinner.
|
|
if [[ $type = startProgress && ! $_logSpinner ]]; then
|
|
{
|
|
set +m
|
|
trap 'printf %s "$show"' EXIT
|
|
printf %s "$hide"
|
|
while printf "$eel$blue$bold[$reset%s$reset$blue$bold]$reset\b\b\b" "${spinner[s++ % ${#spinner[@]}]}" && sleep .1
|
|
do :; done
|
|
} & _logSpinner=$!
|
|
fi 2>/dev/null
|
|
|
|
return $exitcode
|
|
}
|
|
trc() { level=TRC log "$@"; }
|
|
dbg() { level=DBG log "$@"; }
|
|
inf() { level=INF log "$@"; }
|
|
wrn() { level=WRN log "$@"; }
|
|
err() { level=ERR log "$@"; }
|
|
ftl() { level=FTL log "$@"; }
|
|
plog() { log -p "$@"; }
|
|
ulog() { log -u "$@"; }
|
|
golp() { log -P "$@"; }
|
|
ptrc() { level=TRC plog "$@"; }
|
|
pdbg() { level=DBG plog "$@"; }
|
|
pinf() { level=INF plog "$@"; }
|
|
pwrn() { level=WRN plog "$@"; }
|
|
perr() { level=ERR plog "$@"; }
|
|
pftl() { level=FTL plog "$@"; }
|
|
utrc() { level=TRC ulog "$@"; }
|
|
udbg() { level=DBG ulog "$@"; }
|
|
uinf() { level=INF ulog "$@"; }
|
|
uwrn() { level=WRN ulog "$@"; }
|
|
uerr() { level=ERR ulog "$@"; }
|
|
uftl() { level=FTL ulog "$@"; }
|
|
gtrc() { level=trc golp "$@"; }
|
|
gbdp() { level=DBG golp "$@"; }
|
|
fnip() { level=INF golp "$@"; }
|
|
nrwp() { level=WRN golp "$@"; }
|
|
rrep() { level=ERR golp "$@"; }
|
|
ltfp() { level=FTL golp "$@"; }
|
|
_logColor=${_logColor:-$([[ -t 2 ]] && echo 1)} _logVerbosity=2
|
|
_logTrcColor=$grey _logDbgColor=$blue _logInfColor=$white _logWrnColor=$yellow _logErrColor=$red _logFtlColor=$bold$red
|
|
# _______________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Ask _______________________________________________________________|
|
|
#
|
|
# ask [-c optionchars|-d default] [-s|-S maskchar] format [arguments...]
|
|
#
|
|
# Ask a question and read the user's reply to it. Then output the result on stdout.
|
|
#
|
|
# When in normal mode, a single line is read. If the line is empty and
|
|
# -d was specified, the default argument is output instead of an empty line.
|
|
# The exit code is always 0.
|
|
#
|
|
# When in option mode (-c), the user is shown the option characters with
|
|
# which he can reply and a single character is read.
|
|
# If the reply is empty (user hits enter) and any of the optionchars are
|
|
# upper-case, the upper-case option (= the default option) character will
|
|
# be output instead of an empty line.
|
|
# If the reply character is not amoungst the provided options the default
|
|
# option is again output instead if present. If no default was given, an
|
|
# exit code of 2 is returned.
|
|
# You may mark an optionchar as 'valid' by appending a '!' to it. As a
|
|
# result, an exit code of 0 will only be returned if this valid option
|
|
# is replied. If not, an exit code of 1 will be returned.
|
|
#
|
|
ask() {
|
|
|
|
# Initialize the vars.
|
|
local opt arg
|
|
local option=
|
|
local options=
|
|
local default=
|
|
local silent=
|
|
local valid=
|
|
local muteChar=
|
|
local format=
|
|
|
|
# Parse the options.
|
|
local OPTIND=1
|
|
while getopts :sS:c:d: opt; do
|
|
case $opt in
|
|
s) silent=1 ;;
|
|
S) silent=1 muteChar=$OPTARG ;;
|
|
c) while read -n1 arg; do
|
|
case $arg in
|
|
[[:upper:]]) default=$arg ;;
|
|
!) valid=${options: -1}; continue ;;
|
|
esac
|
|
|
|
options+=$arg
|
|
done <<< "$OPTARG" ;;
|
|
d) default=$OPTARG option=$default ;;
|
|
esac
|
|
done
|
|
|
|
# Trim off the options.
|
|
shift $((OPTIND-1))
|
|
|
|
# Figure out what FD to use for our messages.
|
|
[[ -t 1 ]] && local fd=1 || local fd=2
|
|
|
|
# Ask the question.
|
|
format=$1; shift
|
|
level=${level:-WRN} log -n "$format${option:+ [%s]}${options:+ [%s]}" "$@" ${option:+"$option"} ${options:+"$options"}
|
|
|
|
# Read the reply.
|
|
exec 8<&0; [[ -t 8 ]] || exec 8</dev/tty
|
|
if [[ $muteChar ]]; then
|
|
local reply
|
|
while read -u8 -s -n1 && [[ $REPLY ]]; do
|
|
reply+=$REPLY
|
|
printf '%s' "$muteChar" >&$fd
|
|
done
|
|
REPLY=$reply
|
|
else
|
|
read -u8 -e ${options:+-n1} ${silent:+-s}
|
|
fi
|
|
[[ $options && $REPLY ]] || (( silent )) && printf '\n' >&$fd
|
|
|
|
# Evaluate the reply.
|
|
while true; do
|
|
if [[ $REPLY && ( ! $options || $options = *$REPLY* ) ]]; then
|
|
if [[ $valid ]]
|
|
then [[ $REPLY = $valid ]]
|
|
else printf "%s" "$REPLY"
|
|
fi
|
|
|
|
return
|
|
fi
|
|
|
|
[[ -z $default || $REPLY = $default ]] \
|
|
&& return 2
|
|
|
|
REPLY=$default
|
|
done
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Trim ______________________________________________________________|
|
|
#
|
|
# trim lines ...
|
|
#
|
|
# Trim the whitespace off of the beginning and end of the given lines.
|
|
# Each argument is considdered one line; is treated and printed out.
|
|
#
|
|
# When no arguments are given, lines will be read from standard input.
|
|
#
|
|
trim() {
|
|
{ (( $# )) && printf '%s\n' "$@" || cat; } | \
|
|
sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Reverse ___________________________________________________________|
|
|
#
|
|
# reverse [-0|-d delimitor] [elements ...] [<<< elements]
|
|
#
|
|
# Reverse the order of the given elements.
|
|
# Elements are read from command arguments or standard input if no element
|
|
# arguments are given.
|
|
# They are reversed and output on standard output.
|
|
#
|
|
# If the -0 option is given, input and output are delimited by NUL bytes.
|
|
# If the -d option is given, input and output are delimited by the
|
|
# character argument.
|
|
# Otherwise, they are delimited by newlines.
|
|
#
|
|
reverse() {
|
|
|
|
# Initialize the vars.
|
|
local elements=() delimitor=$'\n' i
|
|
|
|
# Parse the options.
|
|
local OPTIND=1
|
|
while getopts :0d: opt; do
|
|
case $opt in
|
|
0) delimitor=$'\0' ;;
|
|
d) delimitor=$OPTARG ;;
|
|
esac
|
|
done
|
|
shift "$((OPTIND-1))"
|
|
|
|
# Get the elements.
|
|
if (( $# )); then
|
|
elements=( "$@" )
|
|
else
|
|
while IFS= read -r -d "$delimitor"; do
|
|
elements+=("$REPLY")
|
|
done
|
|
fi
|
|
|
|
# Iterate in reverse order.
|
|
for (( i=${#elements[@]} - 1; i >=0; --i )); do
|
|
printf "%s${delimitor:-'\0'}" "${elements[i]}"
|
|
done
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Order _____________________________________________________________|
|
|
#
|
|
# order [-0|-d char] [-[fF] isDesired] [-[cC] isAscending|-n|-r|-t] [-T number] [-a array|elements ...] [<<< elements]
|
|
#
|
|
# Orders the elements in ascending order.
|
|
# Elements are read from command arguments or standard input if no element
|
|
# arguments are given.
|
|
# The result is output on standard output.
|
|
#
|
|
# By default, the elements will be ordered using lexicographic comparison.
|
|
# If the -n option is given, the elements will be ordered numerically.
|
|
# If the -r option is given, the elements will be ordered randomly.
|
|
# If the -f option is given, the command name following it will be used
|
|
# as a filter.
|
|
# If the -c option is given, the command name following it will be used
|
|
# as a comparator.
|
|
# If the -C option is given, the bash code following it will be used
|
|
# as a comparator.
|
|
# If the -t option is given, only the first number results are returned.
|
|
# If the -a option is given, the elements in array are ordered instead and
|
|
# array is mutated to contain the result.
|
|
# If number is 0, all results are returned.
|
|
#
|
|
# isDesired is a command name which will get one parameter. The parameter
|
|
# is an element which will only be included if the command exits successfully.
|
|
# isAscending is a command name which will be executed for each element
|
|
# comparison and will be passed two element arguments. The command should
|
|
# succeed if the first argument is less than the second argument for the
|
|
# purpose of this sort.
|
|
#
|
|
# If the -0 option is given, input and output are delimited by NUL bytes.
|
|
# If the -d option is given, input and output are delimited by the
|
|
# character argument.
|
|
# Otherwise, they are delimited by newlines.
|
|
#
|
|
# The ordering is implemented by an insertion sort algorithm.
|
|
#
|
|
order() {
|
|
|
|
# Initialize the vars.
|
|
local delimitor=$'\n' i isDesired=true isAscending=string_ascends top=0 arrayName= array=
|
|
|
|
# Parse the options.
|
|
local OPTIND=1
|
|
while getopts :0nrd:f:F:c:C:tT:a: opt; do
|
|
case $opt in
|
|
0) delimitor=$'\0' ;;
|
|
d) delimitor=$OPTARG ;;
|
|
n) isAscending=number_ascends ;;
|
|
r) isAscending=random_ascends ;;
|
|
t) isAscending=mtime_ascends ;;
|
|
f) isDesired=$OPTARG ;;
|
|
F) isDesired=bash_desired bash_desired_code=$OPTARG ;;
|
|
c) isAscending=$OPTARG ;;
|
|
C) isAscending=bash_ascends bash_ascends_code=$OPTARG ;;
|
|
T) top=$OPTARG ;;
|
|
a) arrayName=$OPTARG array=$arrayName[@] ;;
|
|
esac
|
|
done
|
|
shift "$((OPTIND-1))"
|
|
|
|
# Get the elements.
|
|
local elements=() element
|
|
if [[ $arrayName ]]; then
|
|
for element in "${!array}"; do
|
|
"$isDesired" "$element" && elements+=("$element")
|
|
done
|
|
elif (( $# )); then
|
|
for element; do
|
|
"$isDesired" "$element" && elements+=("$element")
|
|
done
|
|
else
|
|
while IFS= read -r -d "$delimitor" element; do
|
|
"$isDesired" "$element" && elements+=("$element")
|
|
done
|
|
fi
|
|
|
|
# Iterate in reverse order.
|
|
for (( i = 1; i < ${#elements[@]}; ++i )); do
|
|
for (( j = i; j > 0; --j )); do
|
|
element=${elements[j]}
|
|
if "$isAscending" "$element" "${elements[j-1]}"; then
|
|
elements[j]=${elements[j-1]}
|
|
elements[j-1]=$element
|
|
fi
|
|
done
|
|
done
|
|
|
|
(( top )) || top=${#elements[@]}
|
|
if [[ $array ]]; then
|
|
declare -ga "$array=($(printf '%q ' "${elements[@]:0:top}"))"
|
|
else
|
|
printf "%s${delimitor:-\0}" "${elements[@]:0:top}"
|
|
fi
|
|
} # _____________________________________________________________________
|
|
string_ascends() { [[ $1 < $2 ]]; }
|
|
number_ascends() { (( $1 < $2 )); }
|
|
random_ascends() { (( RANDOM % 2 )); }
|
|
mtime_ascends() { [[ $1 -ot $2 ]]; }
|
|
exists_desired() { [[ -e $1 ]]; }
|
|
line_desired() { [[ $1 ]]; }
|
|
code_desired() { line_desired "$1" && ! comment_desired "$1"; }
|
|
comment_desired() { line_desired "$1" && [[ $1 = @(#|//|/\*)* ]]; }
|
|
bash_desired() { bash -c "$bash_desired_code" -- "$@"; }
|
|
bash_ascends() { bash -c "$bash_ascends_code" -- "$@"; }
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Mutex _____________________________________________________________|
|
|
#
|
|
# mutex file
|
|
#
|
|
# Open a mutual exclusion lock on the file, unless another process already owns one.
|
|
#
|
|
# If the file is already locked by another process, the operation fails.
|
|
# This function defines a lock on a file as having a file descriptor open to the file.
|
|
# This function uses FD 9 to open a lock on the file. To release the lock, close FD 9:
|
|
# exec 9>&-
|
|
#
|
|
mutex() {
|
|
local lockfile=${1:-${BASH_SOURCE[-1]}} pid pids
|
|
[[ -e $lockfile ]] || err "No such file: $lockfile" || return
|
|
|
|
exec 9>> "$lockfile" && [[ $({ fuser -f "$lockfile"; } 2>&- 9>&-) == $$ ]]
|
|
}
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ PushJob ___________________________________________________________|
|
|
#
|
|
# pushjob [poolsize] command
|
|
#
|
|
# Start an asynchronous command within a pool, waiting for space in the pool if it is full.
|
|
#
|
|
# The pool is pruned automatically as running jobs complete. This function
|
|
# allows you to easily run asynchronous commands within a pool of N,
|
|
# automatically starting the next command as soon as there's space.
|
|
#
|
|
pushjob() {
|
|
local size=$1; shift 1
|
|
|
|
# Wait for space in the pool.
|
|
until (( ${#jobpool[@]} < size )); do
|
|
sleep 1 & pushjobsleep=$!
|
|
wait "$pushjobsleep"
|
|
done 2>/dev/null
|
|
|
|
# Register prunejobs and start the pushed job.
|
|
trap _prunejobs SIGCHLD
|
|
set -m
|
|
"$@" & jobpool[$!]=
|
|
}
|
|
_prunejobs() {
|
|
# Prune all pool jobs that are no longer running.
|
|
for pid in "${!jobpool[@]}"; do
|
|
kill -0 "$pid" 2>/dev/null || unset "jobpool[$pid]"
|
|
done
|
|
|
|
# Unregister SIGCHLD if our pool is empty.
|
|
(( ${#jobpool[@]} )) || trap - SIGCHLD
|
|
|
|
# Wake up pushjob.
|
|
kill "$pushjobsleep" 2>/dev/null
|
|
}
|
|
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ FSleep _____________________________________________________________|
|
|
#
|
|
# fsleep time
|
|
#
|
|
# Wait for the given (fractional) amount of seconds.
|
|
#
|
|
# This implementation solves the problem portably, assuming that either
|
|
# bash 4.x or a fractional sleep(1) is available.
|
|
#
|
|
fsleep() {
|
|
|
|
local fifo=${TMPDIR:-/tmp}/.fsleep.$$
|
|
trap 'rm -f "$fifo"' RETURN
|
|
mkfifo "$fifo" && { read -t "$1" <> "$fifo" 2>/dev/null || sleep "$1"; }
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ options ___________________________________________________________|
|
|
#
|
|
# options [option description]...
|
|
#
|
|
# Specify and handle options in arguments.
|
|
#
|
|
# The 'setopt' function will be called for each option expected option
|
|
# passed to the script, with $1 set to the option character and $2
|
|
# its description. Check OPTARG if the option takes an argument.
|
|
# 'setopt' will be called with '?' if an invalid option is passed.
|
|
#
|
|
# Unless specified, the -h option will show a usage description,
|
|
# explaining the options.
|
|
#
|
|
# Proposed usage:
|
|
# setopt() {
|
|
# case "$1" in
|
|
# a) echo "got option a" ;;
|
|
# b) echo "got option b with argument $OPTARG" ;;
|
|
# esac
|
|
# }
|
|
# options \
|
|
# a 'option a' \
|
|
# b: 'option b with argument'
|
|
#
|
|
options() {
|
|
|
|
# Parse the expected options and their description.
|
|
declare -A options=()
|
|
while (( $# )); do
|
|
local optchar=$1 optdesc=$2
|
|
shift 2 || ftl 'Missing arguments, expected option (%s), description (%s).' "$optchar" "$optdesc" || exit
|
|
options[$optchar]=$optdesc
|
|
done
|
|
|
|
# Find the script's options.
|
|
local argc=${BASH_ARGC[@]: -1} argv=("${BASH_ARGV[@]: -argc}") arg
|
|
local optstring=$(printf %s "${!options[@]}")h
|
|
set -- # Sigh. BASH_ARGV is all backwards.
|
|
for arg in "${argv[@]}"; do
|
|
set -- "$arg" "$@"
|
|
done
|
|
|
|
# Handle the script's options.
|
|
while getopts "$optstring" arg; do
|
|
if [[ $arg = h && ! ${options[h]} ]]; then
|
|
# Show usage message.
|
|
[[ -t 1 ]]; local fd=$(( $? + 1 )) optarg
|
|
|
|
# Print out the app usage.
|
|
printf " Usage: $reset$bold%s$reset" "${BASH_SOURCE[1]##*/}" >&$fd
|
|
for optchar in "${!options[@]}"; do
|
|
[[ $optchar = *: ]] && optarg=" arg" || optarg=
|
|
printf " [$bold$green-%s$reset%s]" "${optchar%:}" "$optarg" >&$fd
|
|
done
|
|
printf "\n\n" >&$fd
|
|
|
|
# Print out the option descriptions.
|
|
for optchar in "${!options[@]}"; do
|
|
local optdesc=${options[$optchar]}
|
|
[[ $optchar = *: ]] && optarg=" arg" || optarg=
|
|
printf " $bold$green-%s$reset%s\t" "${optchar%:}" "$optarg"
|
|
fmt -w "$COLUMNS" <<< "${optdesc//+( )/ }" | sed $'1!s/^/ \t/'
|
|
printf "\n"
|
|
done | column -t -s $'\t' >&$fd
|
|
else
|
|
optchar=$arg; [[ ! ${options[$arg]} && ${options[$arg:]} ]] && optchar=$arg:
|
|
setopt "$arg" "${options[$arg]}"
|
|
fi
|
|
done
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ ShowHelp __________________________________________________________|
|
|
#
|
|
# showHelp name description author [option description]...
|
|
#
|
|
# Generate a prettily formatted usage description of the application.
|
|
#
|
|
# name Provide the name of the application.
|
|
#
|
|
# description Provide a detailed description of the application's
|
|
# purpose and usage.
|
|
#
|
|
# option An option the application can take as argument.
|
|
#
|
|
# description A description of the effect of the preceding option.
|
|
#
|
|
showHelp() {
|
|
|
|
# Parse the options.
|
|
local appName=$1; shift
|
|
local appDesc=${1//+([[:space:]])/ }; shift
|
|
local appAuthor=$1; shift
|
|
local cols=$(tput cols)
|
|
(( cols = ${cols:-80} - 10 ))
|
|
|
|
# Figure out what FD to use for our messages.
|
|
[[ -t 1 ]]; local fd=$(( $? + 1 ))
|
|
|
|
# Print out the help header.
|
|
printf "$reset$bold\n" >&$fd
|
|
printf "\t\t%s\n" "$appName" >&$fd
|
|
printf "$reset\n" >&$fd
|
|
printf "%s\n" "$appDesc" | fmt -w "$cols" | sed $'s/^/\t/' >&$fd
|
|
printf "\t $reset$bold~ $reset$bold%s\n" "$appAuthor" >&$fd
|
|
printf "$reset\n" >&$fd
|
|
|
|
# Print out the application options and columnize them.
|
|
while (( $# )); do
|
|
local optName=$1; shift
|
|
local optDesc=$1; shift
|
|
printf " %s\t" "$optName"
|
|
printf "%s\n" "${optDesc//+( )/ }" | fmt -w "$cols" | sed $'1!s/^/ \t/'
|
|
printf "\n"
|
|
done | column -t -s $'\t' \
|
|
| sed "s/^\( [^ ]*\)/$bold$green\1$reset/" >&$fd
|
|
printf "\n" >&$fd
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Quote _____________________________________________________________|
|
|
#
|
|
# shquote [-e] [argument...]
|
|
#
|
|
# Shell-quote the arguments to make them safe for injection into bash code.
|
|
#
|
|
# The result is bash code that represents a series of words, where each
|
|
# word is a literal string argument. By default, quoting happens using
|
|
# single-quotes.
|
|
#
|
|
# -e Use backslashes rather than single quotes.
|
|
# -d Use double-quotes rather than single quotes (does NOT disable expansions!).
|
|
# -a Normally, shquote doesn't quote arguments that don't need it. This forces all arguments to be quoted.
|
|
#
|
|
shquote() {
|
|
|
|
# Initialize the defaults.
|
|
local arg escape=0 sq="'\\''" dq='\"' quotedArgs=() type=single always=0
|
|
|
|
# Parse the options.
|
|
while [[ $1 = -* ]]; do
|
|
case $1 in
|
|
-e) type=escape ;;
|
|
-d) type=double ;;
|
|
-a) always=1 ;;
|
|
--) shift; break ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Print out each argument, quoting it properly.
|
|
for arg; do
|
|
(( ! always )) && [[ $arg = "$(printf %q "$arg")" ]] && quotedArgs+=("$arg") && continue
|
|
|
|
case "$type" in
|
|
escape)
|
|
quotedArgs+=("$(printf "%q" "$arg")") ;;
|
|
single)
|
|
arg=${arg//"'"/$sq}
|
|
quotedArgs+=("$(printf "'%s'" "$arg")") ;;
|
|
double)
|
|
arg=${arg//'"'/$dq}
|
|
quotedArgs+=("$(printf '"%s"' "$arg")") ;;
|
|
esac
|
|
done
|
|
|
|
printf '%s\n' "$(IFS=' '; echo "${quotedArgs[*]}")"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ ReQuote __________________________________________________________|
|
|
#
|
|
# requote [string]
|
|
#
|
|
# Escape the argument string to make it safe for injection into a regex.
|
|
#
|
|
# The result is a regular expression that matches the literal argument
|
|
# string.
|
|
#
|
|
requote() {
|
|
|
|
# Initialize the defaults.
|
|
local char
|
|
|
|
printf '%s' "$1" | while IFS= read -r -d '' -n1 char; do
|
|
printf '[%s]' "$char"
|
|
done
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Shorten ___________________________________________________________|
|
|
#
|
|
# shorten [-p pwd] path [suffix]...
|
|
#
|
|
# Shorten an absolute path for pretty printing.
|
|
# Paths are shortened by replacing the homedir by ~, making it relative and
|
|
# cutting off given suffixes from the end.
|
|
#
|
|
# -p Use the given pathname as the base for relative filenames instead of PWD.
|
|
# path The path string to shorten.
|
|
# suffix Suffix strings that must be cut off from the end.
|
|
# Only the first suffix string matched will be cut off.
|
|
#
|
|
shorten() {
|
|
|
|
# Parse the options.
|
|
local suffix path pwd=$PWD
|
|
[[ $1 = -p ]] && { pwd=$2; shift 2; }
|
|
path=$1; shift
|
|
|
|
# Make path absolute.
|
|
[[ $path = /* ]] || path=$PWD/$path
|
|
|
|
# If the path denotes something that exists; it's easy.
|
|
if [[ -d $path ]]
|
|
then path=$(cd "$path"; printf "%s" "$PWD")
|
|
elif [[ -d ${path%/*} ]]
|
|
then path=$(cd "${path%/*}"; printf "%s" "$PWD/${path##*/}")
|
|
|
|
# If not, we'll try readlink -m.
|
|
elif readlink -m / >/dev/null 2>&1; then
|
|
path=$(readlink -m "$path")
|
|
|
|
# If we don't have that - unleash the sed(1) madness.
|
|
else
|
|
local oldpath=/
|
|
while [[ $oldpath != $path ]]; do
|
|
oldpath=$path
|
|
path=$(sed -e 's,///*,/,g' -e 's,\(^\|/\)\./,\1,g' -e 's,\(^\|/\)[^/]*/\.\.\($\|/\),\1,g' <<< "$path")
|
|
done
|
|
fi
|
|
|
|
# Replace special paths.
|
|
path=${path/#$HOME/'~'}
|
|
path=${path#$pwd/}
|
|
|
|
# Cut off suffix.
|
|
for suffix; do
|
|
[[ $path = *$suffix ]] && {
|
|
path=${path%$suffix}
|
|
break
|
|
}
|
|
done
|
|
|
|
printf "%s" "$path"
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ Up ________________________________________________________________|
|
|
#
|
|
# up .../path|num
|
|
#
|
|
# Walk the current working directory up towards root num times or until path is found.
|
|
#
|
|
# Returns 0 if the destination was reached or 1 if we hit root.
|
|
#
|
|
# Prints PWD on stdout on success.
|
|
#
|
|
up() {
|
|
local up=0
|
|
until [[ $PWD = / ]]; do
|
|
cd ../
|
|
|
|
if [[ $1 = .../* ]]; then
|
|
[[ -e ${1#.../} ]] && pwd && return
|
|
elif (( ++up == $1 )); then
|
|
pwd && return
|
|
fi
|
|
done
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ BuildArray ________________________________________________________|
|
|
#
|
|
# buildarray name terms... -- elements...
|
|
#
|
|
# Create an array by adding all the terms to it for each element, replacing {} terms by the element.
|
|
#
|
|
# name The name of the array to put the result into.
|
|
# terms The values to add to the array for each of the elements. A {} term is replaced by the current element.
|
|
# elements The elements to iterate the terms for.
|
|
#
|
|
buildarray() {
|
|
local target=$1 term terms=() element value
|
|
shift
|
|
|
|
while [[ $1 != -- ]]; do
|
|
terms+=("$1")
|
|
shift
|
|
done
|
|
shift
|
|
|
|
for element; do
|
|
for term in "${terms[@]}"; do
|
|
[[ $term = {} ]] && value="$element" || value="$term"
|
|
declare -ag "$target+=($(printf '%q' "$value"))"
|
|
done
|
|
done
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ InArray ___________________________________________________________|
|
|
#
|
|
# inArray element array
|
|
#
|
|
# Checks whether a certain element is in the given array.
|
|
#
|
|
# element The element to search the array for.
|
|
# array This is a list of elements to search through.
|
|
#
|
|
inArray() {
|
|
|
|
# Parse the options.
|
|
local element
|
|
local search=$1; shift
|
|
|
|
# Perform the search.
|
|
for element
|
|
do [[ $element = $search ]] && return 0; done
|
|
return 1
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ HideDebug _________________________________________________________|
|
|
#
|
|
# hideDebug [on|off]
|
|
#
|
|
# Toggle Bash's debugging mode off temporarily.
|
|
# To hide Bash's debugging output for a function, you should have
|
|
# hideDebug on
|
|
# as its first line, and
|
|
# hideDebug off
|
|
# as its last.
|
|
#
|
|
hideDebug() {
|
|
|
|
if [[ $1 = on ]]; then
|
|
: -- HIDING DEBUG OUTPUT ..
|
|
[[ $- != *x* ]]; bashlib_debugWasOn=$?
|
|
set +x
|
|
elif [[ $1 = off ]]; then
|
|
: -- SHOWING DEBUG OUTPUT ..
|
|
(( bashlib_debugWasOn )) && \
|
|
set -x
|
|
fi
|
|
}
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ anfunc ____________________________________________________________|
|
|
#
|
|
# anfunc [on|off]
|
|
#
|
|
# Turn on or off support for annonymous functions.
|
|
#
|
|
# WARNING: This is a hack. It turns on extdebug and causes any argument
|
|
# that matches (){code} to be replaced by a function name that if invoked
|
|
# runs code.
|
|
#
|
|
# eg.
|
|
# confirm '(){ rm "$1" }' *.txt
|
|
# # In this example, confirm() could be a function that asks confirmation
|
|
# # for each argument past the first and runs the anfunc in the first
|
|
# # argument on each confirmed argument.
|
|
#
|
|
# Don't use this. It is an academic experiment and has bugs.
|
|
#
|
|
# Bugs:
|
|
# - commands lose their exit code.
|
|
# To inhibit the real command from running, we use extdebug and
|
|
# a DEBUG trap that returns non-0. As a result, the actual return
|
|
# code is lost.
|
|
#
|
|
anfunc() {
|
|
case "$1" in
|
|
on)
|
|
shopt -s extdebug
|
|
trap _anfunc_trap DEBUG
|
|
;;
|
|
off)
|
|
trap - DEBUG
|
|
shopt -u extdebug
|
|
;;
|
|
esac
|
|
}
|
|
_anfunc_trap() {
|
|
local f w
|
|
|
|
# Perform the command parsing and handling up to its word splitting.
|
|
# This includes command substitution, quote handling, pathname expansion, etc.
|
|
declare -a words="($BASH_COMMAND)"
|
|
|
|
# Iterate the words to run in the final stage, and handle anfunc matches.
|
|
for ((w=0; w<${#words[@]}; ++w)); do
|
|
[[ ${words[w]} = '(){'*'}' ]] &&
|
|
# Declare a new function for this anfunc.
|
|
eval "_f$((++f))${words[w]}" &&
|
|
# Replace the word by the new function's name.
|
|
words[w]="_f$f"
|
|
done
|
|
|
|
# Run the command.
|
|
eval "$(printf '%q ' "${words[@]}")"
|
|
|
|
# Clean up the anfuncs.
|
|
for ((; f>0; --f)); do
|
|
unset -f "_f$f"
|
|
done
|
|
|
|
# Inhibit the real command's execution.
|
|
return 1
|
|
}
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# |__ StackTrace ________________________________________________________|
|
|
#
|
|
# stackTrace
|
|
#
|
|
# Output the current script's function execution stack.
|
|
#
|
|
stackTrace() {
|
|
# Some general debug information.
|
|
wrn " [PID : %15s] [PPID : %8s] [Main PID : %8s]" "$BASHPID" "$PPID" "$$"
|
|
wrn " [Level : %15s] [Subshells : %8s] [Runtime : %7ss]" "$SHLVL" "$BASH_SUBSHELL" "$SECONDS"
|
|
wrn " [Locale : %15s] [IFS : %8s]" "${LC_ALL:-${LC_COLLATE:-${LANG:-C}}}" "$(printf %q "$IFS")"
|
|
wrn " Dir Stack : %s" "${DIRSTACK[*]}"
|
|
wrn " Shell : %s v%s" "$BASH" "$BASH_VERSION"
|
|
wrn " Shell Opts : %s" "${SHELLOPTS//:/, }"
|
|
wrn " Bash Opts : %s" "${BASHOPTS//:/, }"
|
|
wrn " Functions :"
|
|
|
|
|
|
# Search through the map.
|
|
local arg=0
|
|
for stack in "${!FUNCNAME[@]}"; do
|
|
(( stack+1 >= ${#BASH_SOURCE[@]} )) && break
|
|
|
|
func=${FUNCNAME[stack]}
|
|
line=${BASH_LINENO[stack]}
|
|
file=${BASH_SOURCE[stack+1]}
|
|
args=()
|
|
for (( arg=0, s=0; s <= stack; ++s )); do
|
|
for (( sarg=0; sarg < ${BASH_ARGC[s]:-0}; ++sarg, ++arg )); do
|
|
(( s == stack )) && args[${BASH_ARGC[s]} - sarg]=${BASH_ARGV[arg]}
|
|
done
|
|
done
|
|
wrn '%40s:%-3d | %s %s' "$file" "$line" "$func" "$(printf '%s ' "$(shquote "${args[@]}")")"
|
|
done
|
|
|
|
} # _____________________________________________________________________
|
|
|
|
|
|
|
|
|
|
|
|
# ______________________________________________________________________
|
|
# | |
|
|
# | .:: ENTRY POINT ::. |
|
|
# |______________________________________________________________________|
|
|
|
|
# Make sure this file is sourced and not executed.
|
|
( return 2>/dev/null ) || {
|
|
help=$(sed -n '1,/_tocHash=/{ /^#/p; }' "$BASH_SOURCE")
|
|
if [[ $1 ]]; then
|
|
while [[ $1 ]]; do
|
|
awk "p && !/^# *[^ ]/ {exit}
|
|
p || /^# $1/ {print; p=1}" <<< "$help"
|
|
shift
|
|
done
|
|
else
|
|
echo "$help"
|
|
echo
|
|
echo "To use bashlib, copy it into your PATH and put ''source bashlib'' at the top of your script."
|
|
fi
|
|
}
|
|
|
|
:
|
|
: .:: END SOURCING ::.
|
|
: ______________________________________________________________________
|
|
:
|