Linux Basic — Avoid Unnecessary Forking

1 minute read

Avoid Unnecessary Forking

Every subshell, backtick, and external command is a fork() + exec(). Inside a loop of 10,000 iterations, that’s 10,000 forks — and it’s slow.


Subshells: ( ... )

A subshell runs in a child process. Useful for isolation, expensive when overused.

# SLOW — 10k subshells
for i in {1..10000}; do
    (cd /tmp && do_something)
done

# FAST — one cd, manual restore
orig=$PWD
cd /tmp || exit
for i in {1..10000}; do
    do_something
done
cd "$orig" || exit

Or use pushd/popd:

pushd /tmp >/dev/null
for i in {1..10000}; do
    do_something
done
popd >/dev/null

Backticks vs $(...)

Both fork, but $(...) is clearer and nestable:

# old style, hard to nest
result=`command`

# modern, nest freely
result=$(outer $(inner))

Both have the same cost — one fork per substitution.


Pipes Create Subshells

The right-hand side of a pipe runs in a subshell:

# SLOW — cat is an unnecessary fork
cat file | while read line; do
    process "$line"
done

# FAST — redirect from file
while read line; do
    process "$line"
done < file

Every cat file | ... can be ... < file.


echo Piped to a Command

# SLOW — fork for echo
echo "data" | process

# FAST — here-string (no fork)
process <<< "data"

Test for Command Existence

# SLOW — forks which/command
if which grep &>/dev/null; then

# FAST — builtin
if command -v grep &>/dev/null; then

command -v is a Bash builtin — no fork.


Quick Wins

Replace With Why
cat file \| cmd cmd < file no cat fork
echo "$x" \| cmd cmd <<< "$x" no echo fork
$(cd /tmp && cmd) manual cd + restore no subshell
backticks $(...) readability (same cost)

Next → Builtins vs External

Updated: