Skip to content

Instantly share code, notes, and snippets.

@espretto
Last active September 21, 2022 16:02
Show Gist options
  • Save espretto/1f66ccc49f586057a7e109f3b1191cbe to your computer and use it in GitHub Desktop.
Save espretto/1f66ccc49f586057a7e109f3b1191cbe to your computer and use it in GitHub Desktop.
bash: snippets
#!/bin/bash
# ------------------------------------------------------------------------------
# strict mode
set -euo pipefail; IFS=$'\n\t'
# ------------------------------------------------------------------------------
# notes
#
# - `[` represents the command `test`, use `[[` builtin instead.
# - `-a` and `-o` are deprecated, use `&&` and `||` instead.
# - `&&` and `||` cannot be used to construct ternary operations, use if-then-fi instead.
# - paths beginning with a dash may be misinterpreted as arguments.
# either terminate the argument list by `--` or prefix relative paths with `./`.
# - in arithmetic expressions, use `$(( ${array[$key]} ))` syntax when retrieving
# array elements to avoid code injection.
# - mind that redirections are evaluated sequentially from left-to-right:
# `command >out.log 2>&1` redirects _stdout_ first, and then _stderr_.
# - always validate `cd`
# ------------------------------------------------------------------------------
# where am i
# potentially relative script path (even if sourced)
SCR_PATH="${BASH_SOURCE[0]}"
# absolute script path
SCR_PATH=$(readlink -ne "$SCR_PATH")
# absolute script directory
SCR_DIR=${SCR_PATH%/*}
# script filename
SCR_NAME=${SCR_PATH##*/}
# script basename without extension(s)
SCR_BASE=${SCR_NAME%\.*}
SCR_BASE=${SCR_NAME%%\.*}
# create temporary directory in TMPDIR (=/tmp)
TMP_DIR=$(mktemp -d)
# create file with random portion next to the script
SESSION=$(mktemp -p "${SCR_DIR}" "session-XXXXXX")
# setup exit code trap for cleanup
cleanup () {
[[ -d "$TMP_DIR" ]] && rm -rf "$TMP_DIR"
}
trap cmd 0 1 2 3 6 13 15 # EXIT HUP INT QUIT ABORT PIPE TERM
# read single line from another command
random=$(mktemp -u XXXXXX)
read -r random < <(mktemp -u XXXXXX);
# ------------------------------------------------------------------------------
# logging
log () {
typeset loglevel="${1^^}"
# typeset -u loglevel="$1"
typeset timestamp=$(date +Iseconds)
# typeset timestamp=$(date +"%FT%T.%3NZ") # ISO_8601
printf "[${loglevel}][${timestamp}] $2\n" >&2
}
# log file format string
LOG_FILE=$(date +"./%F_%H-%M-%S.log")
# rewire stderr to pass through tee (https://stackoverflow.com/a/19279694)
exec 3>&2
exec 2> >(tee -a "$LOG_FILE" >&3)
# simple interprocess communication using named pipe
LOG_PIPE=$(mktemp -du)
mkfifo "$LOG_PIPE"
trap "rm $LOG_PIPE" TERM INT EXIT
tee -a "$LOG_FILE" < "$LOG_PIPE" >&2 &
echo hello 2> "$LOG_PIPE"
# ------------------------------------------------------------------------------
# variable expansion / string manipulation
# require variable to be set
log debug "input=${input:?}"
# create and set default value is unset
log debug "input=${input:-default}"
# replace all occurrences
${var//search/replace}
# replace first occurence
${var/search/replace}
# strip prefix
${var#prefix}
# strip prefix greedily
${var##prefix}
# strip suffix
${var%suffix}
# strip suffix greedily
${var%%suffix}
# uppercase string (alt: typeset -u text)
${text^^}
# lowercase string (alt: typeset -l text)
${text,,}
# trim left
${text##+([:space:])}
# trim right
${text%%+([:space:])}
# substring characters 5,6,7
${text:5:3}
# ------------------------------------------------------------------------------
# normalize paths
# fails if any path segment is missing
ABS_PATH=$(readlink -ne "$filepath")
# fails if last path segment is missing
ABS_PATH=$(readlink -nf "$filepath")
# never fails, prepare for file/dir creation
ABS_PATH=$(readlink -nm "$filepath")
# verify required command is available
command -v git || { log error "missing git"; exit 1 }
# user interaction
confirm () {
read -p "$1 (y/N)? " ask
[[ "$ask" == [yY] ]]
return
}
# ask user's choice
confirm "continue" || { log debug "user exit"; exit }
# quietly unzip to target directory
unzip -q -d "./target/dir/" "$filepath"
# ------------------------------------------------------------------------------
# file interaction
# read lines from input file
while IFS= read -r line; do
# skip empty lines
[[ -n "$line" ]] || continue
echo "$line"
done < $(<input.txt)
# read lines from another command avoiding subshell+buffering using FIFO
while IFS= read -r line; do
echo "$line"
done < <(find . -type f -name "*.log")
# ------------------------------------------------------------------------------
# split string
array=()
while read -rd "$delimiter" substring; do
array+=("$substring")
done < <(printf '%s%s' "$input" "$delim")
# IFS is a field terminator, not a separator
IFS=, read -ra array <<< "a,b,c,"
join () {
typeset IFS="${1}"; shift;
printf "${*}";
}
# join path segments
filepath=$(join '/' path to file)
# download file
curl -o "$filepath" "$url"
# verify md5 checksum
if ! curl "${url}.md5" | md5sum -c -; then
log error "checksum mismatch. corrupted download suspected at ${filepath}"
exit 1
fi
# ------------------------------------------------------------------------------
# read and render .properties config files
read_properties () {
typeset -n props="$1"
while IFS== read -r key value; do
props["$key"]="$value"
done
}
render_template () {
typeset -n props="$1"
typeset pattern='\{\{([^\}[:space:]]+)\}\}'
typeset line key value
while IFS= read -r line; do
while [[ "$line" =~ $pattern ]]; do
key="${BASH_REMATCH[1]}"
value="${props[$key]}"
if [[ -n "$value" ]]; then
line="${line/$BASH_REMATCH/$value}"
else
break
fi
done
echo "$line"
done
}
declare -A PROPS
read_properties PROPS < app.properties
render_template PROPS < template.conf > rendered.conf
# ------------------------------------------------------------------------------
# remote script execution
run_remote () {
typeset args script
script=$1; shift
# generate eval-safe quoted version of current argument list
printf -v args '%q ' "$@"
# pass that through on the command line to bash -s
# note that $args is parsed remotely by /bin/sh, not by bash!
ssh user@remote-addr "bash -s -- $args" < "$script"
}
# ------------------------------------------------------------------------------
# handling jar-files
unzip -d "$tmpdir" "$jarfile" "$extractpath"
jar -uvf "$jarfile" "$tmpdir/$extractpath"
rm -rf "$tmpdir"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment