Hi Matthew,
I have a couple questions:
1. How are you testing this? (small concrete cases are helpful)
2. Do you have some specific builtins that exhibit that behavior? (it
doesn't make sense for builtins to fork on their own, unless you're
doing an explicit subshell. Like you mentioned, executing cd in a
child process is moot
3. Can you send straces that show these forks?
I cannot reproduce this behavior:
address@hidden:~$ PS1= strace -fe clone,execve,chdir
bash -c 'builtin echo $BASH_VERSION; cd /; /bin/echo'
execve("/bin/bash", ["bash", "-c", "builtin echo $BASH_VERSION; cd
/"...], [/* 16 vars */]) = 0
4.3.30(1)-release
chdir("/") = 0
clone(child_stack=0,
flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0x7fa6dbec69d0) = 1490
Process 1490 attached
[pid 1490] execve("/bin/echo", ["/bin/echo"], [/* 16 vars */]) = 0
[pid 1490] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1490,
si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
address@hidden:~$ strace -fe clone,execve,chdir bash -c
'builtin echo $BASH_VERSION; cd /; /bin/echo'
execve("/bin/bash", ["bash", "-c", "builtin echo $BASH_VERSION; cd
/"...], [/* 15 vars */]) = 0
4.3.30(1)-release
chdir("/") = 0
clone(child_stack=0,
flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0x7f0405bf29d0) = 1497
Process 1497 attached
[pid 1497] execve("/bin/echo", ["/bin/echo"], [/* 16 vars */]) = 0
[pid 1497] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1497,
si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
address@hidden:~$ strace -fe clone,execve,chdir bash -c 'builtin echo
$BASH_VERSION; cd /; /bin/echo'
execve("/bin/bash", ["bash", "-c", "builtin echo $BASH_VERSION; cd
/"...], [/* 26 vars */]) = 0
4.3.46(1)-release
chdir("/") = 0
clone(child_stack=0,
flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0x7f87132ba9d0) = 3661
strace: Process 3661 attached
[pid 3661] execve("/bin/echo", ["/bin/echo"], [/* 26 vars */]) = 0
[pid 3661] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3661,
si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
address@hidden ~]$ strace -fe clone,execve,chdir bash -c 'builtin
echo $BASH_VERSION; cd /; /bin/echo'
execve("/bin/bash", ["bash", "-c", "builtin echo $BASH_VERSION; cd
/"...], [/* 42 vars */]) = 0
4.1.2(1)-release
chdir("/") = 0
clone(child_stack=0,
flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0x7f9e205989d0) = 29707
Process 29707 attached
[pid 29707] execve("/bin/echo", ["/bin/echo"], [/* 43 vars */]) = 0
[pid 29707] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=29707,
si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
There are definitely some optimization strategies bash uses under some
cases were it avoids forking (but this applies to external commands),
like this case:
address@hidden:~$ strace -fe clone,execve,chdir bash -c '/bin/echo
$BASH_VERSION'
execve("/bin/bash", ["bash", "-c", "/bin/echo $BASH_VERSION"], [/* 26
vars */]) = 0
execve("/bin/echo", ["/bin/echo", "4.3.46(1)-release"], [/* 25 vars */]) = 0
4.3.46(1)-release
+++ exited with 0 +++
Since it's a single command in the -c ..., which means that it's safe
for bash to do exec to replace the current process with the external
command.