Skip to content

Instantly share code, notes, and snippets.

@mcastorina
Created November 1, 2019 20:33
Show Gist options
  • Save mcastorina/682fa0ca0ff9646e283a5ef95e4cb36d to your computer and use it in GitHub Desktop.
Save mcastorina/682fa0ca0ff9646e283a5ef95e4cb36d to your computer and use it in GitHub Desktop.
Pure bash implementation of getops
#!/bin/bash
# Usage:
# set_opts 'h,help v:,value: l,list'
# OPTS=$(get_opts "$@")
# if [[ $? -ne 0 ]]; then
# exit 1
# fi
# eval set -- "$OPTS"
# while true; do
# case $1 in
# -v|--value)
# value=$2 ; shift 2 ;;
# -l|--list)
# dryrun=true ; shift 1 ;;
# -h|--help)
# print_usage ; exit 0 ; shift 1 ;;
# --) shift 1 ; break ;;
# *) echo "Invalid argument: $1" ; print_usage ; exit 1 ;;
# esac
# done
function set_opts {
# list can be space or comma separated
valid_opts=$(tr -s ' ,' '\n' <<< "$@")
}
function search_valid_opts {
local arg="$1"
if [[ -z $arg ]]; then
return 1
elif [[ -z $valid_opts ]]; then
>&2 echo "Options not set. Set with set_opts 'list of options'"
return 2
fi
# replace spaces with newlines and search for $arg
grep "$arg" <<< "$valid_opts" &> /dev/null
}
function opt_is_valid {
local opt="$1"
# opt can have an optional ':'
search_valid_opts "^$opt:\?$"
}
function expects_value {
local opt="$1"
# remove leading dashes
opt="${opt#-}"
opt="${opt#-}"
# options expecting a value must end in ':'
search_valid_opts "^${opt##+(-)}:$"
}
# get_opt will take long option or short option(s) and return newline separated
# option names. Each option is validated and should exist in valid_opts. If the
# short variable expects a value, it must be the last one in the list.
# examples:
# get_opt --option => --option
# get_opt -asdf => -a -s -d -f
function get_opt {
local arg="$1"
if [[ -z $arg ]]; then
return 1
fi
if [[ $arg =~ ^--[a-zA-Z0-9].+ ]]; then
# long opt
opt="${arg#--}" # remove leading --
if opt_is_valid "$opt"; then
printf '%s\n' "$arg"
else
>&2 echo "Invalid option: $arg"
return 1
fi
elif [[ $arg =~ ^-[a-zA-Z0-9] ]]; then
# short opt
local last_expecting_opt=""
opt="${arg#-}" # remove leading -
for single_opt in $(fold -w1 <<< "$opt"); do
if [[ ! -z $last_expecting_opt ]]; then
>&2 echo "-$last_expecting_opt expects a value"
return 1
fi
if expects_value "$single_opt"; then
last_expecting_opt="$single_opt"
fi
if opt_is_valid "$single_opt"; then
printf '%s\n' "-$single_opt"
else
>&2 echo "Invalid option: -$single_opt"
return 1
fi
done
else
# normal argument
echo "$arg"
fi
}
# check if the value is an option
function is_opt {
local arg="$1"
if [[ -z $arg ]]; then
return 1
fi
[[ $arg =~ ^--[a-zA-Z0-9].+ || $arg =~ ^-[a-zA-Z0-9] ]]
}
# check if the value is not an option
function is_arg {
local arg="$1"
if [[ -z $arg ]]; then
return 1
fi
! is_opt "$arg"
}
# append a value to a list
function append {
local list="$1"
local value="$2"
if [[ -z "$list" ]]; then
printf '%s\n' "$value"
else
printf '%s\n' "$list $value"
fi
}
# get_opts will iterate over all arguments and validate / organize them into options and arguments
# examples:
# get_opts arg1 --flag flag_value arg2 -atx => --flag flag_value -a -t -x -- arg1 arg2
# get_opts -tf flag_value -- --arg1 -arg2 => -t -f flag_value -- --arg1 -arg2
function get_opts {
local opts=""
local args=""
local double_dash_encountered=""
for arg in "$@"; do
if [[ ! -z $double_dash_encountered ]]; then
args=$(append "$args" "$arg")
continue
fi
if [[ $arg == '--' ]]; then
double_dash_encountered=true
continue
fi
if [[ ! -z $last_expecting_opt ]]; then
opts=$(append "$opts" "$arg")
unset last_expecting_opt
continue
elif is_arg "$arg"; then
args=$(append "$args" "$arg")
continue
fi
expanded_opts=$(get_opt "$arg")
if [[ $? -ne 0 ]]; then
return 1
fi
for opt in $expanded_opts; do
opts=$(append "$opts" "$opt")
if expects_value "$opt"; then
last_expecting_opt="$opt"
fi
done
done
if [[ ! -z $last_expecting_opt ]]; then
>&2 echo "Missing value for $last_expecting_opt"
return 1
fi
printf '%s\n' "$opts -- $args"
}
# Full example:
#
# #!/bin/bash
# source path/to/get_opts.sh
#
# function print_usage {
# echo -n "Usage: $(basename $0) [OPTIONS]
# Example usage.
#
# -a, --action Action to do
# -n, --dry-run Print the actions that would be executed
# -h, --help Display this help and exit
# "
# }
#
# set_opts 'help h dry-run n action: a:'
#
# OPTS=$(get_opts "$@")
# if [[ $? -ne 0 ]]; then
# print_usage
# exit 1
# fi
# eval set -- "$OPTS"
# while true ; do
# case $1 in
# -a|--action)
# action=$2 ; shift 2 ;;
# -n|--dry-run)
# dryrun=true ; shift 1 ;;
# -h|--help)
# print_usage ; exit 0 ; shift 1 ;;
# --) shift 1 ; break ;;
# *) echo "Invalid argument: $1" ; print_usage ; exit 1 ;;
# esac
# done
#
# echo -e "action: $action\ndry-run: $dryrun\nargs: $@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment