Skip to content

Instantly share code, notes, and snippets.

@adrusi
Last active April 3, 2022 00:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adrusi/e6bec921b12364a1dccaf6ff100f09fe to your computer and use it in GitHub Desktop.
Save adrusi/e6bec921b12364a1dccaf6ff100f09fe to your computer and use it in GitHub Desktop.
Full-featured argument parsing in non-forking POSIX shell
#!/bin/sh
source parse_args.inc.sh
arg_alias() {
case "$1" in
h) printf help;;
m) printf monitor;;
w) printf wait;;
r) printf rate; false;;
esac
}
arg_callback() {
case "$1" in
monitor) printf 'mode=monitor';;
wait) printf 'mode=wait';;
rate) printf 'packet_rate=%q' "$2"; false;;
esac
}
read -r -d '' usage <<USAGE
Usage: $(basename "$0") [-h|--help] [-m|--monitor] [-w|--wait] [-r RATE|--rate RATE] HOST...
--help Print this message.
--monitor Report changes to host status indefinitely.
--wait Exit when the host status changes.
--rate Specify how frequently to poll for host status.
USAGE
parse_args arg_alias arg_callback "$usage" "$@"
mode=${mode:-monitor}
packet_rate="${packet_rate:-1}"
case "$packet_rate" in
''|*[!0-9.]*)
printf '%s: invalid value for --rate: %s\n' "$(basename "$0")" "$packet_rate" >&2
exit 1
;;
esac
parse_args_program="$0"
parse_args_program_name="$(basename "$0")"
parse_args() {
if [ "$parse_args_args_already_parsed" ]; then
export -n parse_args_args_already_parsed
export -n parse_args_variable_names
parse_args_args="$(printf '%q ' "$@")"
eval "set -- $parse_arge_variable_name"
for parse_args_variable in "$@"; do
export -n $parse_args_variable
done
eval "set -- $parse_args_args"
return 0
fi
parse_args_arg_alias="$1"
shift
parse_args_arg_callback="$1"
shift
parse_args_usage="$1"
shift
parse_args_normalized_args=""
while [ $# -gt 0 ]; do
case "$1" in
--?*=*) parse_args_normalized_args="$parse_args_normalized_args $(printf '%q %q' "${1%%=*}" "${1#*=}")";;
--*) parse_args_normalized_args="$parse_args_normalized_args $(printf '%q' "$1")";;
-?*)
parse_args_short_args="${1:1}"
while [ ${#parse_args_short_args} -gt 0 ]; do
parse_args_short_arg="${parse_args_short_args:0:1}"
parse_args_short_args="${parse_args_short_args:1}"
parse_args_expanded="$("$parse_args_arg_alias" "$parse_args_short_arg")"
parse_args_exit_code=$?
case "$parse_args_expanded" in
'')
printf '%s: unknown option: -%s\n' "$parse_args_program_name" "$parse_args_short_arg" >&2
printf '%s\n' "$parse_args_usage" >&2
exit 1
;;
*[!A-Za-z_-]*)
printf 'parse_args: invalid alias expansion: %s\n' "$parse_args_expanded" >&2
exit 1
;;
*)
parse_args_normalized_args="$parse_args_normalized_args --$parse_args_expanded"
[ 0 -ne $parse_args_exit_code ] && break
;;
esac
done
if [ ${#parse_args_short_args} -gt 0 ]; then
parse_args_normalized_args="$parse_args_normalized_args $(printf '%q' "$parse_args_short_args")"
fi
;;
*) parse_args_normalized_args="$parse_args_normalized_args $(printf '%q' "$1")";;
esac
shift
done
eval "set -- $parse_args_normalized_args"
parse_args_variables=""
parse_args_positional_args=""
while [ $# -gt 0 ]; do
case "$1" in
--)
shift
parse_args_positional_args="$parse_args_positional_args $(printf '%q ' "$@")"
break
;;
--help)
printf '%s\n' "$parse_args_usage"
exit 0
;;
--*)
parse_args_arg="$1"
shift
parse_args_callback_result="$("$parse_args_arg_callback" "${parse_args_arg:2}" "$@")"
parse_args_exit_code=$?
case "$parse_args_callback_result" in
'')
printf '%s: unknown option: %s\n' "$parse_args_program_name" "$parse_args_arg" >&2
printf '%s\n' "$parse_args_usage" >&2
exit 1
;;
*)
if [ $# -lt $parse_args_exit_code ]; then
if [ 1 = $parse_args_exit_code ]; then
printf '%s: expected 1 parameter for option %s\n' "$parse_args_program_name" "$parse_args_arg" >&2
else
printf '%s: expected %s parameter for option %s\n' "$parse_args_program_name" "$parse_args_exit_code" "$parse_args_arg" >&2
fi
printf '%s\n' "$usage" >&2
exit 1
fi
parse_args_variables="$parse_args_variables $parse_args_callback_result"
shift $parse_args_exit_code
;;
esac
;;
*)
parse_args_positional_args="$parse_args_positional_args $(printf '%q' "$1")";
shift
;;
esac
done
parse_args_variable_names=""
eval "set -- $parse_args_variables"
for parse_args_variable in "$@"; do
parse_args_variable_names="$parse_args_variable_names ${parse_args_variable%%=*}"
done
eval "parse_args_args_already_parsed=1 parse_args_variable_names=$(printf '%q' "$parse_args_variable_names") $parse_args_variables exec $(printf '%q' "$parse_args_program") $parse_args_positional_args"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment