Skip to content
AgileLogics
6 min read

Subshells, Grouping, and Process Substitution

Prerequisites: Pipes: connecting commands, Functions

Subshells: ( )

Commands in parentheses run in a subshell — a copy of the shell. Changes inside (variables, cd) don't affect the parent:

mkdir -p build
count=1
(
  cd build
  count=99
  echo "inside: $(basename "$PWD"), count=$count"
)
echo "outside: count=$count"
inside: build, count=99
outside: count=1

Subshells are handy for doing something in another folder without having to cd back. Pipelines and $(...) also run in subshells — which is why a variable changed in cmd | while read ... is lost afterwards.

Grouping: { }

Braces group commands in the current shell — useful for redirecting several commands' output at once:

{
  echo "Report"
  echo "======"
  date -d @0 -u +%F
} > report.txt
cat report.txt
Report
======
1970-01-01

Note the syntax: a space after { and a newline (or ;) before }.

Process substitution: <( )

<(command) makes a command's output look like a file, so you can pass it to programs that expect file names:

printf '%s\n' apple banana cherry > list1.txt
printf '%s\n' banana cherry date > list2.txt

diff <(sort list1.txt) <(sort list2.txt)
echo "---"
comm -12 <(sort list1.txt) <(sort list2.txt)
1d0
< apple
3a3
> date
---
banana
cherry
  • diff compares two sorted outputs directly — no temporary files.
  • comm -12 prints lines common to both.

Keeping variables when reading command output

Process substitution also fixes the lost-variables problem:

total=0
while read -r n; do
  (( total += n ))
done < <(printf '%s\n' 5 10 15)
echo "total=$total"
total=30

done < <(command) feeds the command's output into the loop without a pipe, so the loop runs in the current shell and total survives.

Common mistakes

  • Writing < <(...) without the space between the two < characters.
  • Expecting cd inside $(...) or a pipeline to change the script's folder.