Parsing command-line arguments in Bash is a pain. Still, it's worthwhile to do it correctly, and well. Many times have I run myscript.sh --help
only to watch helplessly as it barrelled ahead heedless of the --help
flag. Or omitted options or skimped on validation in my own scripts because writing a big for arg in "$@"; do case "$arg" in ... esac; done
loop was too annoying for a small script.
One obstacle is that Bash's limited facilities for abstraction – functions can only return an integer error code and write unstructured bytes to stdout
and stderr
– make it impossible to write something like Python's argparse
library.
Or so I thought. But with the help of associative arrays (which Bash has had since 2009), it is possible to do better than getopts
:
parse_flags "ARG1 [--name=] --verbose" "$@"
arg1="${__args[ARG1]}"
name="${__args[--name]}"
verbose="${__args[--verbose]}"
https://github.com/iafisher/foundation/blob/master/shell/bash_functions.sh#:~:text=ian_parse_flags()
Here we define a command that takes a positional argument called ARG1
, an optional flag --name
that takes an argument, and a switch --verbose
.
__args
is how library clients access the parsed arguments. It's a global variable declared by the library; if that makes you uneasy, you could modify the implementation to use a nameref to pass in a locally-declared associative array.
Subcommands are also supported:
parse_flags_subcmd \
add 'PATH [--name=] --overwrite' \
remove 'NAME' \
-- "$@"
case "$__subcmd" in
# `main_XYZ` functions can access their args in `__args`.
add)
main_add
;;
remove)
main_remove
;;
esac
The format of the description string allows the function to provide a useful help message to the user with no extra effort. You can't add your own help text, but if your script has gotten that complicated, consider Python. ∎