14

In bash, when I run the following command:

sh -c "command"

is there created a subshell and then the command is executed?

My guess is that the command will run in the current shell, but I'm not sure. This guess come from the fact that I already tested using the following commands:

echo $BASHPID, $BASH_SUBSHELL

and

sh -c "echo $BASHPID, $BASH_SUBSHELL"

and the results are the same. But this could be a little misleading as someone told me because the variables may be substituted before the command is executed. Is this the truth?

Radu Rădeanu
  • 2,642
  • 2
  • 26
  • 43
  • 1
    Yes it does. For example see this example: `var=1; sh -c 'var=5 && echo $var'; echo $var`. Variable `$var` is just changed within the subshell. Note the usage of `'` to prevent variable being expanded, as slayed comments in his answer. – fedorqui May 30 '14 at 09:43
  • 4
    Yes, when you use double-quotes in `sh -c "..."`, variables inside those double-quotes are expanded by the parent shell, not the child. Use single-quotes if you don't want that. Also, don't refer to variables starting with `BASH` within a shell started with `sh` -- sh is POSIX sh, not bash, and even when it's symlinked to bash, it disables (some) functionality that isn't in POSIX. If you want to run bash, use `bash -c ...`, not `sh -c ...`. And obviously, any variable whose name starts with `BASH` is a bash feature, not a POSIX sh feature. – Charles Duffy Jun 03 '14 at 19:15
  • 1
    @fedorqui, eh? You should know better than to conflate subshells with subprocesses that happen to be shells (that distinction really being the crux of this question). Subshell-specific signal handling &c. rules specified in POSIX clearly don't apply in the case given here. – Charles Duffy Jun 03 '14 at 19:22
  • @Radu Are you actually wondering whether the `-c` option creates a subshell? Short answer: It just depends on the commands passed to `sh`. They're parsed just as if the text was part of a shell script. – Henk Langeveld Jun 04 '14 at 19:29

7 Answers7

11

I think that with examples it's possible to understand the situation
(in my case sh is a symbolic link to /bin/dash).
I did this tests for sh:

echo $$ ; sh -c 'echo $$ ; sh -c '"'"'echo $$'"'"' '
16102
7569
7570

Three different PID, three different shell. (If there are different shells, there is not a subshell spawn).


In a similar way for BASH

echo $$ $BASHPID, $BASH_SUBSHELL ; bash -c 'echo $$  $BASHPID $BASH_SUBSHELL  ; bash -c '"'"'echo  $$ $BASHPID $BASH_SUBSHELL '"'"' '
16102 16102, 0
10337 10337 0
10338 10338 0

Three different $BASHPID no different $BASH_SUBSHELL (see note below for differences between $$ and $BASHPID).
If we were in a subshell that do not require to be reinitialized, then $$ and $BASHPID should be different.
In the same way $BASH_SUBSHELL is not incremented, it is always 0. So 2 clues to say again that no new subshell are spawned, we have only new shells.


From man bash (4.2.45(1)-release) I report some pertinent parts about when a subshell is spawned:

  1. Each command in a pipeline is executed as a separate process (i.e., in a subshell).

  2. If a command is terminated by the control operator &, the shell executes the command in the background in a subshell. The shell does not wait for the command to finish, and the return status is 0.

    Commands separated by a ; are executed sequentially; the shell waits for each command to terminate in turn. The return status is the exit status of the last command executed. ...

  3. ( list ) list is executed in a subshell environment
    { list; } list is simply executed in the current shell environment.

  4. A coprocess is a shell command preceded by the coproc reserved word. A coprocess is executed asynchronously in a subshell...

  5. $ Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell.

Notes:

  • BASHPID Expands to the process ID of the current bash process. This differs from $$ under certain circumstances, such as subshells that do not require bash to be re-initialized.
  • BASH_SUBSHELL Incremented by one each time a subshell or subshell environment is spawned. The initial value is 0.

  • For the differences between the use of single quote '' an double quote "" you can see this question. Let we remember only that if you write the commands within double quote"" the variables will be evaluated via parameter expansion from the original shell, if extquote is enabled as it is by default from shopt.(cfr. 4.3.2 The shopt builtin in the Bash Reference Manual)

    *extquote* If set, $'string' and $"string" quoting is performed within ${parameter} expansions enclosed in double quotes. This option is enabled by default.

