Chapter 20. Subshells
Running a shell script launches another instance of the
command processor. Just as your commands are interpreted at the
command line prompt, similarly does a script batch process a list
of commands in a file. Each shell script running is, in effect,
a subprocess of the parent shell,
the one that gives you the prompt at the console or in an
xterm window.
A shell script can also launch subprocesses. These
subshells let the script do
parallel processing, in effect executing multiple subtasks
simultaneously.
Command List in
Parentheses
- ( command1; command2; command3; ... )
A command list embedded between
parentheses runs as a
subshell.
| Variables in a subshell are
not visible outside the block of code
in the subshell. They are not accessible to the parent process, to the shell
that launched the subshell. These are, in effect, local variables. |
Example 20-1. Variable scope in a subshell
#!/bin/bash
# subshell.sh
echo
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
# Bash, version 3, adds the new $BASH_SUBSHELL variable.
echo
outer_variable=Outer
(
echo "Subshell level INSIDE subshell = $BASH_SUBSHELL"
inner_variable=Inner
echo "From subshell, \"inner_variable\" = $inner_variable"
echo "From subshell, \"outer\" = $outer_variable"
)
echo
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
echo
if [ -z "$inner_variable" ]
then
echo "inner_variable undefined in main body of shell"
else
echo "inner_variable defined in main body of shell"
fi
echo "From main body of shell, \"inner_variable\" = $inner_variable"
# $inner_variable will show as uninitialized
#+ because variables defined in a subshell are "local variables".
# Is there any remedy for this?
echo
exit 0 |
See also Example 31-2.
+
Directory changes made in a subshell do not carry over to the
parent shell.
Example 20-2. List User Profiles
#!/bin/bash
# allprofs.sh: print all user profiles
# This script written by Heiner Steven, and modified by the document author.
FILE=.bashrc # File containing user profile,
#+ was ".profile" in original script.
for home in `awk -F: '{print $6}' /etc/passwd`
do
[ -d "$home" ] || continue # If no home directory, go to next.
[ -r "$home" ] || continue # If not readable, go to next.
(cd $home; [ -e $FILE ] && less $FILE)
done
# When script terminates, there is no need to 'cd' back to original directory,
#+ because 'cd $home' takes place in a subshell.
exit 0 |
A subshell may be used to set up a "dedicated
environment" for a command group.
COMMAND1
COMMAND2
COMMAND3
(
IFS=:
PATH=/bin
unset TERMINFO
set -C
shift 5
COMMAND4
COMMAND5
exit 3 # Only exits the subshell.
)
# The parent shell has not been affected, and the environment is preserved.
COMMAND6
COMMAND7 |
One application of this is testing whether a variable is defined.
if (set -u; : $variable) 2> /dev/null
then
echo "Variable is set."
fi # Variable has been set in current script,
#+ or is an an internal Bash variable,
#+ or is present in environment (has been exported).
# Could also be written [[ ${variable-x} != x || ${variable-y} != y ]]
# or [[ ${variable-x} != x$variable ]]
# or [[ ${variable+x} = x ]]
# or [[ ${variable-x} != x ]] |
Another application is checking for a lock file:
if (set -C; : > lock_file) 2> /dev/null
then
: # lock_file didn't exist: no user running the script
else
echo "Another user is already running that script."
exit 65
fi
# Code snippet by St�phane Chazelas,
#+ with modifications by Paulo Marcel Coelho Aragao. |
Processes may execute in parallel within different
subshells. This permits breaking a complex task into subcomponents
processed concurrently.
Example 20-3. Running parallel processes in subshells
(cat list1 list2 list3 | sort | uniq > list123) &
(cat list4 list5 list6 | sort | uniq > list456) &
# Merges and sorts both sets of lists simultaneously.
# Running in background ensures parallel execution.
#
# Same effect as
# cat list1 list2 list3 | sort | uniq > list123 &
# cat list4 list5 list6 | sort | uniq > list456 &
wait # Don't execute the next command until subshells finish.
diff list123 list456 |
Redirecting I/O to a subshell uses the "|" pipe
operator, as in ls -al | (command).
| A command block between curly
braces does not launch
a subshell. { command1; command2; command3; ... } |