Skip to content

Instantly share code, notes, and snippets.

@adrolter
Last active April 24, 2017 06:20
Show Gist options
  • Save adrolter/51931cbed06c27e29192f1fa5045696e to your computer and use it in GitHub Desktop.
Save adrolter/51931cbed06c27e29192f1fa5045696e to your computer and use it in GitHub Desktop.
Gist-based Bash script app template
#!/bin/bash
# bash-app-template-v1.sh
# description: Gist-based Bash script app template
# author: Adrian Günter <adrian|gntr/me>
# grepping:
# - sections: ^### (?<section>.+) ###
# - actions: ^# ACTION: (?<action>.+)
# - a function's definition: func_name\(\)
# sections: SHELLCFG,DEPENDENCIES,CONSTANTS,FUNCTIONS,ACTIONS,ROUTINE
# actions: self-install
# notes:
# - Find spaces at the end of code lines: /^\s*[^\s#].*\s+$/
# (dis)conventions:
# - An alternate delimiter is needed when using regex constants containing URLs (as well as any
# other path patterns containing slashes), as they are not escaped. Pipes "|" are therefore
# used as perl regex delimiters ("m|<pattern>|") throughout this script
# - Use "$PNL" ("$P '\n'") in place of "echo"
# - Use "$P 'whatever\n'" in place of "echo whatever"
# - Seriously, please use printf ($PRINTF|$P). Don't define "ECHO=/usr/bin/echo". Just. use. printf.
# - Separate var declarations from command substitution assignments
# * This avoids masking the return value of the cmd substitution (unless intentionally
# ignored), as the return value of "local", "export", "declare", etc. will be
# substituted for it
# ```
# local foo; foo="$(bar)" NOT local foo="$(bar)"
# foo="$(bar)"; declare -r foo NOT declare -r foo="$(bar)" OR local -r foo="$(bar)"
# ```
# - Perl-based "grep -q" replacement w/ token matching on space-separated K/V pairs
# * This doesn't seem to be much faster than grep -q with a regex; is there any use other than
# in removing grep entirely from the dependencies? Grep seems to be only used twice in the
# script as of 20170416, so this might be worth real consideration
# ```
# $PERL -ae'$F[0]eq"COLUMN_A_VALUE"&&($F[1]eq"COLUMN_B_VALUE"&&exit 0||exit 1)'
# ```
# - Print the second column of a line if the first matches
# * If used in a pipe you will need to "set +o pipefail" temporarily using a subshell
# or set +o followed by set -o (we exit early if a match is found, causing a SIGPIPE)
# * TODO: gracefully close the handles if possible instead of causing SIGPIPE
# ```
# $PERL -ae'$F[0]eq"COLUMN_A_VALUE"&&print $F[1] and exit'
# ```
#
# indent-hint
declare -r GIST_AUTHOR='adrianguenter'
declare -r GIST_AUTHOR_EMAIL='adrian|gntr/me'
declare -r GIST_AUTHOR_NAME='Adrian Günter'
declare -r GIST_COMMIT_HASH=
declare -ri GIST_COMMIT_HASH_UNIQLEN=40
declare -r GIST_COMMIT_TIME=
declare -r GIST_FILE_NAME='bash-app-template-v1.sh'
declare -r GIST_ID='51931cbed06c27e29192f1fa5045696e'
declare -ri GIST_VERSION=
### SHELLCFG ###
set -o errexit -o nounset -o noclobber -o pipefail
umask 0027
# Path searching not allowed – all external command paths must be absolute
# shellcheck disable=SC2123
PATH=''
### DEPENDENCIES ###
# pacman -S bash binutils coreutils grep perl time util-linux
declare -r BASENAME='/usr/bin/basename'
declare -r CAT='/usr/bin/cat'
declare -r DATE='/usr/bin/date'
declare -r DIFF='/usr/bin/diff' # Optional, used as backup diff tool
declare -r DIRNAME='/usr/bin/dirname'
declare -r GETOPT='/usr/bin/getopt' # Must be modern GNU Getopt!
declare -r GIT='/usr/bin/git' # Optional, used as default diff tool
declare -r GREP='/usr/bin/grep'
declare -r INSTALL='/usr/bin/install'
declare -r LESS='/usr/bin/less' # Optional, default pager (used by diff tools)
declare -r MKFIFO='/usr/bin/mkfifo'
declare -r MKTEMP='/usr/bin/mktemp'
declare -r PERL='/usr/bin/perl'
declare -r PRINTF='/usr/bin/printf'
declare -r RM='/usr/bin/rm'
declare -r SYNC='/usr/bin/sync'
declare -r TIME='/usr/bin/time'
# Aliases
declare -r P="$PRINTF"
declare -r PNL="$P \n"
# Builtins
declare -r READ='builtin read -r'
# _printferr: printf to STDERR
# USAGE: _printferr <format> [arg]...
_printferr(){ >&2 $P "$@"; }; declare -fr _printferr
# _octdump: Print octal-escape representation of a string
# USAGE: _octdump <string>
# shellcheck disable=SC2016
_octdump(){ $PERL -e'length$ARGV[0]>0&&printf"\\%0*v3o","\\",$ARGV[0];'; }; declare -fr _octdump
# _octescape: Octal-escape specified characters in a string then print it
# USAGE: _octescape <char-list> <string>
# EXAMPLE: _octescape '.[(' 'test[(file]).name' > "test\133\050file])\056name"
# shellcheck disable=SC2016,SC2026
_octescape(){ $PERL -e'length$ARGV[0]>0||exit 2;'\
-e'my$l=sprintf"\\%0*v3o","\\",$ARGV[0];$_=$ARGV[1];s/([$l]+)/sprintf"\\%0*v3o","\\",$1/ge;print;'\
"$1" "$2"; }; declare -fr _octescape
# _E: General-purpose auto-exiting error macro
# USAGE: [NOEXIT=] _E <format> [arg]...
# EXAMPLES:
# - _E 'something %s happened!' awful
# - NOEXIT='' _E 'something %s happened!' recoverable
_E(){ _printferr '%s: %s\n' "$APP_NAME" "$($P "$@")"; [ "${NOEXIT+X}" ] || exit 1; }; declare -fr _E
# _T: Measure the amount of wall time taken by a command (retrieve by calling _T_RESULT)
# USAGE: _T <cmd> [arg]...
# NOTE: Double-quote the subcommand for subshells, e.g. _T sh -c '"sleep 1;sleep 2"'
# shellcheck disable=2034
declare -g _T_RESULT
_T(){ declare o; o="$($MKTEMP -u)"; $MKFIFO "$o"; $TIME -f'%e' -o>($CAT>"$o") "$@";
$READ _T_RESULT <"$o"; $RM "$o"; }; declare -fr _T
# _V: Return 0 only if verbosity is greater than or equal to $1. If $1 is less than or equal to 0,
# return 0 only if verbosity is equal to 0 (supports checking for --quiet mode with "_V 0"
# instead of "! _V 1")
# shellcheck disable=SC2015
_V(){ [ "$1" -le 0 ] && { [ "${OPT[V]}" -eq 0 ]; return $?; } || [ "${OPT[V]}" -ge "$1" ];
}; declare -fr _V
# _confirm: Return 0 only if Y is entered
# USAGE: _confirm <question> [default<Y|N>]
# EXAMPLE: _confirm 'Continue?' N || exit
# Adapted from https://gist.github.com/davejamesmiller/1965569
# shellcheck disable=SC2015
_confirm(){ [ -t 0 ] || _E 'not interactive'; declare p='y/n' d='' r=''
if [ "${2:-}" = Y ]; then p='Y/n';d=Y; elif [ "${2:-}" = N ]; then p='y/N';d=N; fi
while :; do $P '%s [%s] ' "$1" "$p"; $READ r; r="${r^^}";
[ -z "$r" ] && r=$d; [ "$r" = Y ] && return 0 || { [ "$r" = N ] && return 1; }; done
};declare -fr _confirm
# _checkdep: Error if any of the listed paths don't exist
# USAGE: _checkdep <path>...
# EXAMPLE: _checkdep $GREP $LESS
_checkdep(){ for d in "$@"; do [ -e "$d" ] || _E\
'not available due to missing dependency "%s"' "$d"; done }; declare -fr _checkdep
# _str_lazyeq: Return 0 only if string $1 matches the beginning, or the entirety, of string $2
# USAGE: _str_lazyeq <1> <2>
# EXAMPLES:
# - _str_lazyeq foo foobar # Return value is 0
# - _str_lazyeq foobar foobar # Return value is 0
# - _str_lazyeq foobared foobar # Return value is NOT 0
# - _str_lazyeq bar foobar # Return value is NOT 0
_str_lazyeq(){ [ -n "$1" ] && [ "$1" = "${2:0:${#1}}" ]; }; declare -fr _str_lazyeq
### CONSTANTS ###
# We can't use readlink to resolve fd 1 because in a subshell it gets a pipe
TTY= ; [ -t 1 ] && TTY=/dev/fd/1; declare -r TTY
declare -ra ACTIONS=('self-install')
declare -r APP_BASENAME="${0##*/}"
declare -r RE_ESCAPE_CHARS='.^$*+?()[{\|'
# TODO: Fix me - poor substitute for GitHub URL escaping (used for github/gist#file-name URL building which has '-sh' hard coded)
declare -r GIST_FILE_FORENAME="${GIST_FILE_NAME%%.*}"
# shellcheck disable=SC2034,SC2155
declare -r GIST_FILE_NAME_RE="$(_octescape "${RE_ESCAPE_CHARS}" "${GIST_FILE_NAME}")"
declare -r GIST_HTTPURL=https://gist.github.com/${GIST_ID} # This will redirect to $GIST_AUTHOR but is shorter
declare -ri GIST_HASH_LEN=40 # Safe to use unquoted
# shellcheck disable=SC2034
declare -r GIST_HASH_RE="[0-9a-f]{${GIST_HASH_LEN}}"
# shellcheck disable=SC2034
declare -r NL='
';
### FUNCTIONS ###
# __usage: Print contextualized help based on the current value of $ACTION and,
# optionally, the values of flags and options within the $OPT array
__usage() {
[ $# -ge 1 ] && NOEXIT=1 _E "${@}"
$P '\n'
case ${ACTION} in
'self_install') _printferr "$($CAT <<'EOF'
usage: %s [-v, --verbose]... self-install [--force] [--commit=<commit-ish> | --version=<revision>]
%s [[-q, --quiet] self-install --force] [--commit=<commit-ish> | --version=<revision>]
EOF
)" "${APP_BASENAME}" "${APP_BASENAME}";;
*) _printferr "$($CAT <<'EOF'
usage: %s [-q, --quiet] [-v, --verbose]... <action> [option]... [argument]...
%s --version
actions:
self-install
EOF
)" "${APP_BASENAME}" "${APP_BASENAME}" ;;
esac
$P '\n'
exit 1
}; declare -fr __usage
# __version: Print version and author info
__version() {
declare fmt_commit_time file_viewurl
fmt_commit_time="${GIST_COMMIT_TIME:+$($DATE --date=${GIST_COMMIT_TIME} -R)}"
file_viewurl="${GIST_COMMIT_HASH:+${GIST_HTTPURL}/${GIST_COMMIT_HASH}#file-${GIST_FILE_FORENAME}-sh}"
$P '%s version %d (%.*s)\nCommitted %s\n%s\n\nWritten by %s <%s> GitHub: @%s\n' \
"${APP_BASENAME}" ${GIST_VERSION} ${GIST_COMMIT_HASH_UNIQLEN:--1} "${GIST_COMMIT_HASH:-unknown}" \
"${fmt_commit_time:-at an unknown point in time}" \
"${file_viewurl:-"${GIST_HTTPURL}#file-${GIST_FILE_FORENAME}-sh"}" \
"${GIST_AUTHOR_NAME}" "${GIST_AUTHOR_EMAIL}" "${GIST_AUTHOR}"
exit 0
}
# __main: Parse the provided arguments and call the requested action
__main() {
# Parse options
OPT[V]=1
declare parg_data
parg_data=$($GETOPT -o'+qv' -l'quiet,verbose,version' -n"${APP_NAME}" -- "$@") || __usage
eval set -- "${parg_data}"; unset parg_data
while :; do
case "${1}" in
'-q'|'--quiet') OPT[V]=0; shift;; # quiet resets verbosity to 0 (defaults to 1)
'-v'|'--verbose') OPT[V]=$((OPT[V]+1)); shift;;
'--version') __version; shift;;
'--') shift; break;;
*) break;;
esac
done
# All actions require root privileges
# TODO: Make this optional
[ ${EUID} -eq 0 ] || { _printferr '%s must be run as root\n' "${APP_NAME}"; exit 1; }
# We don't declare these variables readonly at this point because its cumbersome
# from a function. Future mutability might well be beneficial anyway
_V 1 && V1OUT="${TTY:-/dev/fd/1}"
_V 2 && V2OUT="${TTY:-/dev/fd/1}"
_V 3 && V3OUT="${TTY:-/dev/fd/1}"
# Assert that first argument must not be empty
[ "${1+X}" ] || __usage 'missing action'
declare ACTION=''
for a in "${ACTIONS[@]}"; do
! _str_lazyeq "${1}" "${a}" && continue
declare -r ACTION=${a//-/_}
break
done
# Assert that first argument must map to a recognized action
[ -n "${ACTION}" ] || __usage 'unknown action "%s"' "${1}"
shift; eval '__act_"${ACTION}" "$@"'
}
### ACTIONS ###
# EXAMPLE
# ===
# Placeholders:
# - <name>: "my_action" – [a-z_] identifier of the action. Used for the
# function name, et al.
# - <arg>: "my-action" – The argument string which triggers the action
# - <pfx>: "MA" – A unique, <= 3 char, [A-Z] identifier used for OPT
# elements
#
# Template:
## ACTION: <arg>
## __act_<name>() {
## APP_NAME+=' <arg>'
## declare parg_data
## parg_data=$($GETOPT -o '' -l '' -n"${APP_NAME}" -- "$@") || __usage
## eval set -- "${parg_data}"; unset parg_data
## while :; do
## case "${1}" in
## '-f'|'--flag') OPT[<pfx>_FLAG]=1; shift;;
## '-o'|'--option') OPT[<pfx>_OPTION]="${2}"; shift 2;;
## '--') shift; break;;
## *) break;;
## esac
## done
## # Handle positional arguments
## declare -ar parg=("$@")
## # ...
## }
# ACTION: self-install
# TODO: Add optional shellcheck dependency to fail out of install if parsing or other errors are found
__act_self_install() {
APP_NAME+=' self-install'
OPT[SI_FORCE]=0
_checkdep $DIFF $LESS $GIT
# Options: Backup, install location, group, permissions, ?
declare parg_data
parg_data=$($GETOPT -o 'l:' -l 'location:,commit:,version:,force' -n"${APP_NAME}" -- "$@") || __usage
eval set -- "${parg_data}"; unset parg_data
while :; do
case "${1}" in
'-l'|'--location') OPT[SI_LOCATION]="${2}"; shift 2;;
'--commit') OPT[SI_COMMIT]="${2}"; shift 2;;
'--version') OPT[SI_VERSION]="${2}"; shift 2;;
'--force') OPT[SI_FORCE]=1; shift;;
'--') shift; break;;
*) break;;
esac
done
# Handle positional arguments
# shellcheck disable=SC2034
declare -ar parg=("$@")
# --quiet requires --force, and therefore also implies --force (OPT[SI_FORCE] check unnecessary when using _V 0)
_V 0 && [ "${OPT[SI_FORCE]}" -ne 1 ] && __usage \
'-q|--quiet requires --force'
# Use --quiet to bypass the TTY check (dangerous!)
_V 1 && [ -z "${TTY}" ] && _E 'the process must have a controlling TTY for this action'
# Declare locals
declare commitish gittemp install_dir install_name install_path \
{,p_}commit_hash {,p_}commit_hash_short {,p_}commit_time
declare -i {,p_}commit_hash_uniqlen {,p_}version
# Build install_path, using --location if provided
install_dir=/usr/local/bin # Default
install_name="${APP_BASENAME}" # Default
install_path="${install_dir}/${install_name}" # Default
if [ "${OPT[SI_LOCATION]+X}" ]; then
[ -n "${OPT[SI_LOCATION]}" ] || __usage $'argument to \'--location\' requires a value'
install_path="${OPT[SI_LOCATION]}"
# If given a directory, we use it and append the default value of $install_name to built path
if [ -d "${install_path}" ]; then
install_dir="${install_path}"
install_path+="/${install_name}"
fi
fi
declare -r install_path
# If the path exists, we perform file type and content checks. If it doesn't exist, we
# make sure its directory exists
if [ -e "${install_path}" ]; then
[ -f "${install_path}" ] || _E \
'location "%s" exists, but is not a directory or file' "${install_path}"
install_dir="$($DIRNAME "${install_path}")"
install_name="$($BASENAME "${install_path}")"
declare line name value
declare -i valid_gist_id=0 valid_gist_file_name=0
while $READ line; do
IFS=$'\2' $READ name value <<<"${line}"
# shellcheck disable=SC2034
case "${name}" in
'GIST_ID') if [ -n "${value}" ] && [ "${value}" = "${GIST_ID}" ];
then valid_gist_id=1; else break; fi;;
'GIST_FILE_NAME') if [ -n "${value}" ] && [ "${value}" = "${GIST_FILE_NAME}" ];
then valid_gist_file_name=1; else break; fi;;
'GIST_COMMIT_HASH') p_commit_hash="${value}";;
'GIST_COMMIT_HASH_UNIQLEN') p_commit_hash_uniqlen="${value}";;
'GIST_COMMIT_TIME') p_commit_time="${value}";;
'GIST_VERSION') p_version="${value}";;
esac
done < <($PERL -ne"$($CAT <<'EOF'
if ($.==1 && not m%^#!(/usr)?/bin/bash$%) { exit $.; } # Exit if shebang test fails
elsif (/^\s*\#/) { ; } # Noop on comment lines
elsif (/^\s*declare\s+-ri?\s+(GIST_[A-Z_]+)=(?:'([^']*)'|([^'\s]?[^\s]*))/) {
printf "%s\2%s\n", $1, (length $2 > 0 ? $2 : $3); } # Print pairs separated by 0x02
else { exit $.; } # Exit on first non-comment line that doesn't match the regex
EOF
)" "${install_path}")
if [ ${valid_gist_id} -ne 1 ] || [ ${valid_gist_file_name} -ne 1 ]; then
_E 'file at "%s" failed validation, refusing to overwrite' "${install_path}"
fi
unset line name value valid_gist_id valid_gist_file_name
else
install_dir="$($DIRNAME "${install_path}")"
install_name="$($BASENAME "${install_path}")"
[ -d "${install_dir}" ] || _E 'all directories in location "%s" must exist' "${install_path}"
fi
# Set the default commit-ish ref string, or validate and use --commit option if exists
commitish=FETCH_HEAD # Default
if [ "${OPT[SI_COMMIT]+X}" ]; then
[ "${OPT[SI_VERSION]+X}" ] \
&& __usage 'options --commit and --version are mutually exclusive'
$GREP -qPe'^[0-9a-f]{5,'${GIST_HASH_LEN}'}$' <<<"${OPT[SI_COMMIT]}"\
|| __usage 'commit must be 5-%d characters of [0-9a-f]' ${GIST_HASH_LEN}
commitish="${OPT[SI_COMMIT]}"
fi
# Sanitize and validate --version option if exists
# Very important as $version gets implicit $(()) wrapped around assignments!
if [ "${OPT[SI_VERSION]+X}" ]; then
version="${OPT[SI_VERSION]//[!0-9]/}"
[ ${version} -lt 1 ] && __usage \
'version must be a number greater than zero'
fi
# Fetch the repository
gittemp="$($MKTEMP -d)"
>"$V2OUT" $GIT -C "${gittemp}" init
>"$V2OUT" $GIT -C "${gittemp}" remote add origin "${GIST_HTTPURL}.git"
&>"$V2OUT" $GIT -C "${gittemp}" fetch --all
# Do a version lookup if set, otherwise verify and resolve commitish
if [ ${version+X} ]; then
commit_hash="$($GIT -C "${gittemp}" rev-list --reverse FETCH_HEAD\
| $PERL -ne'$.=='${version}'&&print and exit')"
[ -n "${commit_hash}" ] || _E 'failed to find version "%d"' "${version}"
else
commit_hash="$($GIT -C "${gittemp}" rev-parse --verify --quiet "${commitish}^{commit}")"
[ -n "${commit_hash}" ] || _E \
'failed to find a unique commit matching "%s"' "${commitish}"
version="$($GIT -C "${gittemp}" rev-list --count "${commit_hash}")"
fi
# Get shortest possible SHA of current revision
commit_hash_short="$($GIT -C "${gittemp}" rev-parse --short=5 "${commit_hash}")"
commit_hash_uniqlen=${#commit_hash_short}
# - Should we use author (%a?) instead of committer (%c?) dates?
# - For collecting more info this should be a read call with proc sub and more fmt specs in the format string
commit_time="$($GIT -C "${gittemp}" log -1 --format="%cI" "${commit_hash}")"
# Print a message with the correct verbage "Installing/upgrading/downgrading ..."
if [ ! -e "${install_path}" ] || [ ! "${p_version+X}" ] || [ "${p_version}" -le 0 ]; then
_V 1 && $P 'Installing %s (%s), committed %s\n' \
"${version}" "${commit_hash_short}" "$($DATE --date="${commit_time}" -R)"
elif [ "${p_version}" -eq "${version}" ]; then
# Abort if not --force
if [ "${OPT[SI_FORCE]}" -ne 1 ]; then
_V 1 && NOEXIT=1 _E 'version %s (%s) is already installed' \
"${version}" "${commit_hash_short}"
exit 2
fi
_V 1 && $P 'Reinstalling %s (%s), committed %s\n' \
"${version}" "${commit_hash_short}" "$($DATE --date="${commit_time}" -R)"
else
declare verb=
[ "${p_version}" -lt "${version}" ] && verb=Upgrading || verb=Downgrading
_V 1 && $P '%s %s from %s (%.*s) to %s (%s), committed %s\n' \
${verb} "${install_path}" "${p_version}" "${p_commit_hash_uniqlen:--1}" \
"${p_commit_hash:-unknown}" "${version}" "${commit_hash_short}" \
"$($DATE --date="${commit_time}" -R)"
# Confirm downgrade if not --quiet
if _V 1 && [ "${p_version}" -gt "${version}" ] && ($PNL; ! _confirm \
'Downgrading can break self-install and other features. Are you sure?' N)
then
_printferr 'Aborting.\n'
exit 5
fi
unset verb
fi
# Checkout the revision at $commit_hash
>"$V2OUT" $GIT -C "${gittemp}" reset --hard "${commit_hash}"
# Write version info into the new script for self-install and version printing
$PERL -i -pe"$($CAT <<EOF
BEGIN { my \$changes = 0; }; next if \$changes == 4;
if ($. == 1 && not m%^\#!(/usr)?/bin/bash$%) { exit 255; } # Exit if shebang test fails
elsif (/^\s*\#/) { ; } # Noop on comment lines
# Fail on first non-comment or GIST_VAR declaration if changes hasn't reached its target value
elsif (not /^\s*declare\s+-ri?\s+GIST_[A-Z_]+=/) { exit 1; }
elsif (s/^\s*declare\s+-r\s+GIST_COMMIT_HASH=\K.*/'${commit_hash}'/) { \$changes++; }
elsif (s/^\s*declare\s+-ri\s+GIST_COMMIT_HASH_UNIQLEN=\K.*/${commit_hash_uniqlen}/) { \$changes++; }
elsif (s/^\s*declare\s+-r\s+GIST_COMMIT_TIME=\K.*/'${commit_time}'/) { \$changes++; }
elsif (s/^\s*declare\s+-ri\s+GIST_VERSION=\K.*/${version}/) { \$changes++; }
# Implicit "else { ; }" – only ignored GIST_VAR declarations fall through to here
EOF
)" "${gittemp}/${GIST_FILE_NAME}" || _E 'failed writing version info'
# Present a diff of the changes between the previous and next versions
if [ -e "${install_path}" ]; then
# -c core.filemode=false doesn't seem to work with --no-index, so we have to do this
declare difftemp; difftemp="$($MKTEMP)"
$INSTALL -m0640 "${install_path}" "${difftemp}"
if _V 1 && ! $GIT diff --no-index --quiet -- "${difftemp}" "${gittemp}/${GIST_FILE_NAME}" \
&& ($PNL; _confirm 'View a unified diff of the previous and next version (RECOMMENDED)?' Y)
then
PATH="${LESS%/*}" $GIT diff --no-index -- "${difftemp}" "${gittemp}/${GIST_FILE_NAME}"||:
fi
$RM "${difftemp}"
unset difftemp
fi
# Provide an option to abort
if _V 1 && ($PNL; ! _confirm "$($P \
'Install version %d to "%s"?' "${version}" "${install_path}")" N)
then
_printferr 'Aborting.\n'
exit 5
fi
# Install the script and set permissions
$INSTALL -m0750 -gwheel "${gittemp}/${GIST_FILE_NAME}" "${install_path}"
# TODO: We really need a trap to clean up this directory as there are multiple failure conditions leading to early exits above
# A way to add paths to the cleanup routine right after creation would be helpful (push onto a global array?)
$RM -rf "${gittemp}"
$SYNC "${install_path}"
}
### RUN ###
declare -A OPT=()
ACTION=''
APP_NAME="${APP_BASENAME}"
# shellcheck disable=SC2034
V1OUT=/dev/null
V2OUT=/dev/null
# shellcheck disable=SC2034
V3OUT=/dev/null
__main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment