Skip to content

Instantly share code, notes, and snippets.

@guilt
Last active July 19, 2025 09:18
Show Gist options
  • Select an option

  • Save guilt/064eb31172aca965e86bb19dfd18fea2 to your computer and use it in GitHub Desktop.

Select an option

Save guilt/064eb31172aca965e86bb19dfd18fea2 to your computer and use it in GitHub Desktop.
Argument Parsing in Bash
#!/usr/bin/env sh
# Variable for the script description
_ARGPARSE_SCRIPT_DESCRIPTION=""
# Use this one wierd trick to shorten your CLI Boolean Flags
_ARGPARSE_FLAG_BEHAVIOUR="Add this flag to set the argument to 'true' (don't use '--flag true')"
# Declare an associative array for argument properties
_ARGPARSE_ARG_VALUES=()
# Default Enums and Values
_ARGPARSE_NULL_VALUE_="null"
_ARGPARSE_DEFAULT_=1
_ARGPARSE_HELP_=2
_ARGPARSE_TYPE_=3
_ARGPARSE_OPTIONAL_=4
_ARGPARSE_NUM_VALUES_=5 # Total number of properties per argument
# Debug printing helpers
if [ -t 1 ]; then
_COLOR_ERR_SET="$(tput setaf 1)"
_COLOR_SET="$(tput setaf 4)"
_COLOR_RESET="$(tput sgr0)"
else
_COLOR_ERR_SET=${_COLOR_ERR_SET:-}
_COLOR_SET=${_COLOR_SET:-}
_COLOR_RESET=${_COLOR_RESET:-}
fi
# Function to set the script description
# Usage: setDescription "Description text"
setDescription() {
_ARGPARSE_SCRIPT_DESCRIPTION="$1"
}
# Function to display help
# Usage: showHelp
showHelp() {
local args=( "${_ARGPARSE_ARG_VALUES[@]}" )
local prefix=" "
echo "\nUsage: $0 [arguments...]"
echo ""
echo "$_ARGPARSE_SCRIPT_DESCRIPTION"
echo ""
echo "Arguments:"
for ((i=0; i<${#args[@]}; i+=$_ARGPARSE_NUM_VALUES_)); do
[[ ${args[i+_ARGPARSE_DEFAULT_]} == "$_ARGPARSE_NULL_VALUE_" ]] && args[i+_ARGPARSE_DEFAULT_]=''
if [[ ${args[i+_ARGPARSE_TYPE_]} == "bool" ]]; then
args[i+_ARGPARSE_TYPE_]=''
args[i+_ARGPARSE_HELP_]="${args[i+_ARGPARSE_HELP_]} $_ARGPARSE_FLAG_BEHAVIOUR"
else
args[i+_ARGPARSE_TYPE_]="<${args[i+_ARGPARSE_TYPE_]}>"
fi
printf "%s ${_COLOR_SET}%-20s${_COLOR_RESET}: (%8s) %s\n" "$prefix" "--${args[i]} ${args[i+_ARGPARSE_TYPE_]}" "${args[i+_ARGPARSE_OPTIONAL_]}" "${args[i+_ARGPARSE_HELP_]} (defaults to '${args[i+_ARGPARSE_DEFAULT_]}')"
done
printf "%s ${_COLOR_SET}%-20s${_COLOR_RESET}: Display this help\n" "$prefix" "-h | --help"
}
# Function Alias to display help
# Usage: usage
usage()
{
showHelp
}
# Internal Helper Function to check an Argument
# Usage: _checkRequired "$@"
_checkRequired() {
for ((i=0; i<${#_ARGPARSE_ARG_VALUES[@]}; i+=$_ARGPARSE_NUM_VALUES_)); do
if [[ "${_ARGPARSE_ARG_VALUES[i+_ARGPARSE_OPTIONAL_]}" == "required" ]]; then
if ! (echo "$@" | grep "${_ARGPARSE_ARG_VALUES[i]}") >/dev/null ; then
echo "${_COLOR_ERR_SET}Error${_COLOR_RESET}: '--${_ARGPARSE_ARG_VALUES[i]}' is required" >&2
showHelp >&2
exit 1
fi
fi
done
}
# Function to define a command-line argument
# Usage: defineArgument "argumentName" ["default"] ["help text"] ["type"] ["required"]
defineArgument() {
argumentName=$1
argumentValue=${2:-"$_ARGPARSE_NULL_VALUE_"}
argumentHelp=${3:-""}
argumentType=${4:-"string"}
argumentIsOptional=${5:-"optional"}
if [[ "$argumentIsOptional" == "required" ]]; then
argumentValue="$_ARGPARSE_NULL_VALUE_"
fi
_ARGPARSE_ARG_VALUES+=("$argumentName" "$argumentValue" "$argumentHelp" "$argumentType" "$argumentIsOptional")
if [[ "$argumentValue" == "$_ARGPARSE_NULL_VALUE_" ]]; then
argumentValue=""
fi
export "$argumentName"="$argumentValue"
}
# Function to parse command-line arguments
# Usage: parseArgs "$@"
parseArgs() {
# Set Description
_setDefaultDescription
# Check for help
_checkForHelp "$@"
# Check for missing required arguments
_checkRequired "$@"
while [[ $# -gt 0 ]]; do
key="${1#--}" # remove '--' prefix
if ! (echo "${_ARGPARSE_ARG_VALUES[*]}" | tr " " "," | grep "$key," > /dev/null ); then
echo "${_COLOR_ERR_SET}Error${_COLOR_RESET}: Unknown argument '$key'" >&2
showHelp >&2
exit 1
fi
for ((i=0; i<${#_ARGPARSE_ARG_VALUES[*]}; i+=5)); do
if [[ "$key" == "${_ARGPARSE_ARG_VALUES[i]}" ]]; then
case ${_ARGPARSE_ARG_VALUES[i+_ARGPARSE_TYPE_]} in
'string')
if [[ -z "${2:-}" || "${2:-}" == --* ]]; then
echo "${_COLOR_ERR_SET}Error${_COLOR_RESET}: Missing value for argument --$key" >&2
exit 1
else
export "$key"="$2"
shift 2
fi
;;
'bool')
if [[ -z "${2:-}" || "${2:-}" == --* ]]; then
export "$key"='true'
shift
else
echo "${_COLOR_ERR_SET}Error${_COLOR_RESET}: Extra value for argument --$key" >&2
exit 1
fi
;;
*)
echo "${_COLOR_ERR_SET}Error${_COLOR_RESET}: Unknown argument type '${_ARGPARSE_ARG_VALUES[i+_ARGPARSE_TYPE_]}' for '$key'" >&2
exit 1
;;
esac
fi
done
done
}
# Internal Function to check for help option
# Usage: _checkForHelp "$@"
_checkForHelp() {
if (echo "$@" | grep -- "-h" > /dev/null) || (echo "$@" | grep -- "--help" > /dev/null); then
showHelp
exit 0
fi
}
# Function to set the default script description
# Usage: _setDefaultDescription
_setDefaultDescription() {
_ARGPARSE_SCRIPT_DESCRIPTION=${_ARGPARSE_SCRIPT_DESCRIPTION:-"Description not given."}
}
# Internal Main Function
# Usage: main "$@"
main() {
setDescription "This is an argument parser, adapted from https://github.com/yaacov/argparse-sh/"
defineArgument "debug" "false" "Enable debug mode" "bool" "optional"
parseArgs "$@"
if [[ $debug == "true" ]]; then
echo "Debug mode is ON."
fi
exit 0
}
# Standalone Invocation
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
@guilt
Copy link
Copy Markdown
Author

guilt commented Apr 21, 2025

This is adopted from: /yaacov/argparse-sh. It fixes a few known bugs, when the argument is missing or when an extra value it set erroneously.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment