Chapter 21. Subshells

Running a shell script launches a new process, a subshell.

A subshell is a separate instance of the command processor -- the shell that gives you the prompt at the console or in an xterm window. Just as your commands are interpreted at the command-line prompt, similarly does a script batch-process a list of commands. Each shell script running is, in effect, a subprocess (child process) of the parent shell.

A shell script can itself launch subprocesses. These subshells let the script do parallel processing, in effect executing multiple subtasks simultaneously.

   1 #!/bin/bash
   2 # subshell-test.sh
   3 
   4 (
   5 # Inside parentheses, and therefore a subshell . . .
   6 while [ 1 ]   # Endless loop.
   7 do
   8   echo "Subshell running . . ."
   9 done
  10 )
  11 
  12 #  Script will run forever,
  13 #+ or at least until terminated by a Ctl-C.
  14 
  15 exit $?  # End of script (but will never get here).
  16 
  17 
  18 
  19 Now, run the script:
  20 sh subshell-test.sh
  21 
  22 And, while the script is running, from a different xterm:
  23 ps -ef | grep subshell-test.sh
  24 
  25 UID       PID   PPID  C STIME TTY      TIME     CMD
  26 500       2698  2502  0 14:26 pts/4    00:00:00 sh subshell-test.sh
  27 500       2699  2698 21 14:26 pts/4    00:00:24 sh subshell-test.sh
  28 
  29           ^^^^
  30 
  31 Analysis:
  32 PID 2698, the script, launched PID 2699, the subshell.
  33 
  34 Note: The "UID ..." line would be filtered out by the "grep" command,
  35 but is shown here for illustrative purposes.

In general, an external command in a script forks off a subprocess, [1] whereas a Bash builtin does not. For this reason, builtins execute more quickly and use fewer system resources than their external command equivalents.

Command List within 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, variables local to the child process.


Example 21-1. Variable scope in a subshell

   1 #!/bin/bash
   2 # subshell.sh
   3 
   4 echo
   5 
   6 echo "We are outside the subshell."
   7 echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
   8 # Bash, version 3, adds the new         $BASH_SUBSHELL variable.
   9 echo; echo
  10 
  11 outer_variable=Outer
  12 global_variable=
  13 #  Define global variable for "storage" of
  14 #+ value of subshell variable.
  15 
  16 (
  17 echo "We are inside the subshell."
  18 echo "Subshell level INSIDE subshell = $BASH_SUBSHELL"
  19 inner_variable=Inner
  20 
  21 echo "From inside subshell, \"inner_variable\" = $inner_variable"
  22 echo "From inside subshell, \"outer\" = $outer_variable"
  23 
  24 global_variable="$inner_variable"   #  Will this allow "exporting"
  25                                     #+ a subshell variable?
  26 )
  27 
  28 echo; echo
  29 echo "We are outside the subshell."
  30 echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
  31 echo
  32 
  33 if [ -z "$inner_variable" ]
  34 then
  35   echo "inner_variable undefined in main body of shell"
  36 else
  37   echo "inner_variable defined in main body of shell"
  38 fi
  39 
  40 echo "From main body of shell, \"inner_variable\" = $inner_variable"
  41 #  $inner_variable will show as blank (uninitialized)
  42 #+ because variables defined in a subshell are "local variables".
  43 #  Is there a remedy for this?
  44 echo "global_variable = "$global_variable""  # Why doesn't this work?
  45 
  46 echo
  47 
  48 # =======================================================================
  49 
  50 # Additionally ...
  51 
  52 echo "-----------------"; echo
  53 
  54 var=41                                                 # Global variable.
  55 
  56 ( let "var+=1"; echo "\$var INSIDE subshell = $var" )  # 42
  57 
  58 echo "\$var OUTSIDE subshell = $var"                   # 41
  59 #  Variable operations inside a subshell, even to a GLOBAL variable
  60 #+ do not affect the value of the variable outside the subshell!
  61 
  62 
  63 exit 0
  64 
  65 #  Question:
  66 #  --------
  67 #  Once having exited a subshell,
  68 #+ is there any way to reenter that very same subshell
  69 #+ to modify or access the subshell variables?

See also $BASHPID and Example 34-2.

