Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Bash & the POSIX Shell  Module 12 – Processes, jobs and signals

Subshells and the execution environment

A subshell copies the parent shell's variables, functions and directory, runs apart and cannot change the parent: learn when Bash creates one and what survives.

  • Intermediate
  • 10 minutes
  • Examples run with Bash 5.3.20, dash 0.5.13.5, shellcheck 0.11.0 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 10 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Explain what a subshell inherits from its parent and what it cannot change
  • Choose between parentheses and braces for grouping commands
  • Predict which constructs create subshells and what variables survive them

Before you start

A script that changes a variable inside ( x=99 ) and prints it afterwards still sees the old value: x stayed 1. The parentheses ran a subshell: A copy of the shell that runs commands apart from it, as Bash does for each command of a pipeline. It starts with the shell's variables, and every change it makes to them is lost when it exits. More in “Reading files line by line with read”, a copy of the shell that starts with all the parent’s variables and cannot change any of them.

You will see what a subshell copies and what it throws away, learn to pick between ( ... ) and { ...; } for grouping, recognise the constructs that create subshells without spelling them out, and trace the special variables that tell shells apart.

Parentheses run a subshell; braces run in the current shell

Wrapping commands in parentheses makes Bash fork a new process, copy the shell’s state into it and run the commands there. Every change the copy makes, a variable assignment, a cd, a shopt, ends when the copy ends. Braces are different: they group commands without a new process, so every change stays.

The session below sets x=1, changes it inside parentheses and inside braces, and shows what survived:

Parentheses vs braces: what survives each Python · subshell_session.py
"""Subshells: ( ) vs { } and what survives each."""
from session import run, write

run("""
# Set a variable and see it
x=1
echo "x is $x"

# Change x inside parentheses (a subshell)
( x=99; echo "inside ( ): x is $x" )
echo "after ( ): x is $x"

# Change x inside braces (same shell)
{ x=42; echo "inside { }: x is $x"; }
echo "after { }: x is $x"

# cd inside a subshell does not change the parent
pwd
( cd /tmp; echo "inside ( ): pwd is $(pwd)" )
pwd

# cd inside braces changes the current shell
{ cd /tmp; echo "inside { }: pwd is $(pwd)"; }
pwd
cd ~/demo

# BASH_SUBSHELL shows the nesting depth
echo "main shell: BASH_SUBSHELL=$BASH_SUBSHELL"
( echo "one level: BASH_SUBSHELL=$BASH_SUBSHELL" )
( ( echo "two levels: BASH_SUBSHELL=$BASH_SUBSHELL" ) )
""")

Output

# Set a variable and see it
$ x=1
$ echo "x is $x"
x is 1
# Change x inside parentheses (a subshell)
$ ( x=99; echo "inside ( ): x is $x" )
inside ( ): x is 99
$ echo "after ( ): x is $x"
after ( ): x is 1
# Change x inside braces (same shell)
$ { x=42; echo "inside { }: x is $x"; }
inside { }: x is 42
$ echo "after { }: x is $x"
after { }: x is 42
# cd inside a subshell does not change the parent
$ pwd
~/demo
$ ( cd /tmp; echo "inside ( ): pwd is $(pwd)" )
inside ( ): pwd is /tmp
$ pwd
~/demo
# cd inside braces changes the current shell
$ { cd /tmp; echo "inside { }: pwd is $(pwd)"; }
inside { }: pwd is /tmp
$ pwd
/tmp
$ cd ~/demo
# BASH_SUBSHELL shows the nesting depth
$ echo "main shell: BASH_SUBSHELL=$BASH_SUBSHELL"
main shell: BASH_SUBSHELL=0
$ ( echo "one level: BASH_SUBSHELL=$BASH_SUBSHELL" )
one level: BASH_SUBSHELL=1
$ ( ( echo "two levels: BASH_SUBSHELL=$BASH_SUBSHELL" ) )
two levels: BASH_SUBSHELL=2

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 subshell_session.py

Inside ( x=99; ... ) the value was 99, but after the parentheses closed x was back to 1. The braces printed 42 both inside and after, because the assignment happened in the same shell. The same goes for cd: the subshell moved to /tmp and came back to ~/demo, while the braces really moved the shell and it stayed in /tmp until the explicit cd ~/demo.

