2

Isn't a subshell created when I run the bash command? For example, after executing bash, I cannot access the value of a non-exported variable. In this case, is the environment I switch to with the bash command not a subshell running under the current bash shell?

:~$ value="testing"
:~$ echo $value
testing
:~$ bash
:~$ ps f
  PID TTY      STAT   TIME COMMAND
   82 tty1     S      0:00 -bash
   97 tty1     S      0:00  \_ bash
  124 tty1     R      0:00     \_ ps f
:~$
:~$ echo $value
:~$ exit
exit
:~$ export value
:~$ bash
:~$ echo $value
testing
:~$
testter
  • 1,410
  • Are you referring to some specific definition or description of "subshell"? Maybe seen in some other Q/A on this site, or in a man page, or...? – fra-san Oct 16 '20 at 11:39

2 Answers2

4

No that isn't a subshell. Subshells in bash are marked using the BASH_SUBSHELL variable. This is incremented by 1 for each level of subshell:

$ echo $BASH_SUBSHELL 
0
$ ( echo $BASH_SUBSHELL )
1
$ ( ( echo $BASH_SUBSHELL ) )
2
$ ( ( ( echo $BASH_SUBSHELL ) ) )
3
$ ( ( ( ( echo $BASH_SUBSHELL ) ) ) )
4
$ ( ( ( ( ( echo $BASH_SUBSHELL ) ) ) ) )
5

But, this variable doesn't change if you just launch another shell:

$ echo $BASH_SUBSHELL 
0
$ bash
$ echo $BASH_SUBSHELL 
0

This is because when you run a new bash shell, this is a fully new instance. Yes, exported variables will be inherited because this is a child shell of your original bash instance, but as you can see above, it isn't actually a subshell of it. Note that subshells inherit all variables, not only the exported ones:

$ foo=var
$ ( echo $BASH_SUBSHELL; echo $foo )
1
var
$ bash
$ echo $var ## <-- prints an empty line

This is also explained in the COMMAND EXECUTION ENVIRONMENT of man bash (emphasis mine):

Command substitution, commands grouped with parentheses, and asynchronous commands are invoked in a subshell environment that is a duplicate of the shell environment, except that traps caught by the shell are reset to the values that the shell inherited from its parent at invocation. Builtin commands that are invoked as part of a pipeline are also executed in a subshell environment. Changes made to the subshell environment cannot affect the shell's execution environment.

So, subshell environments are almost exact duplicates of their parent shells, and that includes all variables, not only exported ones.

