Linux Basic — Subshells & Grouping
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
cdso the rest of the script keeps its working directory - Localise
set -e/set -ufor a risky block - Run a function with a temporary
umaskorIFS - 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/whilechain - 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