For further references you may find useful e.g.

Community
  • 1
  • 1
Hastur
  • 2,470
  • 27
  • 36
  • It might be worth calling out your conclusions more clearly rather than letting them be inferred from the information given. – Charles Duffy Jun 04 '14 at 15:34
  • @CharlesDuffy I thought that _Three different PID, three different shell was enough clean_, but I edited to make it more clear. (I suppose I couldn't avoid to follow the suggestion of someone with a number of badges comparable with my reputation ;-) ) – Hastur Jun 04 '14 at 16:01
  • @Hastur, Wow! What an answer. It completely turned my understanding inside out and upside down. How could have been all wrong all this time!? Thank you ! – Vince Payandeh Apr 13 '20 at 20:28
  • @VincePayandeh Thanks. Versions, flavors, implementations...something (details) may change over the years, but a test is straightforward to dispel doubts. – Hastur Apr 14 '20 at 07:47
8

I would say not.

A subshell is typically reserved for any instance where the shell process needs to perform an operation in an isolated environment, but with access to (a copy of) the current state of the shell, including all global and local variables.

Examples:

  • Pipelines: echo x | read y
  • Command substitution: line=$( read < file && echo "$REPLY" )

In most cases, this would result in the shell process forking itself.

In recent updates of ksh93, some subshells may not actually fork a new process.

The crux of the matter is that a subshell is always created implicitly.

Running sh -c ... will create a new shell process, which will discard most of the state and start from scratch. That means that normal (local, global) shell variables are gone. Naturally, the new shell will have a copy of all exported variables.


A different interpretation of the question could be Does the -c option fork a new process for running ...? Answer: No. It doesn't. However, certain commands passed into the -c argument may require the shell to spawn a subshell, just as when they'd be part of a script, or typed interactively.

Henk Langeveld
  • 8,088
  • 1
  • 43
  • 57
  • +1 for general accuracy, though I'm curious about the recent ksh93 behavior you refer to. – Charles Duffy Jun 03 '14 at 19:14
  • Korn and Fowler have added optimisations over the years so that short lived snippets of shell won't require a separate process. When I (or anyone here) finds examples we can add them to the answer. – Henk Langeveld Jun 04 '14 at 06:14
7

I made a check of it as well and no, I don't think it (-c) summons a subshell. If we refer to sh itself, the yes sh is a shell that gets summoned, but not if it's about a subshell within sh itself. We can verify this by running a command like:

# bash -c 'pstree -p; echo Value of PPID in Callee: $PPID; echo Callee PID: $BASHPID / $$'; echo "Caller PID: $$"
bash(28860)───bash(17091)───pstree(17092)
Value of PPID in Callee: 28860
Callee PID: 17091 / 17091
Caller PID: 28860

As you can see the called shell (17091) and the caller shell (28860) are both connected directly as child-parent. There's nothing in between them. $BASHPID and $$ are even the same, in which case should be different if you're on a subshell. This just tells that there's no subshell summoned when calling commands with -c.

There's only one special behavior to this, and that is when summoning a single external binary e.g.:

# bash -c 'pstree -p'; echo "Caller PID: $$"
bash(28860)───pstree(17124)
Caller PID: 28860

There bash saves itself from forking and decided to just directly exec() the only command. You might think that perhaps bash always does that to the last command if the command refers to an external executable binary, but no it doesn't:

# bash -c 'echo Value of PPID in Callee: $PPID; echo Callee PID: $BASHPID / $$; pstree -p'; echo "Caller PID: $$"
Value of PPID in Callee: 28860
Callee PID: 17128 / 17128
bash(28860)───bash(17128)───pstree(17129)
Caller PID: 28860

Now about

echo $BASHPID, $BASH_SUBSHELL

and

sh -c "echo $BASHPID, $BASH_SUBSHELL"

and the results are the same.

It should be the same if echo $BASHPID, $BASH_SUBSHELL is executed in the same shell since "echo $BASHPID, $BASH_SUBSHELL" is first expanded by the shell that interprets the command before it's passed as an argument to sh. If BASHPID is let's say 28860 and BASH_SUBSHELL 0, then the expanded value of "echo $BASHPID, $BASH_SUBSHELL" is 'echo 28860, 0' in which case the command would actually be sh -c 'echo 28860, 0'. The proper way to this actually is to use a single quote to allow interpretation only within the context of the new called shell: sh -c 'echo $BASHPID, $BASH_SUBSHELL', although I'm not really sure if the command itself would be helpful for testing.

So basically what I'm saying is that the test echo $BASHPID, $BASH_SUBSHELL + sh -c "echo $BASHPID, $BASH_SUBSHELL" doesn't prove anything if -c summons a subshell or not and that the guy who told you that it could mislead since the variables may be substituted is correct.

Nevertheless, my own test showed that Bash really doesn't summon a subshell with it (-c).

konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • 2
    This is the correct answer. See here for a discussion on the difference between SHLVL and BASH_SUBSHELL (http://www.tldp.org/LDP/abs/html/subshells.html). I almost wrote that you were wrong, because SHLVL DOES increase. But all along, I've misunderstood the meaning of that variable. – SeeJayBee Jun 03 '14 at 20:27
  • @ChrisJ.Breisch, how is this "the" correct answer? I see nothing incorrect in Henk's answer, which is shorter, to-the-point, and contradicts nothing given here while being vastly more readable and easier to consume. (To be clear -- I'd give this one my vote if, and only if, it were restructured for readability). – Charles Duffy Jun 04 '14 at 14:13
  • @CharlesDuffy Thanks for the helpful criticism, but let's leave it to the OP to judge it. I find that my answer can be further clarified grammatically, but so far I find it enough to answer the OP's questions (mind the 's') as I understood it. Choosing to add more possibly helpful details over giving a riskily lacking summary is not a bad thing. – konsolebox Jun 04 '14 at 14:57
  • @CharlesDuffy A bar or a separator is an optional eye-candy. As for starting a summary, I -did- start with a summary; and the most important points are found in the first 3 blocks. Really one may find the way I deliver the answer to be bulky but it's not really something you'd need to take seriously if it's just "the" answer you're really after. Since when has a valid answer become invalid or less valid all because it doesn't pass to standards of SO's beautifully formatted answers. – konsolebox Jun 04 '14 at 15:41
4

Check this:

$ name=foo
$ echo $name
foo
$ sh -c "echo $name"
foo
$ sh -c 'echo $name'

$ 

You need ' in stead of " in your command. Else $name will get evaluated to foo before execution

And to answer your question, yes, it does create/spawn a new shell.

slayedbylucifer
  • 22,878
  • 16
  • 94
  • 123
  • I need a better explanation to understand how this new shell is created/spawned. – Radu Rădeanu Jun 03 '14 at 10:27
  • @RaduRădeanu, it's created the same way any other program is -- with a `fork()` call followed by an `execve()` call, where the first argument in the argv is `sh`, the second is `-c`, and the third is `echo foo`. – Charles Duffy Jun 03 '14 at 19:12
3

sh will spawn a subshell. But the subshell id stays the same

21934 pts/0    Ss     0:00 -bash
21963 pts/0    S      0:00  \_ /usr/bin/sudo -u root -H /bin/bash -c  export AUDITUSER=ch002854; cd $HOME && exec -a '-bash' /bin/bash
22031 pts/0    S      0:00      \_ -bash
 2969 pts/0    S      0:00          \_ sleep 1000
 2993 pts/0    S      0:00          \_ sleep 1000
 3726 pts/0    R+     0:00          \_ ps af

With sh -c it will just run the command. This will reinitialize your environment variables and therefore it resets $BASH_SUBSHELL to its default 0.

# sh -c 'echo $BASHPID, $BASH_SUBSHELL'
12671, 0

While with `` or () you can create a subshell

# (echo $BASHPID, $BASH_SUBSHELL)
13214, 1
Chris
  • 3,581
  • 8
  • 30
  • 51
  • The output is only `,`... So it doesn't help me to understand something. – Radu Rădeanu May 30 '14 at 09:42
  • So, it is about a subprocess, not a subshell... This make somehow sense. Can you explain a little the difference between a subshell and a subprocess? – Radu Rădeanu May 30 '14 at 09:58
  • Definition: A subshell is a child process launched by a shell (or shell script). – Chris May 30 '14 at 09:59
  • It is the same. The reason that in the sh command BASH_SUBSHELL is 0 is because it resets your environment variables. Which will reset BASH_SUBSHELL to 0 again. – Chris May 30 '14 at 10:15
  • @Chris, no; "sh will spawn a subshell" is not correct: A subshell is a new shell *created by a fork with no exec*. Running `sh -c` does an exec. A shell created with `(...)` or to instantiate a pipeline would be a subshell; this is just a subprocess which happens to be a shell. – Charles Duffy Jun 03 '14 at 19:10
  • @Chris, ...see the POSIX sh spec if you want to argue this one -- when it talks about rules for signal handling in subshells, those rules clearly don't apply (and aren't applied) in the case given here. – Charles Duffy Jun 03 '14 at 19:21
