Linux & Bash · Part I · Module 1 · Linux, the Shell and the Command Line
Builtins, External Programs, and How Bash Decides
[Chapter 1.2](/linux-bash/what-a-shell-is) said the shell decides what kind of thing your command is before anything runs. This is that chapter. Most commands are separate programs; a small number are built into Bash itself; and the reason the second group exists at all is a genuine constraint rather than an optimisation. Working through that constraint gives you something far more valuable than a list of builtins: the parent-and-child model of processes, which later explains why a setup script has to be sourced, why a script cannot leave you in a different directory, and why a tool version can differ between two sessions on the same machine.
Foundation18 min readLinuxBashPart IFundamentalsProcessesMental Model
Part I · Module 1 · Chapter 1.7 · Builtins, External Programs, and How Bash Decides
1. Two Kinds of Command
Chapter 1.1 established that most commands are ordinary files on disk. That was true and incomplete. There are two kinds:
- External programs. A file on disk. Bash locates it and the kernel runs it as a separate process.
grep,ls,sort, a compiler, a simulator. - Shell builtins. Code inside Bash itself. No separate file runs; the shell does the work in its own process.
cd,pwd,echo,export.
This is not trivia about implementation. The two kinds behave differently in a way you can observe, and one of them has to work the way it does.
2. The Instrument: type
Before any argument, get an instrument. type asks Bash what it would do with a word.
type cdcd is a shell builtintype grepgrep is /usr/bin/grepTwo different answers to the same question, and they are the two kinds from §1. cd is part of the shell; grep is a file at a particular path.
Two useful variations. type -t gives a single word, which is what you want when you only need the category:
type -t cd
type -t grepbuiltin
fileAnd type -a shows every match, not just the winner:
type -a echoecho is a shell builtin
echo is /bin/echoThat last output is the interesting one, and §7 is about it: there are two things called echo on this machine, and Bash told you which one it would use first.
3. External Programs: a Child Doing the Work
When Bash runs an external program, something specific happens: the kernel creates a new process — a separate running program with its own identity — and that process runs the program file.
The new process is a child of your shell. For this chapter, three properties of a child matter:
- It gets a copy of things from its parent, including the working directory it starts in and a set of variables Module 10 will name properly.
- The copy is its own. Changes it makes affect its copy, not the parent's.
- It ends. When the program finishes, the process is gone, and everything private to it goes with it — including any changes it made to its copy.
Property 3 is the one people do not think through, and it decides the whole chapter. Module 7 teaches processes in full. The three properties above are all this chapter needs, and they are enough to derive something useful.
4. Why cd Cannot Be a Program
Here is the argument, and then the demonstration.
cd has one job: change the working directory of your shell, so that your next command — typed into that same shell — is interpreted in the new place.
Suppose cd were an external program. Then running cd /tmp would:
- create a child process;
- that child changes its own working directory to
/tmp; - the child exits, and its working directory goes with it;
- your shell, which never changed, is exactly where it was.
The command would be a no-op with extra steps. Not slow, not buggy — structurally incapable of doing the one thing it is for. Something that must change the shell's own state has to run inside the shell. That is what a builtin is.
The demonstration
This needs no new syntax. Chapter 1.3 showed that shells nest: you can run bash inside Bash and get a second shell in the same window. A nested shell is a child process, so it is a perfect stand-in for any program your shell starts.
engineer@server:~$ pwd
/home/engineer
engineer@server:~$ bash
engineer@server:~$ cd /tmp
engineer@server:/tmp$ pwd
/tmp
engineer@server:/tmp$ exit
exit
engineer@server:~$ pwd
/home/engineerRead it line by line:
- The first shell is in
/home/engineer. bashstarts a child shell. Same window, different process — Chapter 1.3's second proof.- Inside the child,
cd /tmpworks perfectly.pwdconfirms it, and the prompt updates, because the child shell is printing its own prompt. exitends the child.- The first shell reports
/home/engineer. It never moved.
The child genuinely changed its working directory, and the change was real right up until the moment the child ended. Then it was gone. Any external cd would behave exactly like that child — which is why cd is a builtin, in every shell, by necessity.
5. The Two Kinds, Side by Side
The figure also explains a question nobody asks out loud: why is grep not a builtin? Because it does not need to be. grep reads files and writes output; none of that requires touching the shell's own state. Keeping it a separate program is better in every way — it can be updated independently, replaced with a different implementation, used by programs that are not shells, and it cannot crash your shell when it fails.
The rule for which kind a command is: a command is a builtin when it must affect the shell itself, or when it is so small and so frequently used that a separate process would be wasteful. Everything else is a program.
6. The Builtins Worth Recognising Now
Not a list to memorise. A list to recognise, grouped by why each one has to be internal.
| Builtin | Why it cannot be an external program |
|---|---|
cd | changes the shell's working directory |
export | changes the shell's own variables (Module 10) |
source | runs lines in this shell — the whole point is not being a child (Module 10) |
exit | ends this shell |
cd's companions pushd, popd | same reason as cd |
read | stores a value in one of the shell's variables (Module 8) |
type, help | report on the shell's own internal state |
And the ones that are builtins for speed rather than necessity — echo, pwd, test, true, false, printf, kill. Each of these also exists as a program on most systems, which is the subject of the next section.
Bash can list every one of them:
helpThat is Chapter 1.6's command, and this is what it is actually for: it enumerates the commands that are part of Bash.
7. When Both Exist
type -a echo reported two echos. Both are real:
- the builtin, inside Bash;
- a program, usually at
/bin/echo, installed by the distribution.
The builtin wins. When both exist, Bash uses its own. That is one step of the resolution order in §9, and it is the only step this chapter needs to explain.
Why keep the external one at all? Because not everything that runs a command is Bash. A program that launches another program directly — and Module 14's find is the example you will meet first — needs a real file to launch. A different shell may not have the builtin. So the file stays.
8. which Is the Wrong Instrument
This section exists because the mistake is near-universal and produces confident wrong answers.
which is an external program whose job is to look for files. It therefore cannot see the things that are not files — builtins, shell functions and aliases — and it does not know Bash's resolution order.
Ask it about a builtin and you get an answer that is not what will run:
which cd/usr/bin/cdThere really is a file called /usr/bin/cd on some systems. It is not what runs when you type cd — the builtin is, as type cd correctly reported. On other systems which cd prints nothing and reports failure, which is a different wrong answer to the same question.
Compare:
type cdcd is a shell builtintype is a builtin, so it answers from inside the shell, with knowledge of the shell's own rules. That is the whole difference.
9. How Bash Decides, Stated Honestly
With an instrument and the two kinds in hand, here is the decision — stated completely, explained only as far as this module goes.
When Bash has a command name, it checks, in this order:
- an alias — a short name you or a setup file defined;
- a shell function — a named block of shell code (Module 18);
- a builtin — part of Bash itself;
- a file on disk, found by searching a list of directories.
The first match wins, and type reports it.
What this chapter explains: step 3 beating step 4, which is why the echo builtin wins over /bin/echo.
What is deliberately deferred. Steps 1 and 2 are Module 10 and Module 18. Step 4 is the big one: the list of directories is called PATH, how it is searched decides which copy of a program you get, and Module 10 owns that lesson completely. It is deferred because PATH is an environment variable, and the environment needs variables (Module 8) to be taught properly first. Half-teaching it here would mean teaching it twice.
What you can take now is the shape: a command name is resolved by a known order, the order is checkable, and type -a shows you everything that matched.
type -a pwdpwd is a shell builtin
pwd is /bin/pwdChapter 1.4 parked exactly this. Here is the answer: pwd is a builtin, there is also a program of that name, and the builtin is what ran.
10. Why This Matters in Engineering Work
Three consequences, none requiring semiconductor knowledge to follow.
"Which tool am I actually running?" On a shared machine with several versions of a tool installed, the honest answer comes from type. And if a colleague's setup file defined an alias or a function with the same name as a real tool, type will say so and which will not — it cannot see either. When a command behaves unlike its documentation, type is the first thing to run.
type -a python3python3 is aliased to `python3 -X utf8'
python3 is /usr/bin/python3"Why did the setup script do nothing?" Because it ran as a child, changed its own copy of the environment, and exited — §4's rule, in its most expensive form. Running an environment-setup script the obvious way is one of the most common first-week errors in an engineering environment, and the fix is source. Module 10 teaches it properly and Part II Module 25 applies it to real tool setup. You already have the reason.
"Why is my script not where I left it?" Same rule. A script runs as a child, so a cd inside it moves the child, not you. Module 15 builds scripts knowing this; Module 20 makes them check rather than assume.
11. Misconceptions
"Every command is a file in /bin." Builtins are not files anywhere. cd has no program behind it.
"cd is a program that Bash happens to include." It cannot be a program at all. §4 is a proof, not a preference.
"which tells me what will run." It looks for files, cannot see builtins, functions or aliases, and does not implement Bash's resolution order. Use type, or command -v when you want only a path.
"The builtin and the external program are the same thing twice." They share a name and often behave alike. They are different code with different rules, and echo is where the difference bites.
"A program I run can change my shell's directory or variables." It cannot. It gets a copy. This is the rule that half of Part I's later surprises reduce to.
"Builtins are faster, so more things should be builtins." They avoid creating a process, which matters inside a loop running thousands of times. It is not a reason to want grep inside the shell — a separate program is independently updatable, replaceable, usable by non-shells, and unable to take your shell down with it.
12. Check Your Understanding
Question 1. Explain, in terms of parent and child processes, why an external cd program could not do cd's job. Use the word copy.
Question 2. type -a echo lists a builtin and /bin/echo. Which runs when you type echo hello, and why does the file still exist?
Question 3. which cd prints /usr/bin/cd on a machine where type cd says cd is a shell builtin. Which is correct about what will run, and why does the other one get it wrong?
Question 4. A colleague runs a setup script and reports that "nothing happened" — no tool is available afterwards. Name the rule from §4 that explains it, and the builtin that would have been the right way to run it.
Question 5. Give Bash's four-step resolution order from memory. Which step does this chapter explain, and which step is deferred to Module 10?
Question 6. You run type -a sort and get two lines, the first naming a shell function. What does that tell you, and why would which sort have misled you?
13. Remember
- Two kinds of command. External programs are files on disk, run as child processes. Builtins are code inside Bash, run in the shell's own process.
typeis the instrument.type cmdnames the kind,type -tgives one word,type -ashows every match in resolution order.- A child process gets a copy and cannot change its parent. It modifies the copy, then exits and takes it with it.
cdmust therefore be a builtin. An externalcdwould move a child that is about to disappear. The nested-shell session in §4 demonstrates it in five lines.- That one rule returns repeatedly — sourcing a setup script, a script's directory, variables a program cannot hand back, and tool environments in Part II.
- When both forms exist, the builtin wins — and the two are not guaranteed to behave identically. Prefer
printfoverechowhere output must be predictable. whichis the wrong instrument. It looks for files and cannot see builtins, functions or aliases. Usetype, orcommand -vfor a bare path.- Resolution order: alias, function, builtin, then a file on disk. This chapter explains builtin-over-file;
PATHand the rest belong to Module 10.
One chapter of Module 1 remains, and it is the one that makes the rest of the course faster to practise: recalling, editing and re-running the commands you have already typed.
Where this fits
Part of the Linux & Bash curriculum.