Step through a subshell’s life and watch what crosses back:

Interactive · message by message A subshell copies the parent's state but cannot change it Watch x=1 go into the subshell and come back unchanged.
A subshell copies the parent's state but cannot change it, step 6 of 6. Messages so far: Parent shell to Subshell: copy; Subshell to Parent shell: status 0.Parent shellx=99, dir=/tmpSubshellgonecopystatus 0

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Parent starts with x=1. The shell has a variable x with the value 1 and the working directory ~/demo.
  2. Parentheses make a copy. ( x=99; cd /tmp ) forks a subshell. It starts as a copy of the parent, with the same x=1 and the same directory ~/demo.
  3. The subshell changes x and cd. Inside the subshell: x=99 and cd /tmp. The copy's x is now 99 and its directory is /tmp. The parent's x is still 1 and its directory is still ~/demo.
  4. The subshell exits. The subshell ends with exit status 0. Its variables and directory were never the parent's, so they are gone when it exits. Only the status crosses back.
  5. The parent is unchanged. echo "$x" prints 1 and pwd prints ~/demo. Nothing the subshell changed reached the parent. This is why ( cd build && make ) leaves the caller's directory unchanged.
  6. Braces keep the changes. With { x=99; cd /tmp; } the commands run in the parent itself, so x becomes 99 and the directory becomes /tmp. Braces group commands without a subshell.

Only two things cross back from a subshell: the bytes it wrote to standard output and its exit status: The number from 0 to 255 every command ends with, kept by the shell in $?. 0 means success; Bash uses 126 for found but not executable and 127 for not found. More in “Your first script: shebang, chmod and running it”. The Bash Reference Manual states the rule in its section on the command execution environment: The state a shell or program runs in: its open files, current directory, umask, traps, variables, functions and options. A subshell copies most of it; a new program inherits only the exported environment.: what a subshell changes “cannot affect the shell’s execution environment”.

Use parentheses to protect the caller

( cd build && make ) runs the build in another directory and leaves the caller’s directory unchanged, without remembering to cd back afterwards.

Braces need a semicolon or newline before the closing brace

The syntax is easy to slip on. Parentheses are operators, recognised as separate tokens even without spaces, so (echo a) parses correctly. Braces are reserved words: they need whitespace around them and a semicolon or newline before the }, so {echo a} is a syntax error and { echo a; } is correct.

Form Runs where Syntax
( list ) a subshell no semicolon needed
{ list; } the current shell semicolon or newline before }

Use braces when you need the side effects to stay, such as a function body or a group whose output goes to one file: { head -n1; tail -n1; } < data.csv reads the first and last lines from one open file. Use parentheses when the side effects must not leak, such as a temporary cd or a variable you want to throw away.

Checkpoint

Check: Parentheses vs braces

  1. One question After x=10; ( x=20; cd /tmp ); echo "$x $PWD", what is printed?

    Choose one answer.

    Show the answer: One question

    Answer: 10 and the original directory

    Parentheses make a subshell. Every assignment and cd inside it is gone when it ends, so x stays 10 and the directory stays unchanged.

Pipelines, command substitution and background jobs all create subshells

A subshell is not only ( ... ). The Bash manual lists every place one is made:

  • Each stage of a pipeline, except possibly the last one when lastpipe: A Bash option, turned on with shopt -s lastpipe, that runs the last command of a pipeline in the current shell when job control is off, as it is in scripts, so a while loop at the end of a pipeline keeps the variables it sets. More in “Shell scripting interview questions, explained” is on and job control is off.
  • A command substitution: $(command), or the older backquoted form: Bash runs the command in a subshell and puts its standard output in place of the substitution, with every trailing newline removed. More in “Command and process substitution” $(...).
  • process substitution: <(command) or >(command): Bash starts the command and puts a file name such as /dev/fd/63 in its place. Reading that file gives the command's output; writing to it feeds the command's input. More in “Command and process substitution” <(...) and >(...).
  • A command followed by & (a background job).
  • A coprocess started with coproc.

