Skip to content

Instantly share code, notes, and snippets.

@jtpaasch
Created May 11, 2017 05:22
Show Gist options
  • Save jtpaasch/6ee9e12bf27c8230a9ef0798870fd8de to your computer and use it in GitHub Desktop.
Save jtpaasch/6ee9e12bf27c8230a9ef0798870fd8de to your computer and use it in GitHub Desktop.
Declare allowed arguments, parse args, and auto-generate help.
#!/usr/bin/env bash
#
# Utility for parsing command line arguments.
#
# To use it, declare the allowed flags, options, and arguments,
# then run the `parse_args()` function.
#
# To declare flags, e.g., `--verbose` and `--fail-fast`:
#
# flag__id__0="--verbose"
# flag__help__0="Display verbose output?"
#
# flag__id__1="--fail-fast"
# flag__help__1="Exit on first error?"
#
# To declare options (with or without defaults):
#
# opt__id__0="--output"
# opt__help__0="A file to write output to."
# opt__value__0="out.txt" # default
#
# opt__id__1="--user"
# opt__help__1="A username to login with."
#
# To declare positional (required) arguments:
#
# arg__id__0="name"
# arg__help__0="A unique name for the resource."
#
# arg__id__1="greeting"
# arg__help__1="A greeting to display on login."
#
# To specify a description of the command for
# the auto-generated help, declare a `command_description`:
#
# command_description="My custom bash tool."
#
# Once all those things are declared, you can
# parse the arguments:
#
# parse_args "$@"
#
# After that, each flag/opt/arg will have a value.
# For instance:
#
# echo "flag 0: $flag__value__0"
# echo "opt 0: $opt__value__0"
# echo "arg 0: $arg__value__0"
# echo "arg 1: $arg__value__1"
#
# To display auto-generated help, call the script
# with `-h` or `--help`.
# DESCRIPTION
# Get a flag's help text.
#
# ARGUMENTS
# $1: The desired flag's index (X in $flag__help__X).
get_flag_help() {
local key="flag__help__$1"
echo "${!key}"
}
# DESCRIPTION
# Get an option's help text.
#
# ARGUMENTS
# $1: The desired option's index (X in $opt__help__X).
get_opt_help() {
local key="opt__help__$1"
echo "${!key}"
}
# DESCRIPTION
# Get an argument's help text.
#
# ARGUMENTS
# $1: The desired argument's index (X in $arg__help__X).
get_arg_help() {
local key="arg__help__$1"
echo "${!key}"
}
# DESCRIPTION
# Get a flag's value.
#
# ARGUMENTS
# $1: The desired flag's index (X in $flag__value__X).
get_flag_value() {
local key="flag__value__$1"
echo "${!key}"
}
# DESCRIPTION
# Get an option's value.
#
# ARGUMENTS
# $1: The desired option's index (X in $opt__value__X).
get_opt_value() {
local key="opt__value__$1"
echo "${!key}"
}
# DESCRIPTION
# Get an argument's value.
#
# ARGUMENTS
# $1: The desired argument's index (X in $arg__value__X).
get_arg_value() {
local key="arg__value__$1"
echo "${!key}"
}
# DESCRIPTION
# Set a flag's value.
#
# ARGUMENTS
# $1: The desired flag's index (X in $flag__value__X).
# $2: The value to set it to.
set_flag_value() {
printf -v "flag__value__$1" %s "$2"
}
# DESCRIPTION
# Set an option's value.
#
# ARGUMENTS
# $1: The desired option's index (X in $opt__value__X).
# $2: The value to set it to.
set_opt_value() {
printf -v "opt__value__$1" %s "$2"
}
# DESCRIPTION
# Set an argument's value.
#
# ARGUMENTS
# $1: The desired argument's index (X in $arg__value__X).
# $2: The value to set it to.
set_arg_value() {
printf -v "arg__value__$1" %s "$2"
}
# DESCRIPTION
# Given a flag's id, find its index.
#
# ARGUMENTS
# $1: The flag's id (the value of $flag__id__X).
get_flag_index() {
local index;
local value;
for key in ${!flag__id__*}; do
value=${!key}
if [ "$1" == "$value" ]; then
index="${key#*flag__id__}"
break
fi
done
echo $index
}
# DESCRIPTION
# Given an option's id, find its index.
#
# ARGUMENTS
# $1: The option's id (the value of $opt__id__X).
get_opt_index() {
local index;
local value;
for key in ${!opt__id__*}; do
value=${!key}
if [ "$1" == "$value" ]; then
index="${key#*opt__id__}"
break
fi
done
echo $index
}
# DESCRIPTION
# Find the lowest argument index that has no value.
get_arg_index() {
local index
local current_index
local value_key
local value
for key in ${!arg__id__*}; do
current_index="${key#*arg__id__}"
value_key="arg__value__$current_index"
value=${!value_key}
if [ -z "$value" ]; then
index=$current_index
break
fi
done
echo $index
}
# DESCRIPTION
# Given flags/options/arguments, parse the provided arguments
# and fill in their values. If invalid options or arguments
# are found, return with an exit code of 1.
#
# ARGUMENTS
# "$@": All the arguments provided on the command line.
parse_args() {
local args=("$@")
local index=0
local next_index
local next_item
local skip
local flag_index
local opt_index
for item in "${args[@]}"; do
if [ "$item" == "-h" ] || [ "$item" == "--help" ]; then
usage
return 1
fi
next_index=$(($index + 1))
next_item="${args[$next_index]}"
if [ "$skip" == "true" ]; then
skip=""
index=$next_index
continue
fi
flag_index=$(get_flag_index "$item")
if [ ! -z $flag_index ]; then
set_flag_value "$flag_index" "true"
fi
opt_index=$(get_opt_index "$item")
if [ ! -z $opt_index ]; then
if [ -z "$next_item" ]; then
echo "Error: $item requires a value."
return 1
fi
set_opt_value "$opt_index" "$next_item"
skip="true"
fi
arg_index=$(get_arg_index)
if [ "${item:0:1}" != "-" ] && [ ! -z $arg_index ]; then
set_arg_value "$arg_index" "$item"
fi
if [ "${item:0:1}" == "-" ] && \
[ -z $flag_index ] && \
[ -z $opt_index ]; then
echo "Unrecognized option: $item."
return 1
fi
if [ "${item:0:1}" != "-" ] && [ -z $arg_index ]; then
echo "Unrecognized argument: $item."
return 1
fi
index=$next_index
done
local current_index
local id
local value
local value_key
for key in ${!arg__id__*}; do
id=${!key}
current_index="${key#*arg__id__}"
value_key="arg__value__$current_index"
value=${!value_key}
if [ -z "$value" ]; then
echo "Missing required argument: $id."
return 1
fi
done
}
# DESCRIPTION
# Generate usage/help for the command.
usage() {
local current_index
local help
local id
echo "USAGE: $0 [FLAGS] [OPTIONS] ARGUMENTS"
if [ ! -z "$command_description" ]; then
echo ""
echo " $command_description"
fi
echo ""
echo "FLAGS"
for key in ${!flag__id__*}; do
id=${!key}
current_index="${key#*flag__id__}"
help=$(get_flag_help $current_index)
echo " $id -- $help"
done
echo ""
echo "OPTIONS"
for key in ${!opt__id__*}; do
id=${!key}
current_index="${key#*opt__id__}"
help=$(get_opt_help $current_index)
echo " $id TEXT -- $help"
done
echo ""
echo "ARGUMENTS"
for key in ${!arg__id__*}; do
id=${!key}
current_index="${key#*arg__id__}"
help=$(get_arg_help $current_index)
echo " $id -- $help"
done
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment