The following example returns "a b" instead of expected "a b c":

test() { bash -c "testargs() { echo \$@; }; testargs $@"; }
test "a b" c

It seems it's a quoting issue. How do I solve it without using "$*" instead?


$@ should pretty much only be used in the form "$@", alone in a word. When "stuff$@" is expanded, that results in a list consisting of the first positional parameter with stuff prepended, then the other positional parameters. For example, if the positional parameters are foo and bar, you get two words: stufffoo and bar. That's one of your problems.

Another problem is that with sh -c, the first non-option argument becomes the script name which is available as $0. Subsequent non-option arguments become positional parameters. So sh -c 'echo "$@"' foo bar qux prints bar qux — foo is in $0, bar in $1 and qux in$2.

To pass the arguments of the function to the script, use $@ in the script (not, like you did, in the function — "stuff$@" is expanded in the shell that executes the function, due to the double quotes). And account for $0.

test() { bash -c 'testargs() { echo "$@"; }; testargs "$@"' myscript "$@"; }
  • If the first argument is wasted as $0, then why did it return "a b" instead of "c"? – glarry Oct 4 '15 at 6:58
  • @glarry In your code, a b is the first positional parameter in the function call, and it gets to be part of the argument to the -c option in the call to bash. c is the second positional parameter in the function call, and it's $0 for the call to bash -c …. Your example is rather complex since it involves many successive expansions, I suggest to try simpler variations on it to get a feeling for how it works. – Gilles Oct 4 '15 at 13:10

$@ in double quotes expands to several words. bash -c accepts string as a parameter, but it can be followed by other words which are then assigned to positional parameters starting with $0.

You can therefore send the parameters to the code inside bash -c like this:

Test () { bash -c 'echo $@' -- "$@" ; }
Test "a b" c

This works for you case, too:

Test () { bash -c 'testargs() { echo $@ ; } ; testargs $@' -- "$@" ; }
Test "a b" c

If you want to keep the multiword parameters, add double quotes:

Test () {
    bash -c 'testargs() {
                 for a in "$@" ; do
                     echo "$a"
             testargs "$@"' \
        -- "$@"
Test "a b" c

I used Test, as test already exists and might confuse readers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.