home blog portfolio Ian Fisher

Robustly parsing flags in Bash scripts

24 August 2025 Subscribe
cli 2

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. ∎