2

Regardless

Running this simple line will show you a lot:

$ echo $$;sh -c 'echo $$;ps axfw | grep -B2 $$'
14152
12352
14147 ?        S      0:00 xterm
14152 pts/4    Ss     0:00  \_ bash
12352 pts/4    S+     0:00      \_ sh -c echo $$;ps axfw | grep -B2 $$
12353 pts/4    R+     0:00          \_ ps axfw
12354 pts/4    S+     0:00          \_ grep -B2 12352

This is clearly a child but what's a subshell?

Well, my current running shell pid is 14152

$ echo $$ $BASHPID $BASH_SUBSHELL 
14152 14152 0
$ (echo $$ $BASHPID $BASH_SUBSHELL)
14152 12356 1
$ ( (echo $$ $BASHPID $BASH_SUBSHELL) )
14152 12357 2

Well nice: BASHPID is a dynamic variable which take alway the value of executing pid:

$ echo $BASHPID | sed \$a$BASHPID | sed \$a$BASHPID
12371
12372
12373

Hmmm where is my current working pid?

$ sed "\$a$BASHPID $$" < <(echo "$BASHPID $$"| sed "\$a$BASHPID $$")
12386 14152
12387 14152
14152 14152

Well I've find them!

$ echo $BASHPID $$;(echo $BASHPID $$;ps axfw | grep -B3 $BASHPID)
14152 14152
12469 14152
14152 pts/4    Ss     0:00  \_ bash
12469 pts/4    S+     0:00      \_ bash
12470 pts/4    R+     0:00          \_ ps axfw
12471 pts/4    S+     0:00          \_ grep -B3 12471