terdon
  • 242,166
  • 1
    From Bash manual: "A pipeline is a sequence of one or more commands separated by one of the control operators | or |&." "Each command in a pipeline is executed as a separate process (i.e., in a subshell)." From that, I would believe that bash is executed in a subshell. So I'm still confused. But your arguments in the answer seem pretty convincing. (Sorry if I'm being a bit slow, I'm giving my best :) – Quasímodo Oct 16 '20 at 12:02
  • 2
    @Quasímodo Yes, bash is executed in a subshell. The bash process is not a subshell though. To make the subshell that the bash process is running in explicitly visible, think (bash). – Kusalananda Oct 16 '20 at 12:10
  • @Kusalananda Ah, that's it! So the answer to the title is yes, a subshell is created. – Quasímodo Oct 16 '20 at 12:15
  • 2
    @Quasímodo In a sense which is not usable in a practical sort of way, yes. That subshell exits with the bash command terminating. What the user in the question is expecting seems to be that the bash instance is a "subshell" of the initial bash process, which it isn't. It is however running in a subshell, as a child process. As a child process, it does not have access to un-exported variables. – Kusalananda Oct 16 '20 at 12:18
  • 1
    Bash doesn't always increment BASH_SUBSHELL in subshells: bash -c 'echo $BASH_SUBSHELL >&2 | echo $BASH_SUBSHELL'. And yes, those are subshells, acccording both to bash's manpage. (The right hand side of a pipeline may not be a subshell in ksh, etc, but in bash it is; the left hand side is a subshell everywhere). –  Oct 16 '20 at 13:08
  • And this answer is confusing and wrong in general; When bash runs an external command like bash or cat, it does create a subshell, but that subshell is immediately executing another program, and so the process stops being a subshell. It's the very same thing it happens in a command like (cat). –  Oct 16 '20 at 13:28
  • 1
    @ktzap yes, but (at least as far as I can understand) when you are in the shell created by running bash from within another bash instance, then you are not in a "subshell" as bash defines it. Yes, the second bash instance may be technically running in a subshell as far as the parent bash instance is concerned, but as far as the user of the shell is concerned, this is a separate process, that just happens to be a child shell of the original instance. I think much of the problem here comes down to exactly how you define "subshell" and "child shell". – terdon Oct 16 '20 at 13:50
  • 1
    @Quasímodo The Bash manual is not as rigorous as the POSIX specification and it may be read as saying that a simple command, being a single-command pipeline, is executed in a subshell. However, this can't obviously be true because both the standard and the Bash manual clearly state that "Changes made to the subshell environment cannot affect the shell’s execution environment". Running simple commands in subshells would make changing directory or doing variable assignment impossible. – fra-san Oct 18 '20 at 14:19
  • @ commenters: While it may be true that creating a subshell execution environment is equivalent to the forking part in the creation of a separate execution environment, and that a subshell implies a fork in Bash, IMO assuming that any separate process implies a subshell is not really helpful. It'd be better to focus on the abstraction: a subshell environment relates to shell syntax constructs (parentheses, multi-command pipelines, command substitutions, ...) while a separate environment relates to a command being (implemented as) external or not. – fra-san Oct 18 '20 at 14:22
  • 1
    (Sorry for the long commentary, I'm totally fine with it being removed.) Finally, it may be worth mentioning, in this answer, the apparently erratic behavior of BASH_SUBSHELL. For example: echo $BASH_SUBSHELL | cat prints 0 (not expected), { echo $BASH_SUBSHELL; } prints 0 (expected), but { echo $BASH_SUBSHELL; } | cat prints 1 (expected), showing that BASH_SUBSHELL is incremented in pipelines too, just... not always. – fra-san Oct 18 '20 at 14:23
  • @fra-san Indeed, that quote in the Bash manual should probably only address external commands, since builtin commands do not spawn a subshell, as is stated later in the manual. I think the manual is a bit to blame on making such a generalistic statement without caution. – Quasímodo Oct 19 '20 at 11:20
  • @Quasímodo No, "Each command in a pipeline is executed as a separate process" should not refer to external commands, it should say "in a multi-command pipeline". Builtin commands in a multi-command pipeline are run in their own subshells (try strace -e clone,execve -f bash -c 'echo foo | :': you will see two forks, exactly as with strace -e clone,execve -f bash -c '/bin/echo foo | /bin/cat', just without the execve part). – fra-san Oct 19 '20 at 11:33
  • @fra-san Yes, whereas built-in commands in a simple command (not a multi-command pipeline) is executed by the current shell. That is what I was trying to say, but said it wrong, sorry. Saying this too is important, because saying what spawns a subshell does not automatically say what doesn't. – Quasímodo Oct 19 '20 at 11:43
1

Command execution environment section of Bash manual:

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. Unless otherwise noted, the values are inherited from the shell.

  • shell variables and functions marked for export, along with variables exported for the command, passed in the environment

Thus:

  • bash, not being a builtin or shell function, is executed in a subshell.

  • It inherits variables marked for export from its parent shell.

  • Only when value is exported does the bash subshell get it, as your experiments showed.

That does not mean the bash shell you get is a subshell, but that it was executed from a subshell, replacing it, so that the subshell involved is short-lived and is only a middleman.

See the diagram below.

enter image description here

Diagram made with Dia

You type bash and this forks the current shell, creating an identical one. The yellow sibling waits for the orange one, which execs bash. This would happen to any non-builtin command (zsh, g++, firefox...), not only bash. All the environment variables are inherited, but since value is not one of those and was not exported, the blue Bash does not receive it.

Sources:

Quasímodo
  • 18,865
  • 4
  • 36
  • 73