Issue
In a comment on another post, @JonathanLeffler stated that:
{ ... } | somecommand is run in a sub-shell and doesn't affect the parent shell. Demo:
X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X
(with output PQR, ABC, PQR on three lines)
and indeed:
james@bodacious-wired:tmp$X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X
PQR
ABC
PQR
However, man bash
says that { .. }
does not execute in a subshell:
{ list; }
list is simply executed in the current shell environment. list must be
terminated with a newline or semicolon. This is known as a group command.
So what's going on here? Is man bash
wrong? I know that each part of a pipeline executes in a subshell; but I don't see how that causes the observed behaviour. For instance:
james@bodacious-wired:tmp$X=PQR; echo $X | sed; X=ABC; echo $X | sed; echo $X
PQR
ABC
ABC
Edited to add:
A few people have suggested using echo $$
to show that things are (or are not) parts of a subshell. This is not at all useful, as the $$
gets expanded during the parameter expansion stage, which happens long before any commands are executed.
As an example:
james@bodacious-wired:tmp$echo 1$$; ps; ( echo 2$$; ps ); echo 3$$; ps
11194
PID TTY TIME CMD
1194 ttys000 0:00.22 -bash
21194
PID TTY TIME CMD
1194 ttys000 0:00.22 -bash
7894 ttys000 0:00.00 -bash
31194
PID TTY TIME CMD
1194 ttys000 0:00.22 -bash
james@bodacious-wired:tmp$
You can see that the second invocation of ps
happened inside a subshell, with pid 7894
; but echo 2$$
still shows the value that bash substituted in the variable expansion phase, before it spawned the subshell
For contrast, and demonstrating that { .. }
does not spawn a subshell:
james@bodacious-wired:tmp$echo 1$$; ps; { echo 2$$; ps; }; echo 3$$; ps
11194
PID TTY TIME CMD
1194 ttys000 0:00.22 -bash
21194
PID TTY TIME CMD
1194 ttys000 0:00.22 -bash
31194
PID TTY TIME CMD
1194 ttys000 0:00.23 -bash
Just to prove that @nos is correct, add a pipeline to the above:
james@bodacious-wired:tmp$echo 1$$; ps; { echo 2$$; ps; } | sed ; echo 3$$; ps
11194
PID TTY TIME CMD
1194 ttys000 0:00.25 -bash
21194
PID TTY TIME CMD
1194 ttys000 0:00.25 -bash
7945 ttys000 0:00.00 -bash
7946 ttys000 0:00.00 sed
31194
PID TTY TIME CMD
1194 ttys000 0:00.25 -bash
As expected, the shell spawns two subshells, one for each side of the pipeline.
Solution
Each side of a pipeline becomes a subshell at least.
X=PQR; echo $X; { X=ABC; echo $X; } | cat; echo $X
will make a subshell/process of , atleast { X=ABC; echo $X; }
and cat
.
"Each command in a pipeline is executed as a separate process (i.e., in a subshell)." , from man bash
If you instead do this
X=PQR; echo $X; { X=ABC; echo $X; } ; echo $X
You'll see afterwards that echo $X
shows ABC.
There's other ways that commands are executed in subshells too, e.g. if you background a subcommand: { X=SUB ; sleep 1; } &
, that group will run in a subshell, whereas just { X=SUB ; sleep 1; }
will not.
If you want to group commands that always execute in a subshell, use parenthesis, (X=ABC ; echo $X)
instead of braces.
Answered By - nos Answer Checked By - Katrina (WPSolving Volunteer)