Skip to content

Instantly share code, notes, and snippets.

@jaymecd
Last active April 6, 2022 02:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaymecd/ca05ad4e36f8816cd0c868f53c96962e to your computer and use it in GitHub Desktop.
Save jaymecd/ca05ad4e36f8816cd0c868f53c96962e to your computer and use it in GitHub Desktop.
Bash CLI skeleton + debug reader
#!/usr/bin/env bash
#/ Sample command to perform update and query it's status
#/
#/ Usage: <SELF_NAME> [-h|--help] <command>
#/
#/ Available commands:
#/ one - perform some update, idempotent
#/ usage: <SELF_NAME> one <arg1> [arg2]
#/ two - query status of update
#/ usage: <SELF_NAME> two <arg1>
#/
#/ Environment variables:
#/ DEBUG=1 - troubleshoot logic using config.debug file
#/ FORCE=1 - enforce action, disables idempotence
set -eu -o pipefail -o errtrace -o functrace
test "${BASH_VERSINFO[0]}" -lt 4 || shopt -s inherit_errexit nullglob compat"${BASH_COMPAT=42}"
# === prereq === #
: "${DEBUG:=}"
: "${FORCE:=}"
readonly DEBUG FORCE
IS_SOURCED=$(test "${BASH_SOURCE[0]}" == "${0}" || echo 1)
SELF_FILE=$(\grep -E '^(\.|\/)' <<< "${BASH_SOURCE[0]}" || echo "./${BASH_SOURCE[0]}")
WORK_DIR=$(\pwd -P)
DEBUG_FILE="${WORK_DIR}/${SELF_FILE##*/}.dbg"
readonly IS_SOURCED SELF_FILE WORK_DIR DEBUG_FILE
# === func === #
main() {
test -z "${DEBUG}" || {
enable_debug "${DEBUG_FILE}"
: "Sourced=${IS_SOURCED:-0}"
: "WorkDir=${WORK_DIR}"
stderr "# sending debug stream into ${DEBUG_FILE##*/} file in current directory ..."
}
! \grep -Eq "(^|\s)--help|-h(\s|$)" <<< "$*" || usage
check_requirements
declare cmd="${1:-}"
shift || :
case "${cmd,,}" in
one) cmd_one "$@" ;;
two) cmd_two "$@" ;;
*) usage "invalid command specified" ;;
esac
}
cmd_one() {
declare arg1="${1:-}"
declare arg2="${2:-default2}"
test -n "${arg1}" || fatal "arg1 is empty"
echo "running sub command: one(${arg1}, ${arg2})"
}
cmd_two() {
declare arg1="${1:-}"
test -n "${arg1}" || fatal "arg1 is empty"
echo "running sub command: two(${arg1})"
}
enable_debug() {
declare file="$1"
exec 19> "${file}"
export BASH_XTRACEFD=19
shopt -s extdebug
declare -g DEBUG_START_MS
DEBUG_START_MS=$(unix_milliseconds)
readonly DEBUG_START_MS
export PS4='+ [$(( $(unix_milliseconds) - $DEBUG_START_MS ))ms] at ${BASH_SOURCE[0]}:${LINENO} in ${FUNCNAME[0]:-main}${FUNCNAME[0]:+()} [pid:${BASHPID},sub:${BASH_SUBSHELL},uid:${EUID}] '
set -x
: "OS=${OSTYPE}"
: "Bash=${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}.${BASH_VERSINFO[2]}"
: "BashOpts=${BASHOPTS}"
}
check_requirements() {
printf '4.1\n%s\n' "${BASH_VERSION}" | \sort -CV || fatal "bash 4.1+ is required, got ${BASH_VERSION} ..."
for cmd in jq perl sed; do
command -v "${cmd}" &>/dev/null || fatal "command '${cmd}' is missing"
done
declare version
version=$(\jq --version 2>&1 | \awk -F'[ -]' 'OFS="-" {$1="";gsub("^-+","",$0)}1')
printf '1.6\n%s' "${version}" | \sort -CV || fatal "\jq 1.6+ is required, got ${version} ..."
}
fatal() {
printf -- "Error: %s\n" "${@}" >&2
exit 1
}
stderr() {
printf -- "%s\n" "${@}" >&2
}
unix_milliseconds() {
test "${BASH_VERSINFO[0]}" -lt 5 || {
date +%s%3N
return
}
echo $(( "${EPOCHREALTIME/./}" / 1000 ))
}
usage() {
declare err_msg="${1:-}"
\grep '^#/' "${SELF_FILE}" | \cut -c4- | \sed -e "s/<SELF_NAME>/${SELF_FILE##*/}/"
test -n "${err_msg}" || exit 0
echo
fatal "${err_msg}"
}
readlinkf() {
\perl -MCwd -l -e 'print Cwd::abs_path shift' "$1"
}
indent() {
\sed -e 's/^/ /'
}
# === main === #
test -n "${IS_SOURCED}" || {
main "$@"
exit 0
}
#!/usr/bin/env bash
set -e
fatal() {
printf -- "Error: %s\n" "${@}" >&2
exit 1
}
declare file="${1:-}"
declare line prev
test -n "${file}" || fatal 'provide filename'
test -f "${file}" || fatal 'file is missing or not readable'
while IFS= read -r line; do
if test "${line:0:1}" != "+"; then
# 2nd+ multiline
echo "${line}"
continue
fi
declare -i t1=0
declare -i t2=0
if test "${prev}" == ""; then
prev="${line}"
continue
fi
t1=$(echo "${prev}" | sed -r -e 's,.*^\++\s+\[([0-9]+)ms\].*,\1,')
t2=$(echo "${line}" | sed -r -e 's,.*^\++\s+\[([0-9]+)ms\].*,\1,')
elapsed=$((t2-t1))
echo "${prev}" | sed -r -e "s/\[([0-9]+ms)\]/[\1+${elapsed}ms]/"
prev="${line}"
done < "$file"
if test "${prev}" != ""; then
echo "${prev}" | sed -r -e "s/\[([0-9]+ms)\]/[\1+0ms]/"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment