Skip to content

Instantly share code, notes, and snippets.

@delannoy
Last active June 5, 2021 10:17
Show Gist options
  • Select an option

  • Save delannoy/20c75174954714ecab6f7d85d1ef753c to your computer and use it in GitHub Desktop.

Select an option

Save delannoy/20c75174954714ecab6f7d85d1ef753c to your computer and use it in GitHub Desktop.
BASH metafunction to handle assignment of postional parameters (arguments). Prints usage and breaks if required arguments are not provided. Supports optional arguments with default values.
#!/usr/bin/env bash
Args(){
# Metafunction that expects "$FUNCNAME" (name of parent function), the positional parameters passed to a parent function, an ASCII "record separator" delimiter, and the variable names for the required & optional arguments
# Variable names for optional arguments should be indicated by wrapping them in square brackets. A default value for optional arguments may be indicated by an equal sign, e.g. '[optionalVariable=defaultValue]'
# The passed positional parameters are assigned to the variable names and returned as global variables using `printf -v` (optional positional parameters may be skipped with a quoted empty string, '')
# If any required parameters are missing, printUsage() is executed (prints the expected positional parameters) and an exit code of 111 is returned
local functionName posParam varName sqRegex eqRegex red reset # [https://wiki.bash-hackers.org/commands/builtin/local]
functionName="$1"; shift # [https://wiki.bash-hackers.org/syntax/shellvars#funcname] [https://wiki.bash-hackers.org/commands/builtin/shift]
while (( "$#" > 0 )); do [[ "$1" == $'\030' ]] && { shift; break; }; posParam+=("$1"); shift; done # [https://unix.stackexchange.com/a/628543] [https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text]
while (( "$#" > 0 )); do varName+=("$1"); shift; done # [https://wiki.bash-hackers.org/scripting/posparams#loophttps://wiki.bash-hackers.org/scripting/posparams#loopss] [https://wiki.bash-hackers.org/syntax/arrays#storing_values]
sqRegex='[[]([[:print:]]+)[]]' # matches one or more 'printable' character(s) (and groups each match into an array named "$BASH_REMATCH") enclosed in square brackets [https://wiki.bash-hackers.org/syntax/shellvars#bash_rematch]
eqRegex='([[:alnum:]_]+)[=]([[:print:]]+)' # matches and groups one or more 'alphanumeric' or '_' character(s) followed by an '='. Matches and groups the following one or more 'printable' character(s).
red="$(tput setaf 01)"; reset="$(tput sgr 0 0)" # [https://wiki.bash-hackers.org/scripting/terminalcodes#colors_using_tput]
printUsage(){ printf "%s: ${red}%s\n${reset}" "usage" "${functionName} ${varName[*]}"; } # [https://wiki.bash-hackers.org/syntax/arrays#getting_values]
optVar(){
if [[ "$1" =~ ${eqRegex} ]]; then # assign default value, "${BASH_REMATCH[2]}", if '=' is matched unless a value, "${posParam[i]}", is provided
[[ -n "${posParam[i]}" ]] && printf -v "${BASH_REMATCH[1]}" "${posParam[i]}" || printf -v "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
elif [[ -n "${posParam[i]}" ]]; then printf -v "$1" "${posParam[i]}"
fi
}
for (( i=0; i<${#varName[@]}; i++ )); do
if [[ "${varName[i]}" =~ ${sqRegex} ]]; then optVar "${BASH_REMATCH[1]}";
elif [[ -n "${posParam[i]}" ]]; then printf -v "${varName[i]}" "${posParam[i]}"; # note that `printf -v` doesn't support direct assignment to array indexes for BASH<4.1 [https://wiki.bash-hackers.org/commands/builtin/printf#options]
else printUsage && return 111
fi
done
}
# Define an alias to pass "$FUNCNAME", positional parameters, and an ASCII "record separator" delimiter to Args() before passing the variable names.
# Set a trap on ERR signal to force the parent function to break if Args() returns 111.
# [https://wiki.bash-hackers.org/commands/builtin/trap] [https://stackoverflow.com/q/19091498] [https://stackoverflow.com/a/23637952/13019084] [https://stackoverflow.com/a/42954517]
alias args=$'trap "(( \$? == 111 )) && return 222" ERR; Args "${FUNCNAME}" "$@" $\'\\030\''
$ weekToDate
usage: weekToDate year weekOfYear [dayOfWeek=1]
$ weekToDate 2020 9
2020-02-24
$ weekToDate 2020 9 6
2020-02-29
$ printf "<%s> <%s> <%s>\n" "${year}" "${weekOfYear}" "${dayOfWeek}"
<2020> <9> <6>
$ musicbrainzSearch
usage: musicbrainzSearch entity query [jqFilter] [limit] [offset=0] [format=json]
$ musicbrainzSearch release-group arid:"a630b133-bcc4-4796-9a0e-685c68b1e6ab" '."release-groups"[].title' 2
Shapeshifter
Reimagined
$ musicbrainzSearch release-group arid:"a630b133-bcc4-4796-9a0e-685c68b1e6ab" '."release-groups"[].title' '' 10
Language
Clairvoyant
Our Bones
Exoplanet
$ echo "${entity}" "${query}" "${jqFilter}" "${limit}" "${offset}" "${format}"

#!/usr/bin/env bash
weekToDate(){
# [https://en.wikipedia.org/wiki/ISO_week_date#Calculating_an_ordinal_or_month_date_from_a_week_date]
args year weekOfYear [dayOfWeek=1]
local ordinalDate daysInYear daysInPreviousYear
ordinalDate=$((7*weekOfYear + ${dayOfWeek} - ($(date -d "$year-01-04" +%u) + 3)))
daysInYear=$(date -d "$year-12-31" +%j)
daysInPreviousYear=$(date -d "$((year-1))-12-31" +%j)
((ordinalDate <= 0)) && ordinalDate=$((ordinalDate + daysInPreviousYear))
((ordinalDate > daysInYear)) && ordinalDate=$((ordinalDate - daysInYear))
date -d "$year-01-01 + $((ordinalDate-1)) days" +"%Y-%m-%d"
}
musicbrainzSearch(){
local entity query jqFilter limit offset format
args entity query [jqFilter='.'] [limit] [offset=0] [format='json']
url="https://musicbrainz.org/ws/2/${entity}?query=${query}&limit=${limit}&offset=${offset}&fmt=${format}"
jq --raw-output "${jqFilter}" <(curl -s "${url}")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment