If you specify that --
marks the end of options and all following arguments will be passed through to the subcommand, you can do something like you might find in man getopts
:
aflag=
bflag=
while getopts ab: name
do
case $name in
a) aflag=1;;
b) bflag=1
bval="$OPTARG";;
?) printf "Usage: %s: [-a] [-b value] args\n" $0
exit 2;;
esac
done
if [ ! -z "$aflag" ]; then
printf "Option -a specified\n"
fi
if [ ! -z "$bflag" ]; then
printf 'Option -b "%s" specified\n' "$bval"
fi
shift $(($OPTIND - 1))
printf "Remaining arguments are: %s\n$*"
Specifically I'm referring to the very end there - getopts
stops processing options when it encounters --
so all of those arguments will remain in $@
. In the example above all of getopts
processed arguments are shift
ed away and the remaining are printed out all at once as $*
. If you handle it similarly, you could make the following work:
/mysqldumpwrapper.sh \
-u username \
-p password \
-h localhost \
-- \
-now --all of these --are passed through
And to call the wrapped application:
mysqldump "$@" \
--host=$MYSQL_HOST \
--user=$MYSQL_USER \
--password=$MYSQL_PASS "$DB" \
> "$FILE_DEST"
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
If you have control over the wrapper program, then make sure that it doesn't invoke a subshell. Deep down, an instruction to execute a program consists of the full path (absolute or relative to the current directory) to the executable, and a list of strings to pass as arguments. PATH lookup, whitespace separating arguments, quoting and control operators are all provided by the shell. No shell, no pain.
For example, with a Perl wrapper, use the list form of
exec
orsystem
. In many languages, call one of theexec
orexecXXX
functions (orunix.exec
or whatever it's called) rather thansystem
, oros.spawn
withshell=False
, or whatever it takes.If the wrapper is a shell script, use
"$@"
to pass down the arguments, e.g.If you have no choice and the wrapper program invokes a shell, you'll need to quote the arguments before passing them to the shell. The easy way to quote arguments is to do the following:
'
(single quote) by the four-character string'\''
. (e.g.don't
becomesdon'\''t
)'
at the beginning of each argument and also at the end of each argument. (e.g. fromdon't
,don'\''t
becomes'don'\''t'
)If you need to do this in a shell wrapper, here's a way.
(Unfortunately, bash's
${VAR//PATTERN/REPLACEMENT}
construct, which should come handy here, requires quirky quoting, and I don't think you can obtain'\''
as the replacement text.)