2

One of the things I've noticed about BASH and other UNIX shells is that, by default and when used in typical ways, they spawn subshells for almost everything.

e.g.

foo=$(grep "someword" /path/to/somefile | awk '{print $3}')

would spawn two new sessions of bash just to load some text into a variable.

a) Why do shells do this? If a command line program operates on file descriptors, one shouldn't need to spawn a new non-interactive shell session just to feed it a descriptor, right?

b) How can I best work around this, when loading command output into variables? I know that in bash you can use:

read -r -d '' < <(...)

to set a variable from a command without using a subshell, but this rather cumbersome, and I'm looking for better (and more portable) methods. (Likewise it would be cool if anyone knew general alternatives to pipes and command substitution that did not involve subshells.)

NB: I know "user Perl/Python/Ruby" is probably the "correct" solution, but those all tend to need a lot of boilerplate code to do file manipulation, external command invocation, etc.

Edit: thanks for the answers below, however this still does not explain why process substitution must fork new shells even for builtin commands:

$ builtin echo $(builtin echo $(builtin echo $BASH_SUBSHELL))
2
  • See here. Try using exec. – eyoung100 Sep 23 '14 at 16:10
  • That doesn't work the way you say it does. Using 'exec' in a command substitution just forks a shell that immediately exits, incurring the full overhead and other issues of spawning a subshell; you can verify this by '/usr/bin/echo'ing $BASH_SUBSHELL within such an invocation. – DanL4096 Sep 23 '14 at 16:26
  • See Vinc17's answer below. An implicit exec vs something like an explicit exec: exec 2>/dev/null – eyoung100 Sep 23 '14 at 16:32
  • foo=$(grep ... | awk) spawns one subshell, for the command substitution. – chepner Sep 26 '14 at 2:09
  • 1
    You can spare one process: foo=$(awk '/someword/ {print $3}' /path/to/somefile)... – jasonwryan Oct 30 '14 at 0:10
3

To execute a command such as grep or awk, the shell must fork, which means that you get a subshell. The only exceptions are when the command is a builtin or when the command is the last command. But this latter case is just an optimization done by some shells and cannot be done under some conditions that might change the behavior (e.g. existing traps); basically this is an implicit exec. Hence your two subshells for

foo=$(grep "someword" /path/to/somefile | awk '{print $3}')

To avoid subshells, there are various workarounds, and you need to find them depending on the context...

  • Right, because of the fork/exec system of process spawning on UNIX. I had forgotten that UNIX does not have a single syscall to spawn a new process... D'oh! – DanL4096 Sep 23 '14 at 16:37
3

Focussing on a):

Executing the command

foo=$(echo bar)

starts a subshell, because the command is using a command substitution, which implies a subshell as the environment that is used.

The command explicitly runs a subshell, as simple as that.
And that does not change if you nest it.


Now, after the question "Why is the shell doing that" is answered:
Why are you asking that?

It's an interesting topic, but I do not fully see what you are aiming at;
Some wild guesses:

  • Proposing an optimizing bash implementation?
  • Reducing count of forks? For performance reasons? For some kind of aesthetic reasons? It's the fastest way to provide a separate environment, including address space - a process.
  • Changing the shell's syntax? To reach exactly what goal?

If you like, some add details to the question, an leave me a comment.



From man bash

COMMAND EXECUTION ENVIRONMENT
       [ ... ]

       Command substitution, commands grouped with  parentheses,  and  asyn‐
       chronous  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 par‐
       ent 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 envi‐
       ronment.

       [ ... ]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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