Linux Basic — Subshells & Grouping

2 minute read

Subshells ( ... ) & Grouping { ... ; }

Both forms run a block of commands together. The difference is whether they execute in a child process (subshell) or the current shell (group).

Form New process? Side-effects leak out?
( cmds ) yes — subshell no
{ cmds ; } no — same shell yes

Subshells: ( ... )

A subshell inherits everything (vars, functions, cwd) but changes inside don’t propagate back.

echo "before: $PWD"
( cd /tmp && pwd )       # runs in /tmp
echo "after:  $PWD"      # unchanged

Use cases

  • Isolate cd so the rest of the script keeps its working directory
  • Localise set -e / set -u for a risky block
  • Run a function with a temporary umask or IFS
  • Parallelism: ( long_task ) &
# isolate a strict-mode block inside a lax script
( set -euo pipefail
  do_critical_thing
  do_another_thing
)

Cost

A subshell is a real fork(). Inside tight loops it’s measurable — favour grouping when you don’t need isolation.


Grouping: { ... ; }

Braces run commands in the current shell, with shared variables and cwd. The syntax is fiddly:

  • Space after { is required
  • Each command needs ; or newline before }
{ echo "line 1"; echo "line 2"; echo "line 3"; } > out.log

Use cases

  • Apply one redirection to many commands
  • Group commands for an if / while chain
  • Combine output of a sequence into a pipe
# all three lines go into the pipeline
{
    echo "header"
    cat data.txt
    echo "footer"
} | gzip > report.gz

&& / || with grouping

test -d "$dir" || { echo "missing $dir" >&2; exit 1; }

This is the idiomatic one-liner to abort if a precondition fails.


Side-by-Side: Variables

x=1
( x=99 )       # subshell
echo "$x"      # 1

x=1
{ x=99; }      # group
echo "$x"      # 99

Pipelines Always Run in Subshells

The right-hand side of a pipe is a subshell regardless of grouping:

count=0
seq 5 | while read n; do ((count++)); done
echo "$count"   # 0 — `while` ran in a subshell!

Fix with process substitution or lastpipe:

# Fix 1: process substitution
while read n; do ((count++)); done < <(seq 5)
echo "$count"   # 5

# Fix 2: bash-only lastpipe option
shopt -s lastpipe
set +m
seq 5 | while read n; do ((count++)); done
echo "$count"   # 5

When to Use Which

Need Use
Don’t pollute parent shell state ( )
Group commands for one redirection / pipeline { ; }
Run something in parallel ( ) &
Test-then-fail one-liner cmd \|\| { msg; exit 1; }
Loop accumulating to a variable avoid pipe — use < <(cmd)

Next → eval, coprocess & exec

Updated: