Reputation: 183
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
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
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