6

I'm trying to understand a difference between shell subprocess invocation using round brackets and curly brackets. I thought that curly brackets do not launch a subprocess, but it seems that they do launch a subprocess.

#!/bin/sh

a=1
b=1

( a=2; ) | ( a=3; )
{ b=2; } | { b=3; }

echo "a=$a"
echo "b=$b"

This script prints

a=1
b=1

So it seems that all invocations are run inside subprocesses. Is there any difference between them in that context? I understand that if I would use && and ||, then {..} will not launch a subprocess, but I'm trying to understand how pipes work.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
vbezhenar
  • 11,148
  • 9
  • 49
  • 63
  • 1
    Not "all invocations", but "all pipelines". And even that's not entirely true -- POSIX sh doesn't specify the behavior, and some shells will in some circumstances put some pipe component (first, last, or such) inside the parent process. – Charles Duffy Apr 27 '16 at 17:20
  • ...look at the `lastpipe` option in bash, for instance, which would run `a=3` or `b=3` of your pipelines in the parent shell when active (only takes effect under a number of restrictive circumstances defined in the docs). – Charles Duffy Apr 27 '16 at 17:25
  • Aha, so `()` always spawns a subprocess and `{}` may spawn a subprocess if it's required or may re-use current process if it's possible. Is that correct? – vbezhenar Apr 27 '16 at 17:27
  • 1
    It's close enough to correct, yes. `( ... )` spawns a process of its own volition, `{ ...; }` doesn't spawn a process, but can be used in contexts where something else will [be that a pipe, a coprocess, a `&`, or similar], and won't prevent that behavior. – Charles Duffy Apr 27 '16 at 17:28
  • Thank you very much, I'll mark your answer as correct if you'll write one. – vbezhenar Apr 27 '16 at 17:30
  • Believe it or not, I couldn't find an existing question to mark this as a duplicate of. So I had nothing to do but answer it! – ivan_pozdeev Apr 27 '16 at 18:06

3 Answers3

7

To demonstrate that it's the pipeline itself that's generating the subshell, and that curly braces won't change this either way:

#!/bin/bash

echo "Base: $BASHPID"
( echo "In (): $BASHPID" )   # This will differ from the base
{ echo "In {}: $BASHPID"; }  # This will match the base

# In bash, these will both differ from the base
echo "Pipeline, default config:"
{ echo " X: $BASHPID" >&2; } | { echo " Y: $BASHPID" >&2; }

# This is exactly the same without the {}s
echo "Pipeline, no {}s, default config:"
echo " X: $BASHPID" >&2 | echo " Y: $BASHPID" >&2

# Only the former will differ from the base if running a new enough bash
shopt -s lastpipe
echo "Pipeline, lastpipe enabled:"
{ echo " X: $BASHPID" >&2; } | { echo " Y: $BASHPID" >&2; }

Running this locally with bash 4.3, I get:

Base: 82811
In (): 82812
In {}: 82811
Pipeline, default config:
 X: 82813
 Y: 82814
Pipeline, no {}s, default config:
 X: 82815
 Y: 82816
Pipeline, lastpipe enabled:
 Y: 82811
 X: 82817

Note that since all pipeline components run simultaneously, there's no defined ordering of which of X or Y will emit output first; however, with lastpipe enabled, the last pipeline component is invoked in a shell that's already up and running (doesn't need to be fork()ed off from the main process), which slightly modifies the likelihood of who writes to stdout first.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
3

{ ... } doesn't spawn a sub-shell. What you're seeing is due to the fact you're using | between 2 curly list commands.

It will be evident with this test:

$> b=1

$> echo $BASHPID
4401

$> { echo "X. $BASHPID"; b=2; } | { echo "Y. $BASHPID"; b=3; }
Y. 46902

$> echo $BASHPID
4401

$> declare -p b
declare -- b="1"

You can see that { echo "Y. $BASHPID"; b=3; } gets executed in a different sub-shell hence changes made to b are not reflected in current shell where b is still 1.

anubhava
  • 761,203
  • 64
  • 569
  • 643
  • I'm not following you. You are writing that `{ ... } ` doesn't spawn a sub-shell yet you demonstrated that it spawned a subshell because $BASHPID is different and b variable wasn't changed. – vbezhenar Apr 27 '16 at 17:25
  • 1
    @vbezhenar, it's not the `{ ... }` that spawned the subshell, it's the pipeline. `{ ... }` doesn't spawn a subshell itself, but it also doesn't prevent something else you're doing from creating one. – Charles Duffy Apr 27 '16 at 17:25
  • @vbezhenar: I wrote it clearly at the start that **What you're seeing is due to the fact you're using `|` (pipeline) between 2 curly list commands** – anubhava Apr 27 '16 at 17:32
  • I wanted to understand the difference between those forms when using a pipeline. So it turns out that `()` always spawns a sub-process and `{}` may spawn a sub-process as well (in that case there's no difference) or may run its body in the parent shell (in that case there's a difference because that body may alter parent's variables, etc) and the exact behaviour unspecified and differs between shells and their options. – vbezhenar Apr 27 '16 at 17:36
1

Round brackets execute commands in a subshell. Curly brackets execute commands in the current shell:

$ echo $BASHPID; (TEST=test; echo "sub:$TEST;$BASHPID"; exit 1);\
> { echo "current:$TEST;$BASHPID;$?"; }
2920
sub:test;3700
current:;2920;1

Another difference is that curly braces require spaces between them and the enclosed commands and a semicolon after the last one while round braces don't.

This all is documented at the bash's man page, "Compound commands" section.


One exception (or rather, another aspect) is if you use pipes:

command1 | command2

In this case, both commands are executed in separate subshells - regardless of that they are (and that includes compound commands). The "Pipelines" section of the man page documents this.

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
  • "subshell" does indeed imply "subprocess" -- if there isn't a `fork()` call involved, it's not a subshell. (Granted, there's no `execv()`-family call, which is a critical difference between subshells and other, more conventional kinds of subprocesses). – Charles Duffy Apr 27 '16 at 17:18
  • @CharlesDuffy well, you do see that the reported PID is the same, don't you? That surprised me, too. The important thing of "subshell" is that it is a separate execuion environment. – ivan_pozdeev Apr 27 '16 at 17:21
  • 2
    `$$` isn't your real PID in a subshell. Use `$BASHPID` for that, and you'll see that the PIDs aren't **actually** the same. – Charles Duffy Apr 27 '16 at 17:21
  • 1
    @CharlesDuffy Okay, I stay corrected. Never noticed this. – ivan_pozdeev Apr 27 '16 at 17:23
  • 2
    ...btw, it's not strictly true that `command1 | command2` always runs both components in separate subshells -- see the `lastpipe` option, in which case `command2` can be run directly by the parent shell. – Charles Duffy Apr 27 '16 at 17:48
  • @CharlesDuffy the man page says _"Each command in a pipeline is executed as a separate process (i.e., in a subshell)."_ `lastpipe` is [new in `bash` `4.2`](http://tldp.org/LDP/abs/html/bashver4.html). – ivan_pozdeev Apr 27 '16 at 18:19
  • yes, lastpipe is new -- I reference that via the "new enough bash" qualifier that in my answer -- but it's not *that* new; bash 4.3 has been out for a substantial while now. – Charles Duffy Apr 27 '16 at 18:25