0

This question already has an answer here:

I would like to pass multiple arguments to the Tor browser (firefox) programatically through a function arbitrarily entitled tor. This is so as to command tor search terms and voila! My search terms. When using these three variations,

~/tor-browser_en-US/Browser/start-tor-browser -search "$(echo $@)"

eval "~/tor-browser_en-US/Browser/start-tor-browser -search \"$@\""

~/tor-browser_en-US/Browser/start-tor-browser -search "$@"

only the first two lines seem to perform identically. They result in a single Tor browser window searching for terms of arbitrary textual content. However, the third line results in one new search window per word.

Why this behavior?

Let me clarify. I am not searching for workarounds but rather am just interested in an explanation regarding the bahviour.

marked as duplicate by G-Man, roaima, jimmij, Anthon, chaos Aug 28 '15 at 12:15

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

3

For the third version, you want "$*" not "$@".

Explanation

To illustrate, let's set some positional arguments:

$ set -- arg1 arg2 arg3

Now, let's read them out with your echo formulation:

$ printf "%s\n" "$(echo $@)"
arg1 arg2 arg3

Let's see what "$@" does with them:

$ printf "%s\n" "$@"
arg1
arg2
arg3

The difference is that "$@" expands to three words while "$(echo $@)" expands to a single string. If you want behavior like "$(echo $@)", you should use "$*":

$ printf "%s\n" "$*"
arg1 arg2 arg3

Documentation

From man bash:

"$*" produces a single string:

"$*" is equivalent to "$1c$2c...", where c is the first character of the value of the IFS variable.

By contrast, man bash states that $@ produces a series of words:

"$@" is equivalent to "$1" "$2" ...

Using eval

Observe:

$ echo 'printf "%s\n" "' "$@" '"'
printf "%s\n" " arg1 arg2 arg3 "

When bash evaluates the echo command, "$@" expands to separate words. echo, however, takes those separate words and combines them into one string. We saw this before when looking at "$(echo $@)". eval works similarly. It makes one string out of all its arguments and it evaluates that string. Thus:

$ eval 'printf "%s\n" "' "$@" '"'
 arg1 arg2 arg3 

This is also documented by man bash:

eval [arg ...]
The args are read and concatenated together into a single command. This command is then read and executed by the shell, ...

2

Consider a simpler example to see the difference:

$ set "a b" c "d e"
$ printf "%s\n" "$@"
a b
c
d e

The preceding is what you should use; it's simple, easy to understand, and correct.

$ printf "%s\n" "$(echo $@)"
a b c d e

Here, $@ first expands unquoted (the quotes surrounding the command substitution are separate and not yet applied), so it's equivalent to $(echo a b c d e), and printf has one additional argument after the format string.

$ eval "printf \"%s\n\" \"$@\""
a b c d e

Here, $@ is quoted, but the expansion produces 3 separate arguments to eval (printf "%s\n" a b, c, and d e), which are concatenated into a single command that produces the same output as number 2 above.

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