I seem to misunderstand the Bash rule for creating a subshell. I thought parentheses always creates a subshell, which runs as its own process.
However, this doesn’t seem to be the case. In Code Snippet A (below), the second sleep command does not run in a separate shell (as determined by pstree in another terminal). However, in Code Snippet B, the second sleep command does run in a separate shell. The only difference between the snippets is that the second snippet has two commands within the parentheses.
Could somebody please explain the rule for when subshells are created?
CODE SNIPPET A:
sleep 5 ( sleep 5 )
CODE SNIPPET B:
sleep 5 ( x=1 sleep 5 )
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
The parentheses always start a subshell. What’s happening is that bash detects that sleep 5 is the last command executed by that subshell, so it calls exec instead of fork+exec. The sleep command replaces the subshell in the same process.
In other words, the base case is:
( … )create a subshell. The original process callsforkandwait. In the subprocess, which is a subshell:sleepis an external command which requires a subprocess of the subprocess. The subshell callsforkandwait. In the subsubprocess:- The subsubprocess executes the external command →
exec. - Eventually the command terminates →
exit.
- The subsubprocess executes the external command →
waitcompletes in the subshell.
waitcompletes in the original process.
The optimization is:
( … )create a subshell. The original process callsforkandwait. In the subprocess, which is a subshell until it callsexec:sleepis an external command, and it’s the last thing this process needs to do.- The subprocess executes the external command →
exec. - Eventually the command terminates →
exit.
waitcompletes in the original process.
When you add something else after the call the sleep, the subshell needs to be kept around, so this optimization can’t happen.
When you add something else before the call to sleep, the optimization could be made (and ksh does it), but bash doesn’t do it (it’s very conservative with this optimization).
Method 2
From the Advanced Bash Programming Guide:
“In general, an external command in a script forks off a subprocess, whereas a Bash builtin does not. For this reason, builtins execute more quickly and use fewer system resources than their external command equivalents.”
And a little further down:
“A command list embedded between parentheses runs as a subshell.”
Examples:
[<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="2b5944445f6b5f4a474a594a">[email protected]</a> test]# echo $BASHPID 10792 [<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="46342929320632272a273427">[email protected]</a> test]# (echo $BASHPID) 4087 [<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0e7c61617a4e7a6f626f7c6f">[email protected]</a> test]# (echo $BASHPID) 4088 [<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="3a4855554e7a4e5b565b485b">[email protected]</a> test]# (echo $BASHPID) 4089
Example using OPs code (with shorter sleeps because I am impatient):
echo $BASHPID
sleep 2
(
echo $BASHPID
sleep 2
echo $BASHPID
)
The output:
[<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f5879a9a81b5819499948794">[email protected]</a> test]# bash sub_bash 6606 6608 6608
Method 3
An additional note to @Gilles answer.
As said by Gilles: The parentheses always start a subshell.
However, the numbers that such sub-shell have might repeat:
$ (echo "$BASHPID and $$"; sleep 1) 2033 and 31679 $ (echo "$BASHPID and $$"; sleep 1) 2040 and 31679 $ (echo "$BASHPID and $$"; sleep 1) 2047 and 31679
As you can see, the $$ keeps repeating, and that is as expected, because (execute this command to find the correct man bash line):
$ LESS=+/'^ *BASHPID' man bash
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.
That is: If the shell is not re-initialized, the $$ is the same.
Or with this:
$ LESS=+/'^ *Special Parameters' man bash
Special Parameters
$ Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell.
The $$ is the ID of the current shell (not the subshell).
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0