The practical consequence is the same in all of them: a variable set inside is gone when the construct ends. The reading files line by line lesson showed a counter that stayed 0 after piping into a while loop, and the returning values from functions lesson showed a function that could not keep its changes when captured with $(...). This lesson is the reason behind both.

The session below shows what happens when a script pipes into a while loop, and what BASH_SUBSHELL says:

A pipeline subshell and subshell vs bash -c Python · implicit_session.py
"""Implicit subshells: pipelines, $(...), <(...) and &."""
from session import run, write

# Write a script that shows pipeline subshells
write("pipe-count.sh", """\
#!/usr/bin/env bash
# Each pipeline stage (except the last with lastpipe) runs in a subshell
count=0
echo -e "one\\ntwo\\nthree" | while read -r line; do
  count=$((count + 1))
  echo "inside loop: count=$count, BASH_SUBSHELL=$BASH_SUBSHELL"
done
echo "after loop: count=$count"
""")

run("""
# Show the script
cat pipe-count.sh

# Run it - the count stays 0 because the while runs in a subshell
bash pipe-count.sh

# A variable inside $(...) does not survive either
outer=10
result=$( inner=20; echo "inner is $inner" )
echo "result is: $result"
echo "inner is: ${inner:-unset}"
echo "outer is: $outer"

# A subshell sees all the parent's variables (even unexported)
secret=42
( echo "subshell sees secret: $secret" )

# But bash -c only sees exported ones
echo "bash -c sees secret: $(bash -c 'echo "${secret:-unset}"')"
export secret
echo "after export: $(bash -c 'echo "${secret:-unset}"')"
""")

Output

# Show the script
$ cat pipe-count.sh
#!/usr/bin/env bash
# Each pipeline stage (except the last with lastpipe) runs in a subshell
count=0
echo -e "one\ntwo\nthree" | while read -r line; do
  count=$((count + 1))
  echo "inside loop: count=$count, BASH_SUBSHELL=$BASH_SUBSHELL"
done
echo "after loop: count=$count"
# Run it - the count stays 0 because the while runs in a subshell
$ bash pipe-count.sh
inside loop: count=1, BASH_SUBSHELL=1
inside loop: count=2, BASH_SUBSHELL=1
inside loop: count=3, BASH_SUBSHELL=1
after loop: count=0
# A variable inside $(...) does not survive either
$ outer=10
$ result=$( inner=20; echo "inner is $inner" )
$ echo "result is: $result"
result is: inner is 20
$ echo "inner is: ${inner:-unset}"
inner is: unset
$ echo "outer is: $outer"
outer is: 10
# A subshell sees all the parent's variables (even unexported)
$ secret=42
$ ( echo "subshell sees secret: $secret" )
subshell sees secret: 42
# But bash -c only sees exported ones
$ echo "bash -c sees secret: $(bash -c 'echo "${secret:-unset}"')"
bash -c sees secret: unset
$ export secret
$ echo "after export: $(bash -c 'echo "${secret:-unset}"')"
after export: 42

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 implicit_session.py

The loop ran three times, and BASH_SUBSHELL inside it was 1. After the loop, count was 0 because the increments happened in a copy. The same pattern explains the $( inner=20; ... ) capture: inner was unset afterwards. The reading files line by line lesson gives three fixes: feeding the loop with done < file, with done < <(command), or enabling lastpipe in a script.

Key idea

Every pipeline stage, every command substitution, every background job and every process substitution runs in a subshell, so a variable it sets is gone when the construct ends.

Checkpoint

Check: Implicit subshells

  1. One question A script runs count=0; seq 1 5 | while read -r n; do count=$((count+1)); done; echo "$count". What does it print?

    Choose one answer.

    Show the answer: One question

    Answer: 0

    Each stage of a pipeline runs in a subshell. The while loop's increments happen in a copy, and the copy ends at done. The parent's count is still 0.

A subshell sees every variable; a new program sees only exported ones

A subshell starts as a copy of the shell, so it has every variable, whether exported or not. A new program such as bash -c or sh -c starts from scratch and receives only the environment variable: A variable that has been exported, so every program the shell starts receives a copy of it in its environment, the list of name=value pairs a process begins with. More in “Variables, export and the environment”, the exported variables.

