Created
November 1, 2019 20:33
-
-
Save mcastorina/682fa0ca0ff9646e283a5ef95e4cb36d to your computer and use it in GitHub Desktop.
Pure bash implementation of getops
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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