4

I'm confused how the quoting and parameter and glob expansion is supposed to work in a subshell. Does the quoting and expansions of the subshell command line always happen in the context of the subshell process? My test seems to confirm this.

Tuomas@DESKTOP-LI5P50P MINGW64 ~/shell/test1/test
$ ls
a  b  c

Tuomas@DESKTOP-LI5P50P MINGW64 ~/shell/test1/test
$ echo "$(echo *)"
a b c
# The subshell expands the * glob

Tuomas@DESKTOP-LI5P50P MINGW64 ~/shell/test1/test
$ echo $(echo '*')
a b c
# The subshell outputs literal *, parent shell expands the * glob

Tuomas@DESKTOP-LI5P50P MINGW64 ~/shell/test1/test
$ echo $(echo "*")
a b c
# The subshell outputs literal *, parent shell expands the * glob

Tuomas@DESKTOP-LI5P50P MINGW64 ~/shell/test1/test
$ echo "$(echo '*')"
*
# The subshell outputs literal *, parent shell outputs literal *

Tuomas@DESKTOP-LI5P50P MINGW64 ~/shell/test1/test
$ foo=bar

Tuomas@DESKTOP-LI5P50P MINGW64 ~/shell/test1/test
$ echo "$(echo $foo)"
bar
# The subshell expands variable foo

Tuomas@DESKTOP-LI5P50P MINGW64 ~/shell/test1/test
$ echo $(echo '$foo')
$foo
# The subshell outputs literal $foo

Tuomas@DESKTOP-LI5P50P MINGW64 ~/shell/test1/test
$ echo $(echo "$foo")
bar
# The subshell expands variable foo

Tuomas@DESKTOP-LI5P50P MINGW64 ~/shell/test1/test
$ echo "$(echo '$foo')"
$foo
# The subshell outputs literal $foo

Am I correct? Is there any scenarios, where parents shell will somehow process or evaluate the subshell command line before forking?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Tuomas Toivonen
  • 21,690
  • 47
  • 129
  • 225
  • You are correct. Parameter expansions in a subshell are run *in that subshell*. That said, it's not obvious to me what aspects of the truth/faultiness of that assertion most of your tests are intended to show. – Charles Duffy Aug 14 '18 at 16:46
  • A better way to test this would be to compare `echo "$BASHPID"` with `echo "$(echo "$BASHPID")"` – Charles Duffy Aug 14 '18 at 16:47
  • 1
    ...well, referring to the *quoting* is a different matter -- quoting is evaluated (on a character-by-character basis!) at parse time, whereas expansions happen at execution time. But the expansions always happen post-fork. – Charles Duffy Aug 14 '18 at 16:48
  • One thing that might help to ensure that an answer is useful and applicable: What's the real-world use case you're considering that hinges on the answer to this question? – Charles Duffy Aug 14 '18 at 16:58

1 Answers1

2

The parsing -- thus, the determination of which content is quoted how -- happens before the fork. Because the forked-off copy of the shell has a copy-on-write instance of the memory of its parent process, it also has the parse tree, and inherits this information from its parent.

The parameter expansion (of $foo in your examples) happens after the fork. Similarly, the contents of foo are first string-split and glob-expanded to generate an argument list to pass to echo inside the subshell. Then that subshell runs echo, and the output written to stdout is read by the parent process (the original shell).

String-splitting and glob expansion of the command substitution's result (the content written by echo) happens back in the parent process, after it read the output of its child as a string.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • So it's *never* possible for the bash to expand anything on the subshell command before the fork? For example, if I needed the value `$BASHPID` of the parent shell in the subshell command line, my only options is to assign it into a new variable, and let the subshell inherit and expand that variable? I mean to pass something about the parent, that is not known until the runtime. – Tuomas Toivonen Aug 14 '18 at 16:59
  • 1
    Well, for `BASHPID` specifically there are other approaches (`$$` will be the parent shell's PID unless you're a subshell of a subshell, and you could pull the parent's process id out of procfs if targeting Linux), but in general, the statement above is correct. – Charles Duffy Aug 14 '18 at 17:01
  • Yeah, I meant in general. That clarified things – Tuomas Toivonen Aug 14 '18 at 17:02
  • (that said, there aren't all that many aspects of a process that *aren't* copied on `fork()` and shared between parent and child, so if it isn't the PID you're trying to retrieve, I'm actually curious which non-shared/per-process data it is that's pertinent to your use case). – Charles Duffy Aug 14 '18 at 17:08
  • Just theoretical reflection to internalize the concepts :) – Tuomas Toivonen Aug 14 '18 at 17:18
  • I thought the parent shell did the expansion of `$foo`. Does shell parse what is enclosed inside `$( ... )` differently? – codeforester Aug 14 '18 at 18:21
  • 1
    @codeforester, ...how would that work? If you ran, say, `echo "$(cd /; echo "$PWD")"`, the `PWD` *couldn't* be expanded until after the subshell has forked, tried to run the `cd`, and determined whether it succeeded. – Charles Duffy Aug 14 '18 at 19:43
  • That makes sense - `echo $PWD $(cd /some/other/dir; echo $PWD)` does show different values. However, what explains `echo $$ $(echo $$)` echoing the same PID in both parent and the subshell? Is `$$` a special case then? – codeforester Aug 14 '18 at 19:48
  • 1
    @codeforester, that's because `$$` is *supposed to* return the PID of the primary instance of a shell -- by design and documentation -- even when run in a subshell. That's the whole reason `BASHPID` exists; there would be no need for it otherwise. (So `$$` is expanded by the child, but it uses a value initialized by the parent at invocation time). – Charles Duffy Aug 14 '18 at 19:50
  • Thanks for clarifying, @CharlesDuffy. These primitives take a long time to sink in. Found this related post in U&L stackoverflow: [Is a sub-shell the same thing as a child-shell](https://unix.stackexchange.com/q/261638/201820). – codeforester Aug 14 '18 at 19:56