I know that external commands are run in the shell by creating a separate process, but what exactly happens when a built-in command is run in a shell?
Are they executed as a function, or does the shell create a new thread to execute them?
For your concrete example, there is a function
cd_builtin, which is defined in builtins/cd.def (in the bash source code). It normally does a
cd by calling that function. But it may fork first if you use it in a pipeline—for example,
cd / | echo forks and calls
cd_builtin in the child. You can also notice this by how the directory doesn't actually change:
anthony@Zia:~$ cd /tmp/ anthony@Zia:/tmp$ cd / | echo -n anthony@Zia:/tmp$ cd / anthony@Zia:/$
Notice how the directory only changes when I don't pipe from
Built-in commands, by definition, are executed inside the main executable, as opposed to in a different program.
All shell commands are synchronous: the shell waits for the command to complete before it executes the next one. When running an external command, the shell has to create a separate process to run that command, and wait for it to exit. When running a built-in command, there is no need to create a new thread of execution: the command is executed inside the main thread.
I don't think any of the common shells use threads in their internal design. Shell capabilities are pretty tied with the traditional single-thread-per-process Unix model, there's no problem in shell design that threads would solve.
It's highly likely that every builtin is implemented by a function at some level in the shell's source code.
I would think they run as a function within the space of the
bash executable. There is a comment in the Advanced Bash Scripting Guide that states it as follows:
A builtin is a command contained within the Bash tool set, literally built in. This is either for performance reasons -- builtins execute faster than external commands, which usually require forking off 1 a separate process -- or because a particular builtin needs direct access to the shell internals.