Command Execution – Difference Between () and $() in Shell

command-substitutionpipeshellshell-script

I am currently trying to make a script that creates bytes that will be piped as input to netcat.

Here is the idea of the script:

(perl -e "print \"$BYTES\x00\";

cat file;

perl -e "print \"More bytes\"x16 . \"\r\n\"";) | netcat ip port

I tried using both using a subshell and command substitution (ex. with $()) to execute the commands. However I fail to understand why the output of the script when using command substitution is wrong. I suspect that command substitution incorrectly pipes its output when executing multiple commands. Can someone explain to me why this is so?

EDIT

Here is the variant that used command substitution:

$(perl -e "print \"$BYTES\x00\";

cat file;

perl -e "print \"More bytes\"x16 . \"\r\n\"";) | netcat ip port

Best Answer

Okay, let's break this down. A subshell executes its contents in a chain (i.e., it groups them). This actually makes intuitive sense as a subshell is created simply by surrounding the chain of commands with (). But, aside from the contents of the subshell being grouped together in execution, you can still use a subshell as if it were a single command. That is, a subshell still has an stdin, stdout and stderr so you can pipe things to and from a subshell.

On the other hand, command substitution is not the same thing as simply chaining commands together. Rather, command substitution is meant to act a bit like a variable access but with a function call. Variables, unlike commands, do not have the standard file descriptors so you cannot pipe anything to or from a variable (generally speaking), and the same is true of command substitutions.

To try to make this more clear, what follows are a set of maybe-unclear (but accurate) examples and a set of, what I think may be, more easily-understood examples.

Let's say the date -u command gives the following:

Thu Jul  2 13:42:27 UTC 2015

But, we want to manipulate the output of this command. So, let's pipe it into something like sed:

user@host~> date -u | sed -e 's/ /    /g'
Thu    Jul        2    13:42:27    UTC    2015

Wow, that was fun! The following is completely equivalent to above (barring some environment differences that you can read about in the man pages about your shell):

user@host~> (date -u) | sed -e 's/ /    /g'
Thu    Jul        2    13:42:27    UTC    2015

That should be no surprise since all we did was group date -u. However, if we do the following, we are going to get something that may seem a bit odd at first:

user@host~> $(date -u) | sed -e 's/ /    /g'
command not found: Thu

This is because $(date -u) is equivalent to typing out exactly what date -u outputs. So the above is equivalent to the following:

user@host~> Thu Jul  2 13:42:27 UTC 2015 | sed -e 's/ /    /g'

Which will, of course, error out because Thu is not a command (at least not one I know of); and it certainly doesn't pipe anything to stdout (so sed will never get any input).

But, since we know that command substitutions act like variables, we can easily fix this problem because we know how to pipe the value of a variable into another command:

user@host~> echo $(date -u) | sed -e 's/ /    /g'
Thu    Jul        2    13:42:27    UTC    2015

But, as with any variable in bash, you should probably quote command substitutions with "".

Now, for the perhaps-simpler example; consider the following:

user@host~> pwd
/home/hypothetical
user@host~> echo pwd
pwd
user@host~> echo "$(pwd)"
/home/hypothetical
user@host~> echo "$HOME"
/home/hypothetical
user@host~> echo (pwd)
error: your shell will tell you something weird that roughly means “Whoa! you tried to have me echo something that isn't text!”
user@host~> (pwd)
/home/hypothetical

I am not sure how to describe it any simpler than that. The command substitution works just like a variable access where the subshell still operates like a command.