Reading CLI flags in bash
Sometimes you gotta throw together a quick CLI. You want zero system
dependencies, so you use bash
. And then you gotta have some flags and
arguments and suddenly you find yourself thinking "this is hard".
One alternative is to just stick to environment variables instead of parsing CLI flags. This is trivial.
# setting an environment variable:
key=value my-cli
# reading an environment variable
echo "The value of the environment variable is $key"
Non-alternatively, we can stumble on this corner of the internet and find a beautiful demonstration for reading various kinds of flags. I'll paste the code sample here verbatim:
#!/bin/sh
# POSIX
die() {
printf '%s\n' "$1" >&2
exit 1
}
# Initialize all the option variables.
# This ensures we are not contaminated by variables from the environment.
file=
verbose=0
while :; do
case $1 in
-h|-\?|--help)
show_help # Display a usage synopsis.
exit
;;
-f|--file) # Takes an option argument; ensure it has been specified.
if [ "$2" ]; then
file=$2
shift
else
die 'ERROR: "--file" requires a non-empty option argument.'
fi
;;
--file=?*)
file=${1#*=} # Delete everything up to "=" and assign the remainder.
;;
--file=) # Handle the case of an empty --file=
die 'ERROR: "--file" requires a non-empty option argument.'
;;
-v|--verbose)
verbose=$((verbose + 1)) # Each -v adds 1 to verbosity.
;;
--) # End of all options.
shift
break
;;
-?*)
printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2
;;
*) # Default case: No more options, so break out of the loop.
break
esac
shift
done
# if --file was provided, open it for writing, else duplicate stdout
if [ "$file" ]; then
exec 3> "$file"
else
exec 3>&1
fi
# Rest of the program here.
# If there are input files (for example) that follow the options, they
# will remain in the "$@" positional parameters.
After digesting this a bit, we can pare it down to support only the kinds of
flags that we need. For example, here's a simpler function that reads in a
--name=[name]
flag.
#! /usr/bin/env bash
function say_hello() {
local name=''
while :; do
case $1 in
--name=?*)
name="${1#*=}"; shift;
;;
?*)
echo "unknown option \"$1\""
return 1
;;
*)
break;
esac
done
if [[ -z "$name" ]]; then
echo "--name required"
return 1
fi
echo "Hello, $name"
}
say_hello --name=George
say_hello --name=Monica
say_hello --name=Fred
👌
- Previous: Debouncing with promises