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.
< Bash programming. Subshells are one way for a programmer to capture (usually with the intent of processing) the output from a program or script. Commands to be run inside a subshell are enclosed inside single parentheses and preceeded by a dollar sign: DIRCONTENTS=$(ls -l) echoechoIn computing, echo is a command that outputs the strings that are passed to it as arguments. It is a command available in various operating system shells and typically used in shell scripts and batch files to output status text to the screen or a computer file, or as a source part of a pipeline.https://en.wikipedia.org › wiki › Echo_(command)echo (command) - Wikipedia ${DIRCONTENTS}
Solution. Pipelines create subshells. Changes in the while loop do not effect the variables in the outer part of the script, as the while loop is run in a subshell.
Definition: A subshell is a child process launched by a shell (or shell script). A subshell is a separate instance of the command processor -- the shell that gives you the prompt at the console or in an xterm window.
The subshell is started within the same script (or function) as the parent. You do this in a manner very similar to the code blocks we saw in the last chapter. Just surround some shell code with parentheses (instead of curly braces), and that code runs in a subshell.
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 | cat; 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.
Indeed, the braces are executed in a new subshell, but only if piped. The first command is with cat
, the second without:
xxx@yyy:~$ ps; X=PQR; echo $X; { ps; X=ABC; echo $X; } | cat; ps; echo $X
PID TTY TIME CMD
6768 pts/7 00:00:00 bash
13158 pts/7 00:00:00 ps
PQR
PID TTY TIME CMD
6768 pts/7 00:00:00 bash
13159 pts/7 00:00:00 bash
13160 pts/7 00:00:00 cat
13161 pts/7 00:00:00 ps
ABC
PID TTY TIME CMD
6768 pts/7 00:00:00 bash
13162 pts/7 00:00:00 ps
PQR
xxx@yyy:~$ ps; X=PQR; echo $X; { ps; X=ABC; echo $X; }; ps; echo $X
PID TTY TIME CMD
6768 pts/7 00:00:00 bash
13239 pts/7 00:00:00 ps
PQR
PID TTY TIME CMD
6768 pts/7 00:00:00 bash
13240 pts/7 00:00:00 ps
ABC
PID TTY TIME CMD
6768 pts/7 00:00:00 bash
13245 pts/7 00:00:00 ps
ABC
In this case 6768
is the PID of the terminal shell, and 13159
is the PID of the subshell started for the braces.
It seems that {}
are executed in a subshell if (and only if) piped.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With