Last active
September 21, 2022 16:02
-
-
Save espretto/1f66ccc49f586057a7e109f3b1191cbe to your computer and use it in GitHub Desktop.
bash: snippets
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
#!/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