Skip to content

Instantly share code, notes, and snippets.

@hlfbt
Created May 3, 2019 13:46
Show Gist options
  • Save hlfbt/2144c186b9d374dab4135600dda7a37c to your computer and use it in GitHub Desktop.
Save hlfbt/2144c186b9d374dab4135600dda7a37c to your computer and use it in GitHub Desktop.
Simple somewhat advanced getopts only dependent on bash and expr
#!/bin/bash
# Simple more advanced arguments parsing than builtin tools like getopts or a simple while/case looping over the arguments
#
# Features include handling of:
# - multiple grouped shorthands (f.i. -abc becoming -a -b -c),
# - equal sign value assignment (f.i. --arg=value becoming --arg value, and also -a=value becoming -a value)
# - dashless value assignment (f.i. arg=value becoming arg value)
#
# The only dependencies are bash (for arrays) and expr (included in coreutils) (for regexp matching and extraction) which are likely available everywhere (even in a busybox or on a mac!)
#
# Author: Alexander Schulz (alex@nope.bz)
#
parse_args() {
local key val
while [ $# -gt 0 ]; do
# Transform `-abc` into `-a -b -c`, with special handling of keeping passed values for the last arg (i.e. `-abc=value` becomes `-a -b -c=value`)
if expr "$1" : '-[^-].\+' >/dev/null; then
val="$1"
key=()
while [ "${#val}" -gt 1 ]; do
# Special case where the argument has a value (f.i. `-a=value`)
if expr "$val" : '-[^=]=.*' >/dev/null; then
key=( "${key[@]}" "$val" )
val="-"
else
key=( "${key[@]}" "$(expr "$val" : '\(-.\).*')" )
val="-$(expr "$val" : '-.\(.*\)')"
fi
done
set -- "${key[@]}" "${@:2}"
fi
# Transform `--arg=value` into `--arg value`, where both leading dashes are optional (so `-a=...` and `arg=...` also work)
if expr "$1" : '-\?-\?[^=]\+=.\+' >/dev/null; then
key="$(expr "$1" : '\(-\?-\?[^=]\+\)=.\+')"
val="$(expr "$1" : '-\?-\?[^=]\+=\(.\+\)')"
set -- "$key" "$val" "${@:2}"
fi
# The actual arguments handling happens here
case "$1" in
-s|--silent|silent)
SILENT=1
;;
-u|--user|user)
USER="$2"
shift # We have to shift the positional arguments when we consume the second one as well!
;;
-l|--log-file|log[-_]file)
LOG_FILE="$2"
shift
;;
-h|--help|help)
print_help
;;
esac
shift
done
}
# Example of valid usage:
# (setting the silent flag, printing the help, setting the user to foobar and setting the log file to script.log)
parse_args --silent -hu=foobar log_file=script.log
# Inside a script, you'll probably want to adapt the case block and then simply call the parse_args (or otherwise renamed) function before the main execution like so:
parse_args "$@"
# This has the cool side effect, that the actual positional arguments won't be modified, and you can also pass any arbitray arguments
# So you could for example also pass all but the first positional arguments like so:
parse_args "${@:2}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment