Home > Software engineering >  Why "bash -c" can't receive full list of arguments?
Why "bash -c" can't receive full list of arguments?

Time:11-19

I have next two scripts:

test.sh:

echo "start"
echo $@
echo "end"

run.sh:

echo "parameters from user:"
echo $@
echo "start call test.sh:"
bash -c "./test.sh $@"

Execute above run.sh:

$ ./run.sh 1 2
parameters from user:
1 2
start call test.sh:
start
1
end

You could see although I pass 2 arguments to run.sh, the test.sh just receive the first argument.

But, if I change run.sh to next which just drop bash -c:

echo "parameters from user:"
echo $@
echo "start call test.sh:"
./test.sh $@

The behavior becomes as expected which test.sh receive 2 arguments:

$ ./run.sh 1 2
parameters from user:
1 2
start call test.sh:
start
1 2
end

Question:

For some reason, I have to use bash -c in my full scenario, then could you kindly tell me what's wrong here? How I could fix that?

CodePudding user response:

It is because of the quoting of the arguments is in wrong place. When you run a sequence of commands inside bash -c, think of that as it being a full shell script in itself, and need to pass arguments accordingly. From the bash manual

If Bash is started with the -c option (see Invoking Bash), then $0 is set to the first argument after the string to be executed, if one is present. Otherwise, it is set to the filename used to invoke Bash, as given by argument zero.

But if one notices your command below,

bash -c "./test.sh $@"

when your expectation was to pass the arguments to the test.sh, inside '..', but the $@ inside double-quotes expanded pre-maturely, undergoing word-splitting to produce the first argument value only, i.e. value of $1

But even when you have fixed it by using single quotes as below, it still can't work, because remember the contents passed to -c is evaluated in its own shell context and needs arguments passed explicitly,

set -- 1 2
bash -c 'echo $@'       # Both the cases still don't work, as the script
bash -c 'echo "$@"'     # inside '-c' is still not passed any arguments

To fix, the above, you need an explicit passing of arguments the contents inside -c as below. The _ (underscore) character represents the pathname of the shell invoked to execute the script (in this case bash). More at Bash Variables on the manual

set -- 1 2
bash -c 'printf "[%s]\n" "$@"' _ "$@"
[1]
[2]

So to fix your script, in run.sh, pass the arguments as

bash -c './test.sh "$@"' _ "$@"
  • Related