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:
foo=abc; a ; b; echo $foo
=> xyz
, xyz
foo=abc; (a ; b); echo $foo
=> xyz
, abc
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.
$(command)
to create a subshell from this command substitution. Another 'subshell' is if you do:sh -c command
– Gilles Quénot Feb 02 '24 at 20:35a;b
does not create a new shell as we've just discussed, though it does create a couple new processes. Thus is the creation of a new shell/subshell purely relevant as it pertains to the environment in which a given process executes? I don't know too much about Linux/Unix internals, but presumably the virtual "space" for a given process also includes some data from the environment from which it was called and that is where the shell-dependence comes from? – EE18 Feb 02 '24 at 20:40sh -c ...
is not a subshell, but an entirely separate and independent shell. A subshell is a copy of the shell's execution environment, but launching another shell makes no copy. E.g.foo=abc; sh -c 'echo $foo'
prints nothing sincefoo
isn't set in the inner shell (assuming it wasn't exported earlier, which is usually wouldn't be). With a subshell, the value offoo
be visible within the subshell too. – ilkkachu Feb 02 '24 at 20:48export SUBS=foobar; bash -c 'echo $SUBS'
– Gilles Quénot Feb 02 '24 at 21:01