From what I've read, putting a command in parentheses should run it in a subshell, similar to running a script. If this is true, how does it see the variable x if x isn't exported?

x=1

Running (echo $x) on the command line results in 1

Running echo $x in a script results in nothing, as expected

up vote 110 down vote accepted

A subshell starts out as an almost identical copy of the original shell process. Under the hood, the shell calls the fork system call1, which creates a new process whose code and memory are copies2. When the subshell is created, there are very few differences between it and its parent. In particular, they have the same variables. Even the $$ special variable keeps the same value in subshells: it's the original shell's process ID. Similarly $PPID is the PID of the parent of the original shell.

A few shells change a few variables in the subshell. Bash sets BASHPID to the PID of the shell process, which changes in subshells. Bash, zsh and mksh arrange for $RANDOM to yield different values in the parent and in the subshell. But apart from built-in special cases like these, all variables have the same value in the subshell as in the original shell, the same export status, the same read-only status, etc. All function definitions, alias definitions, shell options and other settings are inherited as well.

A subshell created by (…) has the same file descriptors as its creator. Some other means of creating subshells modify some file descriptors before executing user code; for example, the left-hand side of a pipe runs in a subshell3 with standard output connected to the pipe. The subshell also starts out with the same current directory, the same signal mask, etc. One of the few exceptions is that subshells do not inherit custom traps: ignored signals (trap '' SIGNAL) remain ignored in the subshell, but other traps (trap CODE SIGNAL) are reset to the default action4.

A subshell is thus different from executing a script. A script is a separate program. This separate program might coincidentally be also a script which is executed by the same interpreter as the parent, but this coincidence doesn't give the separate program any special visibility on internal data of the parent. Non-exported variables are internal data, so when the interpreter for the child shell script is executed, it doesn't see these variables. Exported variables, i.e. environment variables, are transmitted to executed programs.

Thus:

x=1
(echo $x)

prints 1 because the subshell is a replication of the shell that spawned it.

x=1
sh -c 'echo $x'

happens to run a shell as a child process of a shell, but the x on the second line has no more connection with the x on the second line than in

x=1
perl -le 'print $x'

or

x=1
python -c 'print x'

1 An exception is the ksh93 shell where the forking is optimised out and most of its side effects are emulated.
2 Semantically, they're copies. From an implementation perspective, there's a lot of sharing going on.
3 For the right-hand side, it depends on the shell.
4 If you test this out, note that things like $(trap) may report the traps of the original shell. Note also that many shells have bugs in corner cases involving traps. For example ninjalj notes that as of bash 4.3, bash -x -c 'trap "echo ERR at \$BASH_SUBSHELL \$BASHPID" ERR; set -E; false; echo one subshell; (false); echo two subshells; ( (false) )' runs the ERR trap from the nested subshell in the “two subshells” case, but not the ERR trap from the intermediate subshell — set -E option should propagate the ERR trap to all subshells but the intermediate subshell is optimized away and so isn't there to run its ERR trap.

  • Doesn't (echo $x) print the value of x because of the parent shell doing variable substitution on $x before forking the sub-shell? – Kusalananda Apr 9 '17 at 13:04
  • 2
    @Kusalananda No. (x=out; (x=in; echo $x)) – Gilles Apr 9 '17 at 13:05
  • 1
    @flow2k This is the order of expansion for things happening at the same level. But you also need to consider how expansion is mixed with evaluation. When expansion requires the evaluation of a nested construct, the inner construct is evaluated first. So, for example, to evaluate echo $(x=2; echo $x), the fragment $(x=2; echo $x) needs to be expanded. This requires evaluating the command x=2; echo $x. The expansion of $x happens during this evaluation, after evaluating the part x=2. – Gilles Mar 3 at 22:08
  • 1
    @flow2k There is no order between parameter expansion and command substitution. Note that this sentence uses semicolons to separate expansion steps, but parameter expansion and command substitution are in the same semicolon-delimited clause (yes, it's subtle). The order matters when one of the parts has a side effect that affects the other part, e.g. (with x unset) echo $(echo foo >somefile)${x-$(cat somefile)} or echo $(echo $x),${x=1}. – Gilles Mar 4 at 0:07
  • 1
    @haccks The definition in the ABS is an approximation, and not a very good one. The examples are good, but the first two lines of that page are so oversimplified that they're wrong. Running a script from another script launches a new process which is not a subshell. In the SUS, the definitions are correct (but not always very easy to understand). ./file is not executed in a subshell. See also unix.stackexchange.com/q/261638 and unix.stackexchange.com/a/157962 – Gilles Mar 12 at 18:52

Obviously, yes, as all the documentation says, a parenthesized command is run in a subshell.

The subshell inherits a copy of all the parent's variables. The difference is that any changes you make in the subshell aren't also made in the parent.

The ksh man page makes this a little clearer than the bash one:

man ksh:

A parenthesized command is executed in a sub-shell without removing non-exported variables.

man bash:

(list)

list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes.

COMMAND EXECUTION ENVIRONMENT

The shell has an execution environment, which consists of the following: [...] shell parameters that are set by variable assignment [...].
Command substitution, commands grouped with parentheses, and asynchronous commands are invoked in a subshell environment that is a duplicate of the shell environment, [...]

  • 2
    This is to be contrasted to When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment that consists of the following., which contains the item: · shell variables and functions marked for export, along with variables exported for the command, passed in the environment (from the same man bash section) which explains why an echo $x-script prints nothing if x is not exported. – Johan E Jun 21 '14 at 23:50

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.