Without introducing getopts
, I'd like to parse expressions like so:
./cli.sh data i -f -f="./path/to/file.txt" --flags="--a --b"
And variations/combinations thereof:
./cli.sh -file="./path/to/file.txt" data --flags="--a --b" -f i
Unfortunately, I am stuck with reading all optional values of the long options submitted, for example: --flags="--a --b"
only returns --a
as value for the long option --flags
.
I have created a minimalized version of a parser which exhibits my current issue:
#!/usr/bin/env bash
__init_system () {
CMD_SED="sed"
CMD_SED_EXT="sed -E"
}
__init_system_Darwin () {
__init_system
CMD_SED="gsed"
CMD_SED_EXT="gsed -E"
}
invoke_func () {
SYSTEM=$(uname)
FUNC=$1 && shift 1
declare -f ${FUNC}_${SYSTEM} >/dev/null
[ $? -eq 0 ] && { ${FUNC}_${SYSTEM} "$@"; return $?; } || { ${FUNC} "$@"; return $?; }
}
handle_cli_harmonizing () {
cli_adjusted=$(echo "$@" | ${CMD_SED_EXT} \
-e 's@([ ])+@ @g'\
-e 's@\<h\>@help@g'\
-e 's@\<e\>@enable@g'\
-e 's@\<d\>@disable@g'\
-e 's@\<i\>@import@g'\
-e 's@\<sl\>@showlog@g'\
-e 's@\<st\>@status@g'\
| tr ' ' '\n' | sort -u | xargs
)
#echo ">>> Original: <$@>"
#echo ">>> Adjusted: <$cli_adjusted>"
}
handle_cli_parsing () {
for param in $@; do
case ${param} in
start|stop|status|enable|disable|data|showlog)
CMD=$param
;;
help|app|stats|import|export|dbinfo|exec)
SPEC=$param
;;
--*|-*)
#echo ">>> Found param: <$param>"
OPT="$OPT $param"
;;
*)
echo ">>> Parsing mismatch: $param"
;;
esac
done
}
handle_cli_optparams () {
for opt in $OPT; do
case "$opt" in
--force|-f)
OPT_FORCE=1
;;
--file=*|-f=*)
OPT_FILE=1
FILE=${opt#*=}
;;
--flags=*)
OPT_FLAGS=1
FLAGS=${opt#*=}
;;
*)
echo ">>> Unknown/unimplemented option specifier: $opt"
;;
esac
done
}
invoke_func __init_system
invoke_func handle_cli_harmonizing "$@"
invoke_func handle_cli_parsing ${cli_adjusted}
invoke_func handle_cli_optparams
echo "DEBUG[ CLI]: CMD=$CMD SPEC=$SPEC"
echo "DEBUG[TOGGLE]: FORCE=$OPT_FORCE FILE=$OPT_FILE FLAGS=$OPT_FLAGS"
echo "DEBUG[ OPTS]: FILE=$FILE FLAGS=$FLAGS"
Expected ouput:
$ ./cli.sh data i -f -f="./path/to/file.txt" --flags="--a --b"
DEBUG[ CLI]: CMD=data SPEC=import
DEBUG[TOGGLE]: FORCE=1 FILE=1 FLAGS=1
DEBUG[ OPTS]: FILE=./path/to/file.txt FLAGS=--a --b
Current output:
$ ./cli.sh data i -f -f="./path/to/file.txt" --flags="--a --b"
>>> Unknown/unimplemented option specifier: --b
DEBUG[ CLI]: CMD=data SPEC=import
DEBUG[TOGGLE]: FORCE=1 FILE=1 FLAGS=1
DEBUG[ OPTS]: FILE=./path/to/file.txt FLAGS=--a
It's probably some quoting issue or a missing set
, however I seem to be stuck here, and I'd really prefer to keep the simplistic approach I have taken.
I'd be especially stocked if someone would additionally offer a shell-only (no bashims) version for portability concerns, but that's icing on the cake. Mind you, this is only a very simple part of the whole parser I have written; enough to exhibit my current challenge.
I have read all the suggested linked issues while writing this question, and I have pondered for a while on How do I parse command line arguments in Bash?.
Update (2020/09/12): Even though this question has prematurely been downvoted, I found an elegant and more flexible solution, and have posted it as an answer below. It still beats getopt or any other approach I have seen concerning my specific requirements to command line parsing.