Skip to content

Instantly share code, notes, and snippets.

@multun
Created April 17, 2020 17:22
Show Gist options
  • Save multun/e2ba07dbb6b4088ccf3494d804ef1b8c to your computer and use it in GitHub Desktop.
Save multun/e2ba07dbb6b4088ccf3494d804ef1b8c to your computer and use it in GitHub Desktop.
A shell library for parsing arguments
# parse --my_option into the MY_OPTION variable
# also handles positional arguments
__args_list=()
__required_args_list=()
__unexport_args_list=()
normalize_argument() {
# uppercase
local argname="${1^^}"
# replace - by _
printf '%s\n' "${argname}" | tr - _
}
assign_var() {
# printf can assign variables
printf -v "$1" '%s' "$2"
}
# public arguments get forwarded through the environment
public_optional_argument() {
local argname="$(normalize_argument "$1")"
__args_list+=( "${argname}" )
assign_var "__args_${argname}" 1
}
public_required_argument() {
local argname="$(normalize_argument "$1")"
assign_var "__args_${argname}" 1
__args_list+=( "${argname}" )
__required_args_list+=( "${argname}" )
}
optional_argument() {
local argname="$(normalize_argument "$1")"
assign_var "__args_${argname}" 1
__args_list+=( "${argname}" )
__unexport_args_list+=( "${argname}" )
}
required_argument() {
local argname="$(normalize_argument "$1")"
assign_var "__args_${argname}" 1
__args_list+=( "${argname}" )
__required_args_list+=( "${argname}" )
__unexport_args_list+=( "${argname}" )
}
undefined_variable() {
[ "${!1+x}" == '' ]
}
__positional_arg_count=0
positional_argument() {
assign_var "__positional_arg_$((__positional_arg_count++))" "$(normalize_argument "$1")"
}
__arguments_parsed=0
log_command() {
if [ "${__arguments_parsed}" = 0 ]; then
error "call parse_arguments before log_command"
exit 1
fi
local argname
# print the environment variables
for argname in "${__args_list[@]}"; do
if ! undefined_variable "$argname"; then
printf '%s=%q ' "$argname" "${!argname}"
fi
done
# print the script path
printf '%s' "$0"
# print the positional arguments
if [ "${#positional_arguments[@]}" != 0 ]; then
printf '%s' ' --'
printf ' %s' "${positional_arguments[@]}"
fi
printf '\n'
}
parse_arguments() {
__arguments_parsed=1
while [ $# != 0 ]; do
local argument="$1"
if [ "$argument" = '--' ]; then
shift
positional_arguments=( "$@" )
break
fi
case "$argument" in
--*)
local argname="$(normalize_argument "${argument#--}")"
local slotname="__args_${argname}"
if [ "${!slotname}" != '1' ]; then
error "unknown named argument: $argument ($argname)"
exit 1
fi
if [ $# -lt 2 ]; then
error "missing value for parameter ${argname}"
exit 1
fi
shift
assign_var "$argname" "$1" ;;
*) # when meeting a non option argument, stop parsing
positional_arguments=( "$@" )
break ;;
esac
shift
done
# error out when there are too many positional arguments
local pos_arg_count="${#positional_arguments[@]}"
if [ "${pos_arg_count}" -gt "${__positional_arg_count}" ]; then
error "too many positional arguments"
exit 1
fi
# error out when there are too few positional arguments
if [ "${pos_arg_count}" -lt "${__positional_arg_count}" ]; then
for ((i=pos_arg_count; i < __positional_arg_count; i++)); do
local arg_name_var="__positional_arg_$i"
error "missing positional argument: ${!arg_name_var}"
done
exit 1
fi
# assign positional argument variables
for ((i=0; i < __positional_arg_count; i++)); do
local arg_name_var="__positional_arg_$i"
local arg_name="${!arg_name_var}"
assign_var "${arg_name}" "${positional_arguments[i]}"
done
# detect missing mandatory arguments
for arg in "${__required_args_list[@]}"; do
if undefined_variable "$arg"; then
error "missing required argument: $arg"
exit 1
fi
done
# unexport arguments
for arg in "${__unexport_args_list[@]}"; do
export -n "$arg"
done
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment