Does the shell creating a subshell require the () Groups command?

In my book (Sobell’s A Practical Guide to Linux, 4e) it is written that

You can use the parentheses control operator to group commands. When you use this technique, the shell creates a copy of itself, called a subshell, for each group. It treats each group of commands as a list and creates a new process to execute each command…

I don’t want to interpret this incorrectly so I figured I’d ask here. Does the creation of a subshell necessarily require the use of these () Groups commands, or is this just a way of ensuring that certain commands run in the same subsell?

Let me perhaps ask by example. Suppose I have commands (executables in PATH) a and b. Is there any difference between the following being entered at the command prompt?

  1. a ; b

  2. (a ; b)

  3. (a) ; (b)

Asked By: EE18

||

Let a equal foo=xyz, and b equal echo $foo. Or rather, let’s just define those as functions:

a() { foo=xyz; }
b() { echo $foo; }

Then, let’s try each variant you show, and in each case, initialize foo to abc first, and print the value of foo at the end. Outputs on the right hand side:

  1. foo=abc; a ; b; echo $foo => xyz, xyz
  2. foo=abc; (a ; b); echo $foo => xyz, abc
  3. foo=abc; (a) ; (b); echo $foo => abc, abc

So, in the first one, the assignment happens at the main level and so is visible to the rest of the script. (The functions use the { ..; } grouping construct, so they run in the main shell.) In the second, the assignment happens in the same subshell as the first printout, but doesn’t affect the rest of the script. And in the third, the assignment happens in the first subshell, and is only visible there, not later in the script.

Then again, you asked about executables in PATH, and since those can’t affect the shell’s execution environment anyway, it doesn’t matter if they’re run in subshells or not. That is, ls is the same as (ls). But
with shell builtins the difference matters. Consider e.g. read (which sets variables), or exit (which exits the (sub)shell).


Command substitutions also run in subshells, so e.g.

foo=abc
echo $(foo=xyz; echo $foo)
echo $foo

prints xyz and abc.

But of course the command substitution syntax also uses parenthesis, so there’s some symmetry. (Then again, (( ... )) is something entirely different.)


Anything that runs commands asynchronously or concurrently with the shell also necessarily starts a subshell, since doing that requires spawning a new process which can’t modify the main shell process.

A common case of that is the pipeline. In foo | bar | doo, both foo and bar run in subshells, and doo may run in a subshell or it may run in the main shell environment.

E.g.

foo=abc
{ foo=xyz; echo $foo; } | cat
echo $foo

prints xyz, abc.

See: Why is my variable local in one 'while read' loop, but not in another seemingly similar loop?

Obviously, explicitly running something in the background with foo &, or with process substitutions (<( foo )), or other such also does start a subshell.


Anyway, the ( .. ) is the one that explicitly starts a subshell for the sake of starting one. With the others, one could say it’s a sort of a side-effect.

Answered By: ilkkachu

These three lines do different things.

[me@here foo]$ echo A-$BASHPID ; echo B-$BASHPID
A-534171
B-534171
[me@here foo]$ (echo A-$BASHPID ; echo B-$BASHPID)
A-534798
B-534798
[me@here foo]$ (echo A-$BASHPID) ; (echo B-$BASHPID)
A-534808
B-534809

The first execute a and then b from the context of the current shell (pid=534171 in this example).
The second creates a new subshell (pid=534798) and then executes a and then b in that new subshell.
The third creates a new subshell (pid=534808) and executes a in it. After that subshell exits, then it create another subshell (pid=534809) and execute b in that subshell.
These subshells generally do not inherit their entire environment from the original shell (for example unexported shell variables, file descriptors, ‘ERR, DEBUG, RETURN trap handling are special cases), and some parts of the subshell environment are explicitly changed (process IDSs, shell history …). It is also not generally possible for commands executing in a subshell to modify the parent shell environment.

[me@here foo]$ A=foo
[me@here foo]$ A=bar
[me@here foo]$ (A=baz)
[me@here foo]$ echo $A
bar

Even a binary executable started from these subshells could detect the differences and behave differently.

Answered By: stevea
Categories: Answers Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.