Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash subshell/pipelines - which parts are executing in subshells?

Tags:

bash

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.

like image 659
James Polley Avatar asked Feb 02 '12 08:02

James Polley


People also ask

What is a bash subshell?

< 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}

Does piping create a subshell?

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.

What are Subshells in Linux?

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.

How is subshell implemented?

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.


2 Answers

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.

like image 68
nos Avatar answered Oct 21 '22 21:10

nos


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.

like image 24
krlmlr Avatar answered Oct 21 '22 21:10

krlmlr