-1

I stumbled over this: Boiled down the example is

/ # a=$(eval echo hello world)
/ # echo $a
hello world
/ # $(eval echo hello world)
/bin/sh: hello: not found
/ # a=$(hello world)
/bin/sh: hello: not found

I would expect that the first line expands to hello world inside the parentheses and is then expanded the same way the last line is: a=$(hello world). After all help eval tells us:

Combine ARGs into a single string, use the result as input to the shell, and execute the resulting commands.

If eval does execute the resulting command before expansion of $() takes place—which I expect—the result should be an error for hello world is not executable (unless there is an actual hello command)

Why does a=$(eval echo hello world) not error out as expected?

terdon
  • 242,166
karlsebal
  • 815
  • 1
    I would suggest not experimenting with bash features while logged in a root. – doneal24 Sep 17 '21 at 18:22
  • Thx 4 your kind advice but no worry: I ran it inside a docker container. – karlsebal Sep 17 '21 at 18:26
  • I kinda want to ask why you expect that first one would error out. As in, if there's some particular way you think it should work, that would then lead to an error. – ilkkachu Sep 17 '21 at 18:29
  • My train of thought was: eval echo hello world expands to hello world so basically I expected after expansion it would be equal to the last line a=$(hello world). – karlsebal Sep 17 '21 at 18:35
  • @karlsebal When command substitution is expanded $(..) is replaced by the output of the commands ran inside. So the $(..) is no longer present once eval echo is ran – Inian Sep 17 '21 at 18:41

1 Answers1

4

If eval does execute the resulting command before expansion of $() takes place—which I expect—the result should be an error for hello world is not executable (unless there is an actual hello command)

That is not how substitution works. The mere presence of $(..) is what causes the eval to even run


To start with, $(..) Command substitution in bash is a way to capture the output of command run inside parens and storing it in a variable. The $(..) is replaced by output of contents present inside it

  1. The part a=$(eval echo hello world) is executed by the shell in following order. The eval echo hello world is executed simply as echo hello world which runs the echo command and outputs the string hello world which is stored into the variable a.

  2. The part $(eval echo hello world) just returns hello world as explained in 1). But there is no assignment to capture the output of $(..) anywhere. So a literal string is returned back to the shell i.e. hello world. The shell tries to interpret that as a command (the literal hello string). Hence the error you see, that a command not found of the name

  3. The same happens for a=$(hello world) also. As mentioned in 1), the part $(..) runs the contents inside the parens as commands. So the literal string is evaluated again resulting in the same error.

In cases 2) and 3), when the $(..) is expanded, the literal unquoted string hello world was returned back to the shell which was subject to word splitting causing hello and world as two separate words. Thats why you encounter the error that hello was run as a command and not the whole string hello world.


Be cautious in using eval with any command, as simple as even echo. There is a risk of executing dangerous commands when not properly invoked. See Is it always safe to use eval echo?

Inian
  • 12,807
  • @karlsebal, in that particular case, eval echo hello world is the same as echo hello world. But in general, you have to look at the whole thing to see what the extra evaluation step caused by eval does. eval echo '$BASH_VERSION' is not the same as echo '$BASH_VERSION'. – ilkkachu Sep 17 '21 at 19:10