Skip to content

Instantly share code, notes, and snippets.

@iwill
Last active December 28, 2022 10:57
Show Gist options
  • Save iwill/97935594437d560bbaaa20037a7b73bb to your computer and use it in GitHub Desktop.
Save iwill/97935594437d560bbaaa20037a7b73bb to your computer and use it in GitHub Desktop.
A Bash Script - Get Options without `getopt`, `getopts` or `gnu-getopt`.
#!/bin/bash
## basic
set -e -o pipefail
cmd="get-opts.sh"
RED='01;38;5;196'
LINK='04;38;5;4'
function color {
params=$1
shift
echo -e "\033[${params}m$@\033[0m"
}
## prepare
cmd_debug=false
function debug {
if ! $cmd_debug; then
return 0
fi
if [[ $# -eq 0 || "$1" == "-n" ]]; then
shift || true
echo
fi
if [[ $# -gt 0 ]]; then
echo -e "# $@"
fi
}
if [[ $1 = "d" || $1 == "debug" ]]; then
cmd_debug=true
debug -n "[$1]:"
debug " got cmd_debug [$cmd_debug]"
shift
fi
if [[ $# = 0 ]]; then
set -- "-h"
elif [[ "$1" = "-t" || "$1" = "--test" ]]; then
echo
cat << EOF
Test with builtin cases:
${cmd} -a --opt_b "" --opt_c="-x: \\\`\"'\\\\" d e - -no --unknown -- --ab -c -- d e
EOF
set -- -a --opt_b "" --opt_c="-x: \`\"'\\" d e - -no --unknown -- --ab -c -- d e
fi
## get opts
p_args=() # positional
next_arg_is_value=false
while [[ $# > 0 ]]; do
debug -n "[$1]:"
this_arg_is_value=$next_arg_is_value
next_arg_is_value=false
case "$1" in
# customized options
-a|--opt_a)
opt_a=true
shift
debug " got opt_a [$opt_a]"
;;
-b|--opt_b)
opt_b=true
debug " got opt_b [$opt_b]"
shift
if [[ "$1" != -* ]] || $this_arg_is_value; then
this_arg_is_value=true # for the `shift` at the end of `while`
arg_b=$1
debug " got arg_b [$arg_b]"
fi
;;
-c|--opt_c)
opt_c=true
debug " got opt_c [$opt_c]"
shift
if [[ "$1" != -* ]] || $this_arg_is_value; then
this_arg_is_value=true # for the `shift` at the end of `while`
arg_c=$1
debug " got arg_c [$arg_c]"
fi
;;
# stantard options
\?|-h|--help)
opt_help=true
debug " got opt [$opt_help]"
shift
;;
-*=*) # split to arg and value
arg="${1%=*}"
val="${1#*=}"
next_arg_is_value=true
debug " split to [$arg] and [$val]"
shift; set -- "$arg" "$val" "$@"
debug " set args"
;;
--) # after this arg, only positional arguments are accepted
shift; p_args+=("$@")
debug " got p_args after -- [$@]"
set --
debug " set args"
;;
-|-?|--?*) # `-?`: 1 char at most, `--?*`: 1 char at least
>&2 echo -e "`color $RED unknown arg [$1]`"
shift
;;
-??*) # `-??*`: 2 chars at least, split combined options, e.g. `-ab` -> `-a` and `-b`
# remove first `-`, split to chars, prepand `-` to each char
opts=(`echo "${1##-}" | grep -o . | sed 's/^/-/'`) # https://stackoverflow.com/a/7579022/456536
debug " split to opts [${opts[@]}]"
shift; set -- "${opts[@]}" "$@"
debug " set args"
;;
*) # positional arguments
p_args+=("$1")
debug " got p_arg [$1]"
shift
;;
esac
if $this_arg_is_value; then
this_arg_is_value=false
shift
fi
debug " left args [$@]"
done
set -- "${p_args[@]}"
if ! ${opt_help:-false}; then
echo
echo "Results:"
echo "# opt_a: [$opt_a]"
echo "# opt_b: [$opt_b], val: [$arg_b]"
echo "# opt_c: [$opt_c], val: [$arg_c]"
echo "# p_args: [$@]"
echo
exit 0
fi
## help
less -r << EOF
`color $RED NAME`
`color $RED $cmd` - Get Options without \`getopt\`, \`getopts\` or \`gnu-getopt\`.
`color $RED SYNOPSIS`
`color $RED $cmd` [`color $RED debug`] [<args>]
`color $RED $cmd` [`color $RED debug`] `color $RED -t`|`color $RED --test`
`color $RED $cmd` `color $RED -h`|`color $RED --help`
`color $RED DESCRIPTION`
Get Options without \`getopt\`, \`getopts\` or \`gnu-getopt\`, which based on
Bruno Bronosky's answer:
`color $LINK https://stackoverflow.com/a/14203146/456536`
Commands:
`color $RED d`|`color $RED debug` Display debug information.
Options:
`color $RED -t`|`color $RED --test` Test with builtin cases.
`color $RED -h`|`color $RED --help` Display help information about $cmd.
Features:
short option
-a
long option
--opt_b
combined options
-no # split to \`-n -o\`
option with space separated value
--opt_b "value"
option with equal sign separated value
--opt_c="value"
empty string value
--opt_b ""
value with specific characters
--opt_c="-x: \\\`\"'\\\\"
unknown options
- -no --unknown
signify the end of the options
--
positional arguments
d e\` and \`--ab -c -- d e
Customize:
no arg
if [[ \$# = 0 ]]; then ...
start with command
if [[ "$0" = "debug" ]]; then ...
`color $RED AUTHOR`
Written by Míng <`color $LINK i@iwill.im`>. Idea from Bruno Bronosky:
`color $LINK https://stackoverflow.com/users/117471/bruno-bronosky`
`color $RED COPYRIGHT`
Copyright (c) 2022 Míng. $cmd is available under the MIT license.
EOF
@iwill
Copy link
Author

iwill commented Oct 12, 2022

Download get-opts.sh:

wget https://gist.githubusercontent.com/iwill/97935594437d560bbaaa20037a7b73bb/raw/a3ba34282beafbdb5f7a78603f4c758bda9af119/get-opts.sh

Allow all users can run:

chmod a+x get-opts.sh

Display help information:

./get-opts.sh -h

Test with builtin cases:

./get-opts.sh -t

Result would be:

Test with builtin cases:
    get-opts.sh -a --opt_b "" --opt_c="-x: \`\"'\\" d e - -no --unknow -- --ab -c -- d e

munknow arg: [-]
munknow arg: [-n]
munknow arg: [-o]
munknow arg: [--unknow]

Results:
    opt_a: [true]
    opt_b: [true] val: []
    opt_c: [true] val: [-x: `"'\]
    p_args: [d e --ab -c -- d e]

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