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.
lastpipeoption in bash, for instance, which would runa=3orb=3of your pipelines in the parent shell when active (only takes effect under a number of restrictive circumstances defined in the docs).()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?( ... )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.