I am trying to write a function that has optional arguments in a way such that the optional arguments are follow by something like -x
, for example
my_function man_arg1 man_arg2 -n opt_arg1 -o opt_arg2 -x opt_arg3
and I want this to also support callings like
my_function man_arg1 man_arg2 -x opt_arg
In other questions I see people suggesting using getopts but in those answers it seems like you have to specify all the optional arguments when calling the function? Also it still seems unclear to me how you would use getopts for this.
CodePudding user response:
I am not sure I understood the question correctly, sorry if I don't answer it...
You could use (for example) getopt(1)
, as below, which will allow -x
option to be anywhere.
Please note that optional arguments (man_arg*
) can also be anywhere.
#! /usr/bin/bash
CMD="${0##*/}"
my_function() {
SOPTS="n:o:x:"
TMP=$(getopt -o "$SOPTS" -n "$CMD" -- "$@") || exit 1
eval set -- "$TMP"
unset TMP
while true; do
case "$1" in
-n)
printf "[-n] argument: %s\n" "$2"
shift
;;
-o)
printf "[-o] argument: %s\n" "$2"
shift
;;
-x)
printf "[-x] argument: %s\n" "$2"
shift
;;
--) # end of options
shift
break
;;
esac
shift
done
nargs=$#
printf "remaining %d args :\n" "$nargs"
for ((i=0; i<nargs; i)); do
printf "%d: %s\n" $((i 1)) "$1"
shift
done
}
my_function "$@"
Examples:
br@lorien:~$ ./test-getopt.bash man_arg1 man_arg2 -n opt_arg1 -o opt_arg2 -x opt_arg3
[-n] argument: opt_arg1
[-o] argument: opt_arg2
[-x] argument: opt_arg3
remaining 2 args :
1: man_arg1
2: man_arg2
br@lorien:~$ ./test-getopt.bash man_arg1 man_arg2 -n opt_arg1 -o opt_arg2 -x opt_arg3 man_arg3 man_arg4
[-n] argument: opt_arg1
[-o] argument: opt_arg2
[-x] argument: opt_arg3
remaining 4 args :
1: man_arg1
2: man_arg2
3: man_arg3
4: man_arg4
br@lorien:~$ ./test-getopt.bash man_arg1 man_arg2 -x opt_arg
[-x] argument: opt_arg
remaining 2 args :
1: man_arg1
2: man_arg2
br@lorien:~$ ./test-getopt.bash -x opt_arg4 man_arg2 -n opt_arg1 -x opt_arg3 man_arg3 man_arg4
[-x] argument: opt_arg4
[-n] argument: opt_arg1
[-x] argument: opt_arg3
remaining 3 args :
1: man_arg2
2: man_arg3
3: man_arg4
EDIT: Rewrote the code into a function, as asked in question.
CodePudding user response:
POSIX convention is that non-option arguments to a command must come after all options and option arguments. The POSIX getopts
utility is designed around this convention, so if you don't want to insist on conventional argument order then getopts
is not a good fit. It might still be possible to make getopts
work in that case, but I wouldn't try to do so.
If you are willing to depend on the GNU version of the getopt
utility (not to be confused with getopts
, with an s
), then it can accommodate you. By default, it recognizes options anywhere on the command line, including after non-option arguments. That might look like this:
args=$(getopt --name "$0" --options n:o:x: -- "$@")
eval set -- "$args"
while [[ $# -gt 0 ]]; do
case "$1" in
-n) n_argument=$2; shift 2;;
-o) o_argument=$2; shift 2;;
-x) x_argument=$2; shift 2;;
*)
# Handle non-option argument $1 ...
shift
;;
esac
done
But note that you can do much the same without getopt
. What getopt
(or getopts
) does for you is:
- normalize ganged options (
-abc
equivalent to-a -b -c
; only for options that do not require arguments) - normalize option / argument separation (
-xfoo
equivalent to-x foo
when optionx
takes an argument) - recognize and accommodate the significance of argument
--
for indicating that only non-option arguments follow - recognize errors in the arguments, especially the omission of option arguments where they are required
- similar to the above for GNU-style long options (GNU
getopt
only)
If you're not interested in any of that, or if you're willing to roll your own for the ones of those that you want, or if you can't rely on having the GNU version of getopt
then you can use a loop similar to the above without first using getopt
to massage the argument list, and without involving getopts
.