Subshells / Process Substitution
| Since: | ( ) サブシェル | POSIX(sh互換) |
|---|---|---|
| <( ) プロセス置換 | Bash(bash拡張) |
In shell scripts, you can use the ( commands ) syntax to launch a subshell (child process) and run commands without affecting the current shell environment. Any variable changes or cd directory changes made inside a subshell are never reflected back in the calling shell. Bash also provides process substitution <( commands ), which lets you treat a command's output as if it were a file. The diff <(cmd1) <(cmd2) pattern is a common technique for comparing the output of two commands without creating any temporary files.
Syntax
# ----------------------------------------------- # Subshell: ( commands ) # Runs commands in a child process # Variable scope and current directory are isolated from the caller # ----------------------------------------------- ( command1; command2; command3 ) # ----------------------------------------------- # Process substitution: <( commands ) # Passes command output as a file via a named pipe # Lets you feed command output to commands that expect a file argument # ----------------------------------------------- command <( commands ) # ----------------------------------------------- # Comparing command output with diff (typical usage pattern) # ----------------------------------------------- diff <(cmd1) <(cmd2)
Syntax reference
| Syntax | Description |
|---|---|
( commands ) | Runs commands in a subshell (child process). Variable changes and directory changes are not propagated back to the caller. |
( commands ) > file | Redirects the combined output of multiple commands into a file. |
var=$( commands ) | Command substitution. Captures the subshell's output into a variable (a form of subshell). |
<( commands ) | Process substitution. Makes a command's output available as a file (Bash extension). |
>( commands ) | Write-side process substitution. Passes a file path as input to a command. |
diff <(cmd1) <(cmd2) | Compares the output of two commands with diff without using any temporary files. |
comm <(cmd1 | sort) <(cmd2 | sort) | Sorts both command outputs and classifies lines into common and unique sets. |
Sample code
sg_subshell_scope.sh
#!/bin/bash
# -----------------------------------------------
# Demonstrates subshell variable scope
# using characters from STEINS;GATE
# -----------------------------------------------
# Set current lab member status in variables
LAB_MEMBER="Okabe Rintaro"
DIVERGENCE="0.000000"
echo "=== Before subshell ==="
echo "LAB_MEMBER : ${LAB_MEMBER}"
echo "DIVERGENCE : ${DIVERGENCE}"
echo ""
# -----------------------------------------------
# Change variables inside a subshell
# Changes inside ( ) are not visible to the caller
# -----------------------------------------------
(
echo "=== Inside subshell ==="
LAB_MEMBER="Amane Suzuha" # Overwrite inside the subshell
DIVERGENCE="1.048596" # Change the divergence value only inside the subshell
echo "LAB_MEMBER : ${LAB_MEMBER}"
echo "DIVERGENCE : ${DIVERGENCE}"
cd /tmp
echo "Current directory: $(pwd)"
echo ""
)
echo "=== After subshell (caller) ==="
echo "LAB_MEMBER : ${LAB_MEMBER}" # Still the original value
echo "DIVERGENCE : ${DIVERGENCE}" # Still the original value
echo "Current directory: $(pwd)" # cd inside the subshell does not affect the caller
echo ""
# -----------------------------------------------
# Retrieve the exit status of a subshell
# -----------------------------------------------
echo "=== Subshell exit status ==="
( exit 42 )
echo "Exit status: $?" # Returns 42
$ chmod +x sg_subshell_scope.sh $ ./sg_subshell_scope.sh === Before subshell === LAB_MEMBER : Okabe Rintaro DIVERGENCE : 0.000000 === Inside subshell === LAB_MEMBER : Amane Suzuha DIVERGENCE : 1.048596 Current directory: /tmp === After subshell (caller) === LAB_MEMBER : Okabe Rintaro DIVERGENCE : 0.000000 Current directory: /home/kuu/work === Subshell exit status === Exit status: 42
sg_process_substitution.sh
#!/bin/bash
# -----------------------------------------------
# Demonstrates the use of process substitution <()
# using character data from STEINS;GATE
# -----------------------------------------------
# -----------------------------------------------
# Define the lab member list for the alpha worldline
# -----------------------------------------------
ALPHA_MEMBERS="Okabe Rintaro
Shiina Mayuri
Hashida Itaru
Makise Kurisu
Amane Suzuha"
# -----------------------------------------------
# Define the lab member list for the beta worldline
# -----------------------------------------------
BETA_MEMBERS="Okabe Rintaro
Shiina Mayuri
Hashida Itaru
Faris NyanNyan
Urushibara Ruka"
echo "=== Alpha worldline lab members ==="
echo "${ALPHA_MEMBERS}"
echo ""
echo "=== Beta worldline lab members ==="
echo "${BETA_MEMBERS}"
echo ""
# -----------------------------------------------
# Compare using the diff <(cmd1) <(cmd2) pattern
# Passes both outputs to diff without creating temporary files
# < exists only in the alpha worldline (absent from beta)
# > exists only in the beta worldline (absent from alpha)
# -----------------------------------------------
echo "=== diff: worldline differences ==="
diff \
<(echo "${ALPHA_MEMBERS}" | sort) \
<(echo "${BETA_MEMBERS}" | sort)
echo ""
# -----------------------------------------------
# Extract members common to both alpha and beta with comm
# comm -12: shows only lines present in both inputs
# comm requires sorted input
# -----------------------------------------------
echo "=== comm: members in both worldlines ==="
comm -12 \
<(echo "${ALPHA_MEMBERS}" | sort) \
<(echo "${BETA_MEMBERS}" | sort)
echo ""
# -----------------------------------------------
# comm -23: members only in the left side (alpha worldline)
# -----------------------------------------------
echo "=== Members only in alpha worldline ==="
comm -23 \
<(echo "${ALPHA_MEMBERS}" | sort) \
<(echo "${BETA_MEMBERS}" | sort)
echo ""
# -----------------------------------------------
# comm -13: members only in the right side (beta worldline)
# -----------------------------------------------
echo "=== Members only in beta worldline ==="
comm -13 \
<(echo "${ALPHA_MEMBERS}" | sort) \
<(echo "${BETA_MEMBERS}" | sort)
echo ""
# -----------------------------------------------
# tee >() pattern: write output to two destinations simultaneously
# Here we display alpha worldline members
# while also counting them at the same time
# -----------------------------------------------
echo "=== tee >(): display members and count simultaneously ==="
echo "${ALPHA_MEMBERS}" | tee >(wc -l > /tmp/sg_count.txt)
COUNT=$(cat /tmp/sg_count.txt)
echo "Alpha worldline member count: ${COUNT}"
rm -f /tmp/sg_count.txt
$ chmod +x sg_process_substitution.sh $ ./sg_process_substitution.sh === Alpha worldline lab members === Okabe Rintaro Shiina Mayuri Hashida Itaru Makise Kurisu Amane Suzuha === Beta worldline lab members === Okabe Rintaro Shiina Mayuri Hashida Itaru Faris NyanNyan Urushibara Ruka === diff: worldline differences === < Amane Suzuha < Makise Kurisu > Faris NyanNyan > Urushibara Ruka === comm: members in both worldlines === Hashida Itaru Okabe Rintaro Shiina Mayuri === Members only in alpha worldline === Amane Suzuha Makise Kurisu === Members only in beta worldline === Faris NyanNyan Urushibara Ruka === tee >(): display members and count simultaneously === Okabe Rintaro Shiina Mayuri Hashida Itaru Makise Kurisu Amane Suzuha Alpha worldline member count: 5
Overview
A subshell ( commands ) runs as a child process forked from the current shell. Variable changes, directory changes via cd, and set option changes are never reflected in the parent shell. You can intentionally exploit this "scope isolation" to perform temporary operations without side effects. For example, writing ( cd /some/dir && make ) runs a build in a different directory without changing the caller's current directory.
Process substitution <( commands ) is an extension available in Bash and Zsh that passes a command's standard output as a named pipe (a path of the form /dev/fd/N). This lets you pass command output directly to commands that expect file path arguments, such as diff, comm, and join. The diff <(cmd1) <(cmd2) pattern is widely used for comparing log files and checking configuration file differences because it avoids creating temporary files. The write-side variant >( commands ) can be combined with tee to send output to multiple destinations simultaneously. Process substitution is not part of the POSIX standard, so it cannot be used with #!/bin/sh. Be sure to specify #!/bin/bash explicitly. See also Pipe for a comparison between pipes and subshells.
If you find any errors or copyright issues, please contact us.