7

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.

6
  • 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. Commented Apr 27, 2016 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). Commented Apr 27, 2016 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? Commented Apr 27, 2016 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. Commented Apr 27, 2016 at 17:28
  • Thank you very much, I'll mark your answer as correct if you'll write one. Commented Apr 27, 2016 at 17:30

3 Answers 3

8

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.

Sign up to request clarification or add additional context in comments.

Comments

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.

4 Comments

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, 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.
@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
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.
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.

7 Comments

"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).
@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.
$$ isn't your real PID in a subshell. Use $BASHPID for that, and you'll see that the PIDs aren't actually the same.
@CharlesDuffy Okay, I stay corrected. Never noticed this.
...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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.