Note

While the $BASH_SUBSHELL internal variable indicates the nesting level of a subshell, the $SHLVL variable shows no change within a subshell.

   1 echo " \$BASH_SUBSHELL outside subshell       = $BASH_SUBSHELL"           # 0
   2   ( echo " \$BASH_SUBSHELL inside subshell        = $BASH_SUBSHELL" )     # 1
   3   ( ( echo " \$BASH_SUBSHELL inside nested subshell = $BASH_SUBSHELL" ) ) # 2
   4 # ^ ^                           *** nested ***                        ^ ^
   5 
   6 echo
   7 
   8 echo " \$SHLVL outside subshell = $SHLVL"       # 3
   9 ( echo " \$SHLVL inside subshell  = $SHLVL" )   # 3 (No change!)

Directory changes made in a subshell do not carry over to the parent shell.


Example 21-2. List User Profiles

   1 #!/bin/bash
   2 # allprofs.sh: Print all user profiles.
   3 
   4 # This script written by Heiner Steven, and modified by the document author.
   5 
   6 FILE=.bashrc  #  File containing user profile,
   7               #+ was ".profile" in original script.
   8 
   9 for home in `awk -F: '{print $6}' /etc/passwd`
  10 do
  11   [ -d "$home" ] || continue    # If no home directory, go to next.
  12   [ -r "$home" ] || continue    # If not readable, go to next.
  13   (cd $home; [ -e $FILE ] && less $FILE)
  14 done
  15 
  16 #  When script terminates, there is no need to 'cd' back to original directory,
  17 #+ because 'cd $home' takes place in a subshell.
  18 
  19 exit 0

A subshell may be used to set up a "dedicated environment" for a command group.
   1 COMMAND1
   2 COMMAND2
   3 COMMAND3
   4 (
   5   IFS=:
   6   PATH=/bin
   7   unset TERMINFO
   8   set -C
   9   shift 5
  10   COMMAND4
  11   COMMAND5
  12   exit 3 # Only exits the subshell!
  13 )
  14 # The parent shell has not been affected, and the environment is preserved.
  15 COMMAND6
  16 COMMAND7
As seen here, the exit command only terminates the subshell in which it is running, not the parent shell or script.

One application of such a "dedicated environment" ir21 echo "c = $c" # c = BB34 22 d=${c/BB/23} # Substitute "23" for "BB". 23  # This makes $d an integer. 24 echo "d = $d" # d = 2334 25 let "d += 1" # 2334 + 1 26 echo "d = $d" # d = 2335 27 echo 28  29  30 # What about null variables? 31 e='' # ... Or e="" ... Or e= 32 echo "e = $e" # e = 33 let "e += 1" # Arithmetic operations allowed on a null variable? 34 echo "e = $e" # e = 1 35 echo # Null variable transformed into an integer. 36  37 # What about undeclared variables? 38 echo "f = $f" # f = 39 let "f += 1" # Arithmetic operations allowed? 40 echo "f = $f" # f = 1 41 echo # Undeclared variable transformed into an integer. 42 # 43 # However ... 44 let "f /= $undecl_var" # Divide by zero? 45 # let: f /= : syntax error: operand expected (error token is " ") 46 # Syntax error! Variable $undecl_var is not set to zero here! 47 # 48 # But still ... 49 let "f /= 0" 50 # let: f /= 0: division by 0 (error token is "0") 51 # Expected behavior. 52  53  54 # Bash (usually) sets the "integer value" of null to zero 55 #+ when performing an arithmetic operation. 56 # But, don't try this at home, folks! 57 # It's undocumented and probably non-portable behavior. 58  59  60 # Conclusion: Variables in Bash are untyped, 61 #+ with all attendant consequences. 62  63 exit $?


Untyped variables are both a blessing and a curse. They permit more flexibility in scripting and make it easier to grind out lines of code (and give you enough rope to hang yourself!). However, they likewise permit subtle errors to creep in and encourage sloppy programming habits.

To lighten the burden of keeping track of variable types in a script, Bash does permit declaring variables.

./usr/share/doc/abs-guide/html/subshells.html0000644000000000000000000004066611754034701020105 0ustar rootroot Subshells