Conclusion

When you

$ echo $BASHPID $$ $BASH_SUBSHELL;(echo $BASHPID $$ $BASH_SUBSHELL)
14152 14152 0
12509 14152 1

use parenthesis or pipe (|), you will create subshell which is a forked child of a running shell.

But when you

$ echo $BASHPID $$ $BASH_SUBSHELL;bash -c 'echo $BASHPID $$ $BASH_SUBSHELL'
14152 14152 0
12513 12513 0

You will burn a child, running a shell interpreter, so it's a subshell, but not linked to his parent.

Nota: If the child is not linked to his parent, they use same I/O files descriptors. So if you close your window (pts/4 in my run), you will send a SIGHUP to all process using them.

F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
1

In this answer I'm thinking of subshell the same way as I think of a subprocess.

First off, don't be confused by variable expansion. If you write a variable in double quotes, it will be expanded by the calling shell, not the sh or its -c command. So

sh -c "echo $$" 

gives you the PID of the invoking shell, because it expands $$ before it invokes sh, whereas

sh -c 'echo $$' 

gives you the PID of the sh command that has been invoked, because the single quotes tell the invoking shell to pass the string $$ to sh unchanged.

That said, the general answer to your question is:

  1. The sh command is definitely a subshell, invoked by the parent shell when it sees 'sh'
  2. When you note that the argument to -c is treated by it as a shell script, then you see that you'll get a grand-subshell of the invoked sh, whenever a regular shell script would get one.

More on item 2: Some shell builtins create subshells; others do not. The sh man page talks about this; check out the "if" builtin, or constucts that use backquotes ``. Usage of pipes also causes subshells. You can also deliberately force a subshell by enclosing commands in parentheses. However, enclosing commands in braces {} does NOT of itself cause a subshell.

sh is designed to run commands, so in most cases you will have subshells. Notable exceptions are the 'case' builtin, variable assignment, option setting, and the . command.