Last active
September 26, 2023 19:18
-
-
Save milo-minderbinder/6737217136ccfad263aab36106741e57 to your computer and use it in GitHub Desktop.
bash-scripts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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}" | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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