The session above showed the difference: ( echo "$secret" ) saw the value because the subshell was a copy, while bash -c 'echo "${secret:-unset}"' saw nothing until export secret added it to the environment. The same four readings, side by side:

After secret=42 ( echo "$secret" ) prints bash -c 'echo "$secret"' prints
before export 42 nothing
after export secret 42 42

This is why library functions work in subshells without re-sourcing the file, and why a helper script that needs a variable must have it exported.

Confusing a subshell with a new shell

Wrong: assuming bash script.sh can read your shell’s unexported variables. A child process inherits only the environment, and only export name puts a variable there. Right: export what the child needs, or pass it as an argument.

Checkpoint

Check: Subshell vs new program

  1. One question A caller sets region=mumbai and runs bash helper.sh, which prints nothing for $region. What fixes it?

    Choose one answer.

    Show the answer: One question

    Answer: Export the variable in the caller, or pass it as an argument

    A subshell is a copy and sees every variable, but bash helper.sh starts a new program, which receives only the exported variables. Export the variable or pass it on the command line.

exit inside a subshell leaves only the subshell

When a script runs exit 1, the script ends. When a subshell runs exit 1, only the subshell ends; the parent continues and sees the exit status in $?. This matters for error handling: a library function that calls exit inside a $(...) capture does not end the caller’s script. The script goes on, often with an empty variable where it expected a value.

exit in a subshell and exec replacing the shell Python · exit_exec_session.py
"""Exit inside a subshell leaves only the subshell; exec replaces the shell."""
from session import run, write

# Write a script showing exit in subshell
write("exit-sub.sh", """\
#!/usr/bin/env bash
echo "before subshell"
( echo "inside, about to exit 3"; exit 3 )
echo "after subshell, status was $?"
echo "script continues"
""")

# Write a script showing exec replaces the shell
write("exec-demo.sh", """\
#!/usr/bin/env bash
echo "before exec"
exec echo "this line ran via exec"
echo "this line never runs"
""")

run("""
# exit inside ( ) leaves only the subshell
cat exit-sub.sh
bash exit-sub.sh

# exec CMD replaces the shell entirely
cat exec-demo.sh
bash exec-demo.sh

# exec with only redirections changes the current shell's descriptors
exec 3> logfile.txt
echo "first line" >&3
echo "second line" >&3
exec 3>&-
cat logfile.txt
rm logfile.txt
""")

Output

# exit inside ( ) leaves only the subshell
$ cat exit-sub.sh
#!/usr/bin/env bash
echo "before subshell"
( echo "inside, about to exit 3"; exit 3 )
echo "after subshell, status was $?"
echo "script continues"
$ bash exit-sub.sh
before subshell
inside, about to exit 3
after subshell, status was 3
script continues
# exec CMD replaces the shell entirely
$ cat exec-demo.sh
#!/usr/bin/env bash
echo "before exec"
exec echo "this line ran via exec"
echo "this line never runs"
$ bash exec-demo.sh
before exec
this line ran via exec
# exec with only redirections changes the current shell's descriptors
$ exec 3> logfile.txt
$ echo "first line" >&3
$ echo "second line" >&3
$ exec 3>&-
$ cat logfile.txt
first line
second line
$ rm logfile.txt

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exit_exec_session.py

The subshell printed “about to exit 3” and ended. The script printed “after subshell, status was 3” and continued. This is the mechanism behind the interview question below: a die function that calls exit inside a command substitution ends only that substitution, and the caller runs on with whatever was printed before the exit.

The exec example shows the opposite: exec echo ... replaced the shell with echo, so the next line never ran. exec with only redirections, such as exec 3> file, changes the shell’s own file descriptors and is covered in the file descriptors and exec lesson.

Checkpoint

Check: Exit inside a subshell

  1. One question A script runs ( echo "in"; exit 5 ); echo "out $?". What is printed?

    Choose one answer.

    Show the answer: One question

    Answer: in, then out 5

    exit inside a subshell ends only the subshell. The parent sees the exit status in $? and runs the next command.

