Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do ( ... ) and { ... } interact with subshell creation with pipes in bash?

Tags:

bash

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.

like image 880
vbezhenar Avatar asked Apr 27 '16 17:04

vbezhenar


2 Answers

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.

like image 159
Charles Duffy Avatar answered Nov 14 '22 22:11

Charles Duffy


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

like image 35
anubhava Avatar answered Nov 14 '22 21:11

anubhava