37

I used one of the following

echo $(stuff)
echo `stuff`

(where stuff is e.g. pwd or date or something more complicated).

Then I was told this syntax is wrong, bad practice, non-elegant, excessive, redundant, overly complicated, cargo cult programming, noobish, naive etc.

But the command does work, so what exactly is wrong with it?

8
  • 11
    It's redundant and therefore should not be used. Compare with Useless Use Of Cat: porkmail.org/era/unix/award.html
    – dr_
    Aug 28, 2018 at 8:20
  • 8
    echo $(echo $(echo $(echo$(echo $(stuff))))) also does work, and still you probably wouldn't use it, right? ;-) Aug 29, 2018 at 7:57
  • 1
    What are you trying to do with that expansion, exactly?
    – curiousguy
    Aug 29, 2018 at 8:08
  • 4
    Also, don't we all agree that it's unwise to confine oneself to an echo chamber?? ;-) Aug 29, 2018 at 8:24
  • 1
    @curiousguy "The standard command language interpreter". I added this to indicate shells compatible and/or derived from sh (e.g. not powershell). It's not about any single shell; it's about the concept of using echo with command substitution this way. Aug 29, 2018 at 8:38

2 Answers 2

69

tl;dr

The sole stuff would most probably work for you.


Full answer

What happens

When you run foo $(stuff), this is what happens:

  1. stuff gets executed, in a subshell, setting it up requires some time;
  2. Its output (stdout), instead of being printed, replaces $(stuff) in the argument of foo; Stderr still goes to /dev/stderr.
  3. The string generated by the execution of stuff and captured by $(...), as in not quoted, is subject to globbing and splitting.
  4. First splitting will break (split) the string at any of the characters in IFS, by default <space><tab><newline>.
  5. Then globbing, in which any *, ? and valid [] will be replaced with a list of matching files.
  6. then foo runs, its command line arguments obviously depend on what stuff returned.

This $(…) mechanism is called "command substitution". In your case the main command is echo which basically prints its command line arguments to stdout separated by a single space. So whatever stuff tries to print to stdout is captured, modified, expanded and given to echo and then printed to stdout by echo.

If you want the output of stuff to be printed to stdout, just run the sole stuff.

The `…` syntax serves the same purpose as $(…) (under the same name: "command substitution"), there are few differences though, so you cannot blindly interchange them. See this FAQ and this question.


Should I avoid echo $(stuff) no matter what?

There is a reason you may want to use echo $(stuff) if you know what you're doing. For the same reason you should avoid echo $(stuff) if you don't really know what you're doing.

The point is stuff and echo $(stuff) are no way equivalent. The latter means calling split+glob operator on the output of stuff with the default value of $IFS. Double quoting the command substitution prevents this. Single quoting the command substitution makes it no longer be a command substitution.

To observe this when it comes to splitting run these commands:

echo "a       b"
echo $(echo "a       b")
echo "$(echo "a       b")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "a       b")'

And for globbing:

echo "/*"
echo $(echo "/*")
echo "$(echo "/*")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'

As you can see echo "$(stuff)" is equivalent(-ish*) to stuff. You could use it but what's the point of complicating things this way?

On the other hand if you want the output of stuff to undergo splitting+globbing then you may find echo $(stuff) useful. It has to be your conscious decision though.

There are commands generating output that should be evaluated (which includes splitting, globbing and more) and run by the shell, so eval "$(stuff)" is a possibility (see this answer). I have never seen a command that needs its output to undergo additional splitting+globbing before being printed. Deliberately using echo $(stuff) seems very uncommon.


What about var=$(stuff); echo "$var"?

Good point. This snippet:

var=$(stuff)
echo "$var"

should be equivalent to echo "$(stuff)" equivalent(-ish*) to stuff. If it's the whole code, just run stuff instead.

If, however, you need to use the output of stuff more than once then this approach

var=$(stuff)
foo "$var"
bar "$var"

is usually better than

foo "$(stuff)"
bar "$(stuff)"

Even if foo is echo and you get echo "$var" in your code, it may be better to keep it this way. Things to consider:

  1. With var=$(stuff) stuff runs once; even if the command is fast, avoiding computing the same output twice is the right thing. Or maybe stuff has effects other than writing to stdout (e.g. creating a temporary file, starting a service, starting a virtual machine, notifying a remote server), so you don't want to run it multiple times.
  2. If stuff generates time-depending or somewhat random output, you may get inconsistent results from foo "$(stuff)" and bar "$(stuff)". After var=$(stuff) the value of $var is fixed and you can be sure foo "$var" and bar "$var" get identical command line argument.

In some cases instead of foo "$var" you may want (need) to use foo $var, especially if stuff generates multiple arguments for foo (an array variable may be better if your shell supports it). Again, know what you're doing. When it comes to echo the difference between echo $var and echo "$var" is the same as between echo $(stuff) and echo "$(stuff)".


*Equivalent(-ish)?

I said echo "$(stuff)" is equivalent(-ish) to stuff. There are at least two issues that make it not exactly equivalent:

  1. $(stuff) runs stuff in a subshell, so it's better to say echo "$(stuff)" is equivalent(-ish) to (stuff). Commands that affect the shell they run in, if in a subshell, don't affect the main shell.

    In this example stuff is a=1; echo "$a":

     a=0
     echo "$(a=1; echo "$a")"      # echo "$(stuff)"
     echo "$a"
    

    Compare it with

     a=0
     a=1; echo "$a"                # stuff
     echo "$a"
    

    and with

     a=0
     (a=1; echo "$a")              # (stuff)
     echo "$a"
    

    Another example, start with stuff being cd /; pwd:

     cd /bin
     echo "$(cd /; pwd)"           # echo "$(stuff)"
     pwd
    

    and test stuff and (stuff) versions.

  2. echo is not a good tool to display uncontrolled data. This echo "$var" we were talking about should have been printf '%s\n' "$var". But since the question mentions echo and since the most probable solution is not to use echo in the first place, I decided not to introduce printf up until now.

  3. stuff or (stuff) will interleave stdout and stderr output, while echo $(stuff) will print all the stderr output from stuff (which runs first), and only then the stdout output digested by echo (which runs last).

  4. $(…) strips off any trailing newline and then echo adds it back. So echo "$(printf %s 'a')" | xxd gives different output than printf %s 'a' | xxd.

  5. Some commands (ls for example) work differently depending if the standard output is a console or not; so ls | cat does not the same ls does. Similarly echo $(ls) will work differently than ls.

    Putting ls aside, in a general case if you have to force this other behavior then stuff | cat is better than echo $(stuff) or echo "$(stuff)" because it doesn't trigger all the other issues mentioned here.

  6. Possibly different exit status (mentioned for completeness of this wiki answer; for details see another answer that deserves credit).

9
  • 6
    One more difference: the stuff way will interleave stdout and stderr output, while echo `stuff` will print all the stderr output from stuff, and only then the stdout output.
    – Ruslan
    Aug 28, 2018 at 5:55
  • 3
    And this is another example for: There can hardly be too many quotes in bash scripts. echo $(stuff) can put you into far more troubles as echo "$(stuff)" if you do not want globbing and expansion explicitly. Aug 28, 2018 at 7:01
  • 2
    One more slight difference: $(…) strips off any trailing newline and then echo adds it back. So echo "$(printf %s 'a')" | xxd gives different output than printf %s 'a' | xxd.
    – derobert
    Aug 28, 2018 at 19:39
  • 1
    You should not forget that some commands (ls for example) work differently depending if the standard output is a console or not; so ls | cat does not the same ls does. And of course echo $(ls) will work differently than ls. Aug 29, 2018 at 5:14
  • @Ruslan The answer is now community wiki. I have added your useful comment to the wiki. Thank you. Aug 29, 2018 at 9:42
6

Another difference: The sub-shell exit code is lost, so the exit code of echo is retrieved instead.

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0
3
  • 3
    Welcome to Super User! Can you explain the difference you are demonstrating? Thanks
    – bertieb
    Aug 29, 2018 at 9:04
  • 4
    I believe that this shows that the return code$? will be the return code of echo (for echo $(stuff)) instead of the one returned by stuff. The stuff function return 1 (exit code), but echo sets it to "0" regardless of the return code of stuff. Still should be in the answer. Aug 29, 2018 at 9:32
  • the return value from the subshell has been lost
    – phuclv
    Aug 29, 2018 at 9:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .