简体   繁体   中英

Bash discards command line arguments when passing to another bash shell

I have a big script (call it test ) that, after stripping out the unrelated parts, comes down to just this using which I can explain my question:

#!/bin/bash

bash -c "$@"

This doesn't work as expected. Eg ./test echo hi executes the only the echo and the argument disappears! Testing with various inputs I can see only $1 is passed to bash -c ... and rest are discarded.

But if I use a variable like:

#!/bin/bash

cmd="$@"
bash -c "$cmd"

it works as expected for all inputs.

Questions:

1) I would like to understand why the double quotes don't "pass" the entire command line arguments to bash -c ... . What am I missing here (that it works perfectly fine when using an intermediate variable)?

2) Why does bash discard the rest of the arguments (except $1 ) without any error messages?

For example:

bash -c "ls" -l -a hi hello blah

simply runs echo and hi hello blah doesn't result in any errors at all?

(If possible, please refer to the bash grammar where this behaviour is documented).

You should be using $* instead of $@ to pass command line as string. "$@" expands to multiple quoted arguments and "$*" combines multiple arguments into a single argument.

#!/bin/bash

bash -c "$*"

Problem is with your $@ it executes:

bash -c echo hi

But with $* it executes:

bash -c 'echo hi'

When you use:

cmd="$@"

and use: bash -c "$cmd" it does the same thing for you.

Read: What is the difference between “$@” and “$*” in Bash ?

1) I would like to understand why the double quotes don't "pass" the entire command line arguments to bash -c .... What am I missing here (that it works perfectly fine when using an intermediate variable)?

From info bash @ :

@ ( $@ ) Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, each parameter expands to a separate word. That is, "$@" is equivalent to "$1" "$2" ... .

Thus, bash -c "$@" is equivalent to bash -c "$1" "$2" ... . In the case of ./test echo hi invocation, the expression is expanded to

bash -c "echo" "hi"

2) Why does bash discard the rest of the arguments (except $1) without any error messages?

Bash actually doesn't discard anything. From man bash :

If the -c option is present, then commands are read from the first non-option argument command_string . If there are arguments after the command_string , they are assigned to the positional parameters, starting with $0 .

Thus, for the command bash -c "echo" "hi" , Bash passes "hi" as $0 for the "echo" script.


bash -c "ls" -l -a hi hello blah

simply runs echo and hi hello blah doesn't result in any errors at all?

According to the rules mentioned above, Bash executes "ls" script and passes the following positional parameters to this script:

  • $0 : "-l"
  • $1 : "-a"
  • $2 : "hi"
  • $3 : "hello"
  • $4 : "blah"

Thus, the command actually executes ls , and the positional parameters are unused in the script. You can use them by referencing to the positional parameters, eg:

$ set -x
$ bash -c "ls \$0 \$1 \$3" -l -a hi hello blah

+ bash -c 'ls $0 $1 $3' -l -a hi hello blah
ls: cannot access hello: No such file or directory

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM