Well, "$@"
expands to the list of positional parameters, one argument per positional parameter.
When you do:
set '' 'foo bar' $'blah\nblah'
cmd "$@"
cmd
is being invoked with those 3 arguments: the empty string, foo bar
and blah<newline>blah
. The shell will call the execve()
system call with something like:
execve("/path/to/cmd", ["cmd", "", "foo bar", "blah\nblah"], [envvars...]);
If you want to reconstruct a shell command line (that is code in the shell language) that would reproduce that same invocation, you could do something like:
awk -v q="'" '
function shellquote(s) {
gsub(q, q "\\" q q, s)
return q s q
}
BEGIN {
for (i = 1; i < ARGC; i++) {
printf "%s", sep shellquote(ARGV[i])
sep = " "
}
printf "\n"
}' cmd "$@"
Or with zsh
, asking for different types of quotes:
$ set '' 'foo bar' $'blah\nblah'
$ print -r -- cmd "${(q)@}"
cmd '' foo\ bar blah$'\n'blah
$ print -r -- cmd "${(qq)@}"
cmd '' 'foo bar' 'blah
blah'
$ print -r -- cmd "${(qqq)@}"
cmd "" "foo bar" "blah
blah"
$ print -r -- cmd "${(qqqq)@}"
cmd $'' $'foo bar' $'blah\nblah'
Or with zsh
, bash
or ksh93
(here for bash
, YMMV with other shells):
$ set '' 'foo bar' $'blah\nblah'
$ printf cmd; printf ' %q' "$@"; printf '\n'
cmd '' foo\ bar $'blah\nblah'
You could also use the shell's xtrace option that causes the shell to print what it's going to execute:
$ (PS4=; set -x; : cmd "$@")
: cmd '' 'foo bar' 'blah
blah'
Above, we ran the :
no-op command with cmd
and the positional parameters as argument. My shell printed them in a nice quoted fashion suitable for reinput to the shell. Not all shells do that.
Instead of making the list of filenames and patterns as a single string, you could make it an array to begin with:
list=(file1 dir1 "**.data" "**.source")
and then loop over the elements:
args=()
for item in "${list[@]}" ; do
args+=(--filter="+ $item")
done
That would create arguments like --filter=+ file1
, without any quotes inside the argument string. (You don't want the quotes to go to rsync
. It will complain about a filter rule that has them, e.g. rsync "--filter='+ foo'" ...
)
And when you pass the array to the command, make sure to use "${args[@]}"
to pass the array elements as distinct strings:
rsync "${args[@]}" "$srcdir" "$destdir"
In stead of "--filter=+ foo"
, I think you could just use --include=foo
. That would remove one problematic space from the arguments (but would no nothing about spaces or globs in the filename patterns).
In your case, you used set -f
to disable globbing, for i in $list
should work, but since you need an array, you might as well use one to begin with.
More importantly, the assignment args=${arr[*]}
flattens the array to a single string. Now the spaces inside the arguments and the spaces between the arguments are equal, there's just --filter=+ file1 --filter=+ dir1 ...
and the shell has no way of telling the different kinds of whitespace apart. The unquoted expansion $args
will split on any and all white space (which the set -x
output shows, if you care to parse the mess of quotes.)
In effect, all gains from using an array were made moot at this assignment.
Best Answer
If you don't mind being limited to single-letter argument names i.e.
my_script -p '/some/path' -a5
, then in bash you could use the built-ingetopts
, e.g.Then you can do
There is a helpful Small getopts tutorial or you can type
help getopts
at the shell prompt.