DJCrashdummy
DJCrashdummy

Reputation: 183

What is the difference between `$( ... )` and `( ... )`?

What is the difference between $( ... ) and ( ... ) and perhaps . (source)?


At the very beginning of writing shell scripts I learned $( ... ) is appropriate if i want to use the output of a command, e.g. for a variable:

size=$( wc -c < /path/to/file )

Then I figured out that I can use ( ... ) if I want a command to be executed (in a sub-shell), wait for it to exit and work with its output file, like:

( echo -e "a lot of data, if everything worked... ;-)" > /path/to/file )

Interestingly, it also works with $( ... ), but gives the somehow cryptic error message scriptname: line 1: : Command not found.:

$( echo -e "a lot of data, if everything worked... ;-)" > /path/to/file )

Why does $( ... ) not work flawlessly for both use cases? Considering the error message, I guess the reason is something like: $( ... ) "forces even an empty output at the shell" although there is none, whereas ( ... ) just executes the command and does not care about the output. Is that right?


I know there is also . (with its alias source) to execute a command in the current shell, which seems to be more similar to $( ... ), concerning the function.

Or are there more differences between . and ( ... ) than just executing the command in the current or a sub-shell that I'm not aware of?

Upvotes: 1

Views: 493

Answers (2)

tripleee
tripleee

Reputation: 189908

Merely running commands, in a subshell or not, does not involve any interaction with the shell itself after it executes those commands.

$ echo echo poo
echo poo

By contrast, a command substitution is evaluated by the shell after it finishes:

$ $(echo echo poo)
poo

$ var=$(echo echo poo)

$ echo "$var"
echo poo

$ $(echo "$var")   # expands to the first line (but is unnecessarily redundant)
poo

$ $var             # more succinct version of the previous line
poo

Of course, if you redirect the output of the command substitution, the shell ends up evaluating an empty string.

The . command (or, in Bash and some other non-POSIX shells, its synonym source) evaluates a sequence of commands in a file. This is in some sense vaguely similar to a command substitution, but the latter lets you capture and manipulate the result of the evaluation (and in some sense the whole purpose of the shell is to evaluate expressions).

A command substitution can really be used to evaluate an expression anywhere, whereas . or a subshell can only be used where the shell accepts a command. Look at these gymnastics:

$ $(echo e)$(echo c)$(echo h)$(echo o) "poo"
poo

$ echo p$(printf x | wc -l | tr -d ' ' | tr '1' 'o')o
poo

$ ech$(echo o poo)
poo

Like you discovered, a subshell can be useful when you want a group of commands to share some file discriptors;

( printf '%s\n' 'Subject: hello' \
    'MIME-version: 1.0' \
    'Content-type: application/octet-stream' \
    'Content-transfer-encoding: base64' \
    ''
  base64 binaryfile ) |
sendmail -oi [email protected]

or if you want to limit the scope of some environment changes;

for subdir in */; do
  ( cd "$subdir"
    condiments=$(cat spice.txt)
    export condiments
    make -s dinner )
# back in parent dir
# $condiments is back to its old value
# probably unset, and not exported
done

Upvotes: 5

William Pursell
William Pursell

Reputation: 212634

$( cmd ) executes cmd and then tries to execute its output. So

$( echo -e "a lot of data, if everything worked... ;-)" > /path/to/file )

executes a command that writes data to /path/to/file and produces no output. The shell takes that output (the empty string) and tries to execute it, producing the error message that you see.

Upvotes: 3

Related Questions