Since this question has been viewed so much (for me at least) but no answers were submitted, passing on the solution adopted...
NOTE
Some functions, like the multi-interface output functions ifHelpShow()
and uiShow()
are used but not included here as their calls contain relevant information but their implementations do not.
###############################################################################
# FUNCTIONS (bash 4.1.0)
###############################################################################
function isOption () {
# isOption "$@"
# Return true (0) if argument has 1 or more leading hyphens.
# Example:
# isOption "$@" && ...
# Note:
# Cannot use ifHelpShow() here since cannot distinguish 'isOption --help'
# from 'isOption "$@"' where first argument in "$@" is '--help'
# Revised:
# 20140117 docsalvage
#
# support both short and long options
[[ "${1:0:1}" == "-" ]] && return 0
return 1
}
function optionArg () {
ifHelpShow "$1" 'optionArg --option "$@"
Echo argument to option if any. Within "$@", option and argument may be separated
by space or "=". Quoted strings are preserved. If no argument, nothing echoed.
Return true (0) if option is in argument list, whether an option-argument supplied
or not. Return false (1) if option not in argument list. See also option().
Examples:
FILE=$(optionArg --file "$1")
if $(optionArg -f "$@"); then ...
optionArg --file "$@" && ...
Revised:
20140117 docsalvage' && return
#
# --option to find (without '=argument' if any)
local FINDOPT="$1"; shift
local OPTION=""
local ARG=
local o=
local re="^$FINDOPT="
#
# echo "option start: FINDOPT=$FINDOPT, o=$o, OPTION=$OPTION, ARG=$ARG, @=$@" >&2
#
# let "$@" split commandline, respecting quoted strings
for o in "$@"
do
# echo "FINDOPT=$FINDOPT, o=$o, OPTION=$OPTION, ARG=$ARG" >&2
# echo " o=$o" >&2
# echo "re=$re" >&2
#
# detect --option and handle --option=argument
[[ $o =~ $re ]] && { OPTION=$FINDOPT; ARG="${o/$FINDOPT=/}"; break; }
#
# $OPTION will be non-null if --option was detected in last pass through loop
[[ ! $OPTION ]] && [[ "$o" != $FINDOPT ]] && { continue; } # is a positional arg (no previous --option)
[[ ! $OPTION ]] && [[ "$o" == $FINDOPT ]] && { OPTION="$o"; continue; } # is the arg to last --option
[[ $OPTION ]] && isOption "$o" && { break; } # no more arguments
[[ $OPTION ]] && ! isOption "$o" && { ARG="$o"; break; } # only allow 1 argument
done
#
# echo "option final: FINDOPT=$FINDOPT, o=$o, OPTION=$OPTION, ARG=$ARG, @=$@" >&2
#
# use '-n' to remove any blank lines
echo -n "$ARG"
[[ "$OPTION" == "$FINDOPT" ]] && return 0
return 1
}
###############################################################################
# MAIN (bash 4.1.0) (excerpt of relevant lines)
###############################################################################
# options
[[ "$@" == "" ]] && { zimdialog --help ; exit 0; }
[[ "$1" == "--help" ]] && { zimdialog --help ; exit 0; }
[[ "$1" == "--version" ]] && { uiShow "version $VERSION\n"; exit 0; }
# options with arguments
TITLE="$(optionArg --title "$@")"
TIP="$( optionArg --tip "$@")"
FILE="$( optionArg --file "$@")"
POSIXly, the parsing for options should stop at --
or at the first non-option (or non-option-argument) argument whichever comes first. So in
cp -R file1 -t /mybackup file2 -f
that's at file1
, so cp
should recursively copy all of file1
, -t
, /mybackup
and file2
into the -f
directory.
GNU getopt(3)
however (that GNU cp
uses to parse options (and here you're using GNU cp
since you're using the GNU-specific -t
option)), unless the $POSIXLY_CORRECT
environment variable is set, accepts options after arguments. So it is actually equivalent to POSIX option style parsing's:
cp -R -t /mybackup -f -- file1 file2
The getopts
shell built-in, even in the GNU shell (bash
) only handles the POSIX style. It also doesn't support long options or options with optional arguments.
If you want to parse the options the same way as GNU cp
does, you'll need to use the GNU getopt(3)
API. For that, if on Linux, you can use the enhanced getopt
utility from util-linux
(that enhanced version of the getopt
command has also been ported to some other Unices like FreeBSD).
That getopt
will rearrange the options in a canonical way which allows you to parse it simply with a while/case
loop.
$ getopt -n "$0" -o t:Rf -- -Rf file1 -t/mybackup file2
-R -f -t '/mybackup' -- 'file1' 'file2'
You'd typically use it as:
parsed_options=$(
getopt -n "$0" -o t:Rf -- "$@"
) || exit
eval "set -- $parsed_options"
while [ "$#" -gt 0 ]; do
case $1 in
(-[Rf]) shift;;
(-t) shift 2;;
(--) shift; break;;
(*) exit 1 # should never be reached.
esac
done
echo "Now, the arguments are $*"
Also note that that getopt
will parse options the same way as GNU cp
does. In particular, it supports the long options (and entering them abbreviated) and honours the $POSIXLY_CORRECT
environment variables (which when set disables support for options after arguments) the same way GNU cp
does.
Note that using gdb and printing the arguments that getopt_long()
receives can help building the parameters to getopt(1)
:
(gdb) bt
#0 getopt_long (argc=2, argv=0x7fffffffdae8, options=0x4171a6 "abdfHilLnprst:uvxPRS:T", long_options=0x417c20, opt_index=0x0) at getopt1.c:64
(gdb) set print pretty on
(gdb) p *long_options@40
$10 = {{
name = 0x4171fb "archive",
has_arg = 0,
flag = 0x0,
val = 97
}, {
name = 0x417203 "attributes-only",
[...]
Then you can use getopt
as:
getopt -n cp -o abdfHilLnprst:uvxPRS:T -l archive... -- "$@"
Remember that GNU cp
's list of supported options may change from one version to the next and that getopt
will not be able to check if you pass a legal value to the --sparse
option for instance.
Best Answer
Your requirements are logically contradictory. Given input like
gitploy up -t 2.0.0 test_repo
, you need to parse the options, and in particular notice the presence of the-t
option and the fact that it takes one argument, in order to identify that the first non-option argument istest_repo
.So first parse the options normally. Then you can know what the first operand is. At that point, process the options. Store the information you need about the options in variables.