Three special variables tell processes apart

The positional and special parameters lesson introduced $$ and BASHPID. Their difference matters inside subshells:

Variable What it holds
$$ The process ID: A number the kernel gives to each running process, unique at any moment and reused after the process ends. Bash keeps it in $$ and BASHPID. of the main shell, unchanged inside subshells
BASHPID The process ID of the process running now
BASH_SUBSHELL How many subshells deep the current command is (0, 1, 2 …)

$$ is useful when every part of one script must agree on one name: myscript.$$ is the same name in the main shell and in its subshells. For a file only this run should read, mktemp is the safer way to get a name. BASHPID tells processes apart: inside a subshell it differs from $$. BASH_SUBSHELL counts the depth without exposing a PID.

The first session showed BASH_SUBSHELL rising from 0 in the main shell to 1 and 2 inside nested parentheses. POSIX specifies $$, and says it keeps the value of the current shell inside a subshell, but it specifies neither BASHPID nor BASH_SUBSHELL, and dash has neither. A script that must run under /bin/sh relies on the logic of where subshells are created rather than on these names.

BASHPID arrived in Bash 4.0

macOS ships /bin/bash 3.2.57, which has $$ and BASH_SUBSHELL but not BASHPID. A script that needs BASHPID should test the version in BASH_VERSINFO first, or run a newer Bash installed beside the system one.

Checkpoint

Check: Telling processes apart: $$, BASHPID, BASH_SUBSHELL

  1. One question A script prints $$ and BASHPID in the main shell, then prints both again inside ( ... ). Which value differs between the two places?

    Choose one answer.

    Show the answer: One question

    Answer: BASHPID differs; $$ stays the same

    $$ holds the main shell's PID and keeps it inside subshells. BASHPID holds the PID of the process running the command, so it differs in every subshell.

Practise: predict what survives

Write a short script in three parts and predict every line it prints before you run it. First, put three lines in a file, set count=0, feed the file into a while read loop with a pipe and echo count after done. Second, feed the same loop with done < file instead and echo count again. Third, end with ( cd /tmp ); pwd followed by { cd /tmp; }; pwd. Then run it and compare the two counts and the two directories with what you expected:

Practice that adapts to you

Practise this lesson

10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 Which of these is correct syntax?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: { echo a; echo b; }

    Braces are reserved words, not operators, so they need whitespace around them and a semicolon or newline before the closing brace.

  2. Question 2 of 10 Which of these create a subshell? Select all that apply.

    Choose every answer that is right.

    Show the answer: Question 2 of 10

    Answer:

    • $(command)
    • ( list )
    • command &

    Braces group commands in the current shell. Parentheses, command substitution and background jobs all create subshells.

  3. Question 3 of 10 After secret=42, which can read the value?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: ( echo "$secret" ) can; bash -c 'echo "$secret"' cannot

    A subshell starts as a copy of the parent with all its variables. A new program such as bash -c sees only exported variables, and secret was not exported.

  4. Question 4 of 10 What does export name do to the variable name?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: Adds it to the environment that child processes receive

    export adds a variable to the environment, the array of name=value strings that every new program receives from exec. Subshells see all variables anyway, so export matters only for new programs such as bash -c.

  5. Question 5 of 10 What does ( ( ( echo "$BASH_SUBSHELL" ) ) ) print?

    Type a number.

    Show the answer: Question 5 of 10

    Answer: 3

    Each pair of parentheses adds one level of subshell nesting. BASH_SUBSHELL counts the depth, so three levels gives 3.

  6. Question 6 of 10 A library function ends with exit 1. A script calls it as result=$(load_settings). What happens?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: The subshell ends; the script continues with an empty result

    A command substitution runs in a subshell. exit ends that subshell; the parent continues. The status is in $?, and the output is whatever was printed before exit.

  7. Question 7 of 10 Inside ( echo $$ ), what is printed?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: The same PID as the parent shell

    $$ is defined as the process ID of the main shell. It stays the same inside subshells. BASHPID, by contrast, gives the current process and changes in subshells.

  8. Question 8 of 10 A deploy script installs packages in web/ and then uploads from the repository root. Whatever happens to the install, the shell must not be left in web/. Which shape is safest?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: ( cd web && npm ci ) && upload

    Parentheses are the tool for a directory change that must not leak, because the subshell ends and takes the cd with it, whatever exit status the commands inside produced. Braces would move the caller's own shell.

  9. Question 9 of 10 What does ( exit 4 ); echo "$?" print?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: 4

    A subshell's exit status is the status of the last command it ran, here exit 4. The parent continues and reads 4 in $?.

  10. Question 10 of 10 After cat log | while read -r line; do n=$((n+1)); done, n is still 0. Which change keeps the count?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: Feed the loop from the file instead: while read -r line; do n=$((n+1)); done < log

    The loop loses its count because a pipeline stage is a subshell. Removing the pipe with done < log keeps the loop in the current shell; done < <(cat log) and the lastpipe option in a script are the other two ways out.

Interview questions

Warm-up (fresher to 2–5 years): what does a subshell copy from its parent, and what can it change there?

A subshell starts as a copy of the parent shell. It has the parent’s variables (exported and unexported), functions, aliases, shell options, the current directory, open file descriptors and the process ID in $$. It cannot change anything in the parent: a variable assignment, a cd, a shopt or an unset inside the subshell is gone when the subshell exits. Only two things cross back to the parent: the bytes the subshell wrote to standard output (if the parent captured them with $(...)) and its exit status. Parentheses ( list ) make a subshell explicitly, but pipelines, command substitutions, process substitutions, background jobs and coprocesses all create one implicitly.

Two harder questions follow, about a library function that calls exit inside a capture and about worker processes that share a temporary file name. Name the one command you would run first in each case, then read its first step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A shared library has die() { echo "FATAL: $*" >&2; exit 1; }. A script calls settings=$(load_settings) where load_settings calls die when the file is missing. The script runs to completion with empty settings and deploys with defaults. What went wrong and how do you fix it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to staff

    Eight workers start as ( process_item "$item" ) & and each writes /tmp/report.$$. Only one report file exists, and workers overwrite each other. Explain the bug and give a portable fix.

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. How do ( list ) and { list; } differ?

    ( list ) runs in a subshell, so every variable, cd and option change is lost when it ends. { list; } runs in the current shell and keeps every change.

  2. Why does { echo hi } fail as a command group?

    The closing brace is a reserved word that Bash recognises only where a command could start, so it needs a semicolon or a newline before it: { echo hi; }.

  3. Which constructs run their commands in a subshell without any parentheses?

    Each stage of a pipeline, except the last one when lastpipe is on and job control is off; a command substitution; a process substitution; a background job started with &; and a coprocess.

  4. Which variables does a subshell see, and which does a new program such as bash -c see?

    A subshell starts as a copy of the parent and sees every variable, exported or not. A new program receives only the exported ones.

  5. What does exit do inside ( ... )?

    It ends only the subshell. The parent carries on and finds the subshell's exit status in $?.

  6. How do $$, BASHPID and BASH_SUBSHELL tell processes apart?

    $$ is the main shell's process ID, the same inside subshells; BASHPID is the ID of the process running now; and BASH_SUBSHELL counts how deeply subshells are nested.

Key takeaways

  • ( list ) runs in a subshell: every variable, cd and option change is lost when it ends; { list; } runs in the current shell and keeps every change.
  • A subshell is created implicitly by a pipeline stage (except the last with lastpipe), a command substitution $(...), a process substitution <(...) or >(...), a background job & and a coprocess.
  • A subshell starts with all the parent’s variables, but a new program such as bash -c sees only exported ones.
  • exit inside a subshell leaves only the subshell; the parent continues and reads the exit status in $?.
  • $$ is the main shell’s PID even inside subshells, BASHPID is the current process and BASH_SUBSHELL counts the nesting depth.

Terms in this lesson

execution environment
The state a shell or program runs in: its open files, current directory, umask, traps, variables, functions and options. A subshell copies most of it; a new program inherits only the exported environment.
process ID
A number the kernel gives to each running process, unique at any moment and reused after the process ends. Bash keeps it in $$ and BASHPID.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.