Is $() a subshell?

$(…) is a subshell by definition: it's a copy of the shell runtime state¹, and changes to the state made in the subshell have no impact on the parent. A subshell is typically implemented by forking a new process (but some shells may optimize this in some cases).

It isn't a subshell that you can retrieve variable values from. If changes to variables had an impact on the parent, it wouldn't be a subshell. It's a subshell whose output the parent can retrieve. The subshell created by $(…) has its standard output set to a pipe, and the parent reads from that pipe and collects the output.

There are several other constructs that create a subshell. I think this is the full list for bash:

  • Subshell for grouping: ( … ) does nothing but create a subshell and wait for it to terminate). Contrast with { … } which groups commands purely for syntactic purposes and does not create a subshell.
  • Background: … & creates a subshell and does not wait for it to terminate.
  • Pipeline: … | … creates two subshells, one for the left-hand side and one for the right-hand side, and waits for both to terminate. The shell creates a pipe and connects the left-hand side's standard output to the write end of the pipe and the right-hand side's standard input to the read end. In some shells (ksh88, ksh93, zsh, bash with the lastpipe option set and effective), the right-hand side runs in the original shell, so the pipeline construct only creates one subshell.
  • Command substitution: $(…) (also spelled `…`) creates a subshell with its standard output set to a pipe, collects the output in the parent and expands to that output, minus its trailing newlines. (And the output may be further subject to splitting and globbing, but that's another story.)
  • Process substitution: <(…) creates a subshell with its standard output set to a pipe and expands to the name of the pipe. The parent (or some other process) may open the pipe to communicate with the subshell. >(…) does the same but with the pipe on standard input.
  • Coprocess: coproc … creates a subshell and does not wait for it to terminate. The subshell's standard input and output are each set to a pipe with the parent being connected to the other end of each pipe.

¹ As opposed to running a separate shell.


From the bash(1) man page in bash version 4.4, "EXPANSION" section, "Command Substitution" subsection:

Bash performs the expansion by executing command in a subshell environment [...]


Yes, ( commands... ) is a bash subshell that will execute commands... in another process.

The only difference when you have $( commands... ) is that this part of code will after execution of commands... be replaced with everything that commands... wrote to stdout.