I would like to parse --this=style command line arguments in bash.
Main requirements:
- --abc=xyz Obviously should set abc to xyz
- --thing Should set thing=true
- --no-thing Should set thing=false
- Minimal noise when defining default values.
Other constraints:
- Keeping non --this=style arguments around in some way is helpful.
- I'm not interested in short form -a -b -c style.
- A way of requiring certain args would be nice.
I'm interested in both short lightweight portable solutions that can just be inlined into any script, but also larger library style solutions that provide more features. Below is a middle of the ground solution I cooked up (worst of both worlds?) but I'll be interested to see what other ideas people have to balance the trade-off between length and functionality.
parse_args.sh
# Parse double dashed command line options.
#
# Parse positional options in the following formats:
# --option Sets {option} to "true".
# --no-option Sets {option} to "false".
# --option=value Sets {option} to {value}.
#
# Option names must already be defined or an "unknown option" error will be
# printed to stderr.
#
# Output variables:
# remaining_args All remaining args in the order
# they were defined.
#
# Returns: 1 if there was an error.
parse_args() {
remaining_args=()
for arg; do
if [[ "$arg" =~ ^--(.+)$ ]]; then
local k="${BASH_REMATCH[1]}"
local v=true
if [[ "$k" =~ ^([^=]+)=(.*)$ ]]; then
k="${BASH_REMATCH[1]}"
v="${BASH_REMATCH[2]}"
elif [[ "$k" =~ ^no-(.*)$ ]]; then
k="${BASH_REMATCH[1]}"
v=false
fi
if [[ ! -v "$k" ]]; then
echo "error: unknown option: '$k'" > /dev/stderr
return 1
fi
eval $k="$v"
else
remaining_args+=("$arg")
fi
done
return 0
}
Here's an example program using it:
example.sh
#!/bin/bash
. parse_args.sh
# Declare defaults
breakfast="avocado toast"
lunch="burrito"
vegetarian=false
vegan=true
# Define help as a string, not a function, so that we have access to the
# defaults
help=$( cat - <<EOF
Usage: $0 --option=value other_food1 ... other_foodN
Options (with given defaults):
breakfast ($breakfast)
What to eat for breakfast.
lunch ($lunch)
What to eat for lunch.
vegetarian ($vegetarian)
Whether the meals should be vegetarian or not.
vegan ($vegan)
Whether the meals should be vegan or not.
other_foodN:
Positional arguments are other foods. These can be before, after, or in
between options.
EOF
)
parse_args "$@" || { echo "$help" > /dev/stderr; exit 1 ; }
set -- "${remaining_args[@]}"
cat - <<EOF
breakfast: '$breakfast'
lunch: '$lunch'
vegetarian: '$vegetarian'
vegan: '$vegan'
other foods:
EOF
for p; do echo " '$p'" ; done
Running it looks like:
$ ./example.sh \
--breakfast=muesli mole \
--lunch=rosti horchata \
--vegetarian \
--no-vegan
breakfast: 'muesli'
lunch: 'rosti'
vegetarian: 'true'
vegan: 'false'
other foods:
'mole'
'horchata'
Edit:
While digging a bit more I found that an old co-worker of mine ended up porting the "heavy weight" library from my old work. https://github.com/kward/shflags/blob/master/README.md This is exactly what I wanted for "heavy weight" solutions, thanks Kate for porting it! I'm still interested to see ideas that people have for light weight versions though.