Skip to content
Linux Basics: Limit Temporary Shell Changes with Bash Subshells

Linux Basics: Limit Temporary Shell Changes with Bash Subshells

October 20, 2026

Parentheses run a group of commands in a subshell. They are useful when a short operation needs a different directory, shell option, or variable value without leaving that shell state changed afterward.

These examples were tested on Linux with GNU Bash 5.2.37. Use Bash rather than sh. No administrator access is needed.

Prepare a scratch directory

You need Bash, a text editor, and the mkdir, mktemp, rm, and rmdir utilities. Keep using the same terminal. Save each named source file in this scratch directory before running its command. Calling bash explicitly avoids changing executable permissions.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Change directory for one operation

Save this as subshell-directory.sh:

#!/usr/bin/env bash
original=$PWD
mkdir -- scoped-work || exit 1
(
  cd -- scoped-work || exit 1
  printf 'Inside: %s\n' "${PWD##*/}"
) || exit 1
if [[ $PWD == "$original" ]]; then
  printf 'Original directory preserved\n'
fi
rmdir -- scoped-work

Run it:

bash subshell-directory.sh

Output:

Inside: scoped-work
Original directory preserved

The directory change ends with the subshell. The guards prevent continuing after a failed directory operation. The directory itself is shared filesystem state, so it still needs explicit removal. Subshell isolation is not a filesystem sandbox or an undo feature.

2. Enable an option for a single filename expansion

Save this as subshell-options.sh:

#!/usr/bin/env bash
shopt -u nullglob
set -- missing-*.txt
printf 'Before: %d argument\n' "$#"
(
  shopt -s nullglob
  set -- missing-*.txt
  printf 'Inside: %d arguments\n' "$#"
)
set -- missing-*.txt
printf 'After: %d argument\n' "$#"

Run it:

bash subshell-options.sh

Output:

Before: 1 argument
Inside: 0 arguments
After: 1 argument

The scratch directory contains no matching filenames. With nullglob off, the unmatched pattern stays one literal argument. With it on, the pattern disappears. The option change is confined to the subshell. The script explicitly starts with nullglob off so the demonstration is predictable.

3. Keep a child’s variables and exit separate

Save this as subshell-state.sh:

#!/usr/bin/env bash
stage=planned
(
  stage=testing
  printf 'Inside: %s\n' "$stage"
  exit 7
)
status=$?
printf 'Parent: %s; child status: %d\n' "$stage" "$status"
{
  stage=ready
}
printf 'After braces: %s\n' "$stage"

Run it:

bash subshell-state.sh

Output:

Inside: testing
Parent: planned; child status: 7
After braces: ready

The child’s exit 7 ends that subshell, and its status is available to the parent. Its variable assignment does not change the parent’s value. A brace group runs in the current shell, so the later assignment does persist. This script intentionally handles the child’s nonzero status without enabling set -e.

Clean up

In the same terminal, remove only the files created for these examples, return to your original directory, and remove the empty scratch directory:

rm -- subshell-directory.sh subshell-options.sh subshell-state.sh
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

References

Acknowledgements

This post was created with assistance from AI.

Last updated on