Skip to content

Instantly share code, notes, and snippets.

@milo-minderbinder
Last active September 26, 2023 19:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save milo-minderbinder/6737217136ccfad263aab36106741e57 to your computer and use it in GitHub Desktop.
Save milo-minderbinder/6737217136ccfad263aab36106741e57 to your computer and use it in GitHub Desktop.
bash-scripts
#!/usr/bin/env bash
set -o errexit -o errtrace -o noclobber -o nounset -o pipefail
trap 'e=$?; if [ "$e" -ne "0" ]; then printf "LINE %s: exit %s <- %s%s\\n" "$BASH_LINENO" "$e" "${BASH_COMMAND}" "$(printf " <- %s" "${FUNCNAME[@]:-main}")" 1>&2; fi' EXIT
PROGNAME="${0##*/}"
log_verbose() {
if [ "${verbose:-n}" == "y" ]; then
log_info "$@"
fi
}
log_debug() {
(>&2 printf '%sDEBUG%s: %s\n' "$(tput setaf 7)" "$(tput sgr0)" "$@")
}
log_info() {
(>&2 printf '%sINFO%s: %s\n' "$(tput setaf 2)" "$(tput sgr0)" "$@")
}
log_warn() {
(>&2 printf '%sWARNING%s: %s\n' "$(tput setaf 3)" "$(tput sgr0)" "$@")
}
log_error() {
(>&2 printf '%sERROR%s: %s\n' "$(tput setaf 1)" "$(tput sgr0)" "$@")
}
get_context() {
local line
local subroutine
local filename
line="$1"
subroutine='call'
if [ "$#" -eq "2" ]; then
filename="$2"
elif [ "$#" -eq "3" ]; then
subroutine="$2"
filename="$3"
else
log_error 'incorrect number of arguments!'
exit 1
fi
printf '%s%s on line %d of %s:%s\n' "$(tput setaf 1)" "$subroutine" "$line" "$filename" "$(tput sgr0)"
awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L="$line" "$filename"
}
log_stack_trace() {
local last_exit=$?
local max_depth
local depth
if [ "$last_exit" -ne "0" ]; then
depth=0
max_depth="${1:-3}"
while [ "$depth" -le "$max_depth" ] && caller "$depth" 1>/dev/null 2>&1 ; do
log_error "$(get_context $(caller "$depth"))"
depth+=1
done
log_error "$(printf '%s(%d) -> exit %d\n' $(caller 0 | awk '{ print $3,$1 }') "$last_exit")"
fi
}
append_trap () {
local trap_cmd
local trap_sig
local old_trap_cmd
trap_cmd="$1"
trap_sig="$2"
old_trap_cmd="$(trap -p "$trap_sig" | sed -E -e "s/^[^'\"]*['\"]//" -e "s/['\"][[:space:]]*${trap_sig}\$//")"
if [[ -n "$old_trap_cmd" ]]; then
trap_cmd="$old_trap_cmd; $trap_cmd"
fi
trap "$trap_cmd" "$trap_sig"
}
trap 'log_stack_trace' EXIT
get_script_dir() {
## resolve the directory of the given script
# example:
# get_script_dir "${BASH_SOURCE[0]}"
SOURCE="${1}"
#SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$SCRIPTDIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
printf '%s\n' "${SCRIPTDIR}"
}
contains_value() {
local value
value="$1"
shift
for arg in "$@"; do
if [ "$value" == "$arg" ]; then
return 0
fi
done
return 1
}
ltrim_ws() {
local input
local smallest_lws
input="$(cat "${1:--}")"
smallest_lws=$(printf '%s\n' "$input" | sed -e '/^[[:space:]]*$/d' -e 's/[^[:space:]].*$//' | sort | head -n 1 | wc -m)
printf '%s\n' "$input" | cut -c ${smallest_lws}-
}
get_color() {
local color
case "$1" in
bla*) color=0;;
r*) color=1;;
g*) color=2;;
y*) color=3;;
blu*) color=4;;
m*|p*) color=5;;
c*) color=6;;
w*) color=7;;
*)
1>&2 printf '%s[ERROR]%s: unknown color name: %s\n' "$(tput setaf 1)" "$(tput sgr0)" "$1"
return 1
;;
esac
printf '%d\n' "$color"
}
hl() {
local fg
local bg
local text
if [ "$#" -lt "2" ] || [ "$#" -gt "3" ]; then
cat <<EOF | ltrim_ws 1>&2
$(tput setaf 1)[ERROR]$(tput sgr0): incorrect # of arguments
USAGE
${FUNCNAME[@]:-main} FG_COLOR [BG_COLOR] TEXT
EXAMPLE
${FUNCNAME[@]:-main} red 'this text is red'
EOF
return 1
fi
fg="$(tput setaf "$(get_color "$1")")"
if [ "$#" -eq "3" ]; then
bg="$(tput setab "$(get_color "$2")")"
shift
else
bg=""
fi
text="$2"
printf '%s%s%s\n' "${fg}${bg:-}" "$text" "$(tput sgr0)"
}
xml_format() { cat "${1:--}" | sed -E 's/^[[:space:]]+//' | tr -d '\n' | XMLLINT_INDENT=$'\t' xmllint --format - ; };
add_to_path() {
local path_entries
path_entries="$(printf '%s\n' "${PATH}" | tr ':' '\n')"
for path_entry in "$@"; do
if ! printf '%s\n' "${path_entries}" | grep --fixed-strings --line-regexp --quiet "${path_entry}"; then
if [ ! -d "${path_entry}" ]; then
printf '%sWARNING:%s Adding non-directory path to PATH: %s\n' "$(tput setaf 1)" "$(tput sgr0)" "${path_entry}" 1>&2
else
printf '%sINFO:%s Adding to PATH: %s\n' "$(tput setaf 2)" "$(tput sgr0)" "${path_entry}" 1>&2
fi
export PATH="${path_entry}:${PATH}"
fi
done
}
update_path() {
local formula_name
if [[ $# -eq 1 ]]; then
formula_name="$1"
else
formula_name="all"
fi
if [ "$(uname)" == "Darwin" ]; then
if [ "${formula_name}" == "all" ] || [ "${formula_name}" == "grep" ]; then
add_to_path "$(brew --prefix 'grep')/libexec/gnubin"
fi
if [ "${formula_name}" == "all" ] || [ "${formula_name}" == "gnu-getopt" ]; then
add_to_path "$(brew --prefix 'gnu-getopt')/bin"
fi
if [ "${formula_name}" == "all" ] || [ "${formula_name}" == "coreutils" ]; then
add_to_path "$(brew --prefix 'coreutils')/libexec/gnubin"
fi
fi
log_verbose "updated PATH: ${PATH}"
printf '%s\n' "${PATH}"
}
#!/usr/bin/env bash
verbose="${verbose:-n}"
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$SCRIPTDIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
source "${SCRIPTDIR}/common.bash"
export PATH="$(update_path 'gnu-getopt')"
if command -v apt-get 1> /dev/null && ! command -v xmllint 1> /dev/null; then
log_warn 'installing missing dependencies'
DEBIAN_FRONTEND=noninteractive apt-get -q -y update && apt-get -q -y install \
libxml2-utils
fi
find_xml_attr_vals() {
OPTIONS=e:a:
LONGOPTS=element-xpath:,attr-name:
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "${0##*/}" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
exit 2
fi
eval set -- "$PARSED"
local element_xpath
local attr_name
local input
local xml_data
while true; do
case "$1" in
-e|--element-xpath)
element_xpath="$2"
shift 2
;;
-a|--attr-name)
attr_name="$2"
shift 2
;;
--)
shift
break
;;
*)
echo "Programming error"
exit 2
;;
esac
done
if [ "${element_xpath:-}" == "" ]; then
log_error "find_xml_attr_vals --element-xpath option is requred"
exit 123
fi
if [ "${attr_name:-}" == "" ]; then
log_error "find_xml_attr_vals --attr-name option is requred"
exit 123
fi
if [[ $# -ne 1 ]]; then
log_error "find_xml_attr_vals accepts one positional argument for the xml input, but got $#"
exit 123
fi
[ -f "$1" ] && input="$1" || input="-"
xml_data="$(cat "${input}" | sed 's/xmlns[[:space:]]*=/ignore=/')"
local num_matched
num_matched="$(printf '%s' "${xml_data}" | \
xmllint --xpath "count(${element_xpath}/@${attr_name})" - )"
log_verbose "found ${num_matched} attrs matching '${element_xpath}/@${attr_name}'"
local attr_val
for i in $(seq 1 "${num_matched}"); do
attr_val="$(printf '%s' "${xml_data}" | \
xmllint --xpath "${element_xpath}[${i}]/@${attr_name}" - | \
sed -E 's/.*"(.*)".*/\1/')"
#log_verbose " ${element_xpath}[${i}]/@${attr_name} = ${attr_val}"
printf '%s\n' "${attr_val}"
done
}
get_versions() {
local versions_url
local versions_xml
local versions
versions_url="$1"
log_verbose "fetching latest versions from ${versions_url}"
versions_xml="$(curl -s "${versions_url}" | sed -n '2,$p')"
versions=()
while IFS= read -r; do
versions+=("${REPLY}")
done < <(printf '%s' "${versions_xml}" | \
find_xml_attr_vals --element-xpath "//a" --attr-name "href" - | \
grep -Ev '\-|\.\.' | \
sed 's/\/$//' | \
sort -rV)
log_verbose "${#versions[@]} parsed versions:$(printf '\n\t%s' "${versions[@]}")"
printf '%s\n' "${versions[@]}"
}
get_latest_version() {
local versions_url
local major_version
local versions
local latest_version
versions_url="$1"
if [[ $# -eq 2 ]]; then
major_version="$2"
fi
versions="$(get_versions "${versions_url}" | grep -E "^${major_version:-[0-9]+}\." | sort -rV)" || \
(log_error "no versions ${major_version:+matching ${major_version}.X.X }found"; exit 2)
latest_version="$(printf '%s' "${versions}" | head -n 1)"
log_verbose "latest version: ${latest_version}"
printf '%s' "${latest_version}"
}
get_latest_version "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment