If you've ever opened a terminal, typed a command, and pressed Enter, you've probably taken for granted that Bash somehow knows what to do next.
But have you ever wondered what actually happens behind the scenes?
How does Bash know whether cd is a built-in command, an executable program, a shell function, or perhaps something you've created yourself? Why does one command work perfectly while another suddenly says "command not found"?
Understanding how Bash executes commands is one of the most important concepts in shell scripting and Linux administration. It doesn't just help you write better scripts; it also helps you debug strange behavior when things don't work as expected.
In this post, we'll take a deep dive into the order Bash uses to search for commands, explain what built-in commands really are, discuss aliases and shell functions, and even look at what happens when Bash can't find your command at all.
So let's find out what really happens when you press Enter.
Pressing Enter Isn't Magic
When you type something like:
ls -la
and press Enter, Bash doesn't immediately execute the ls program.
Instead, it performs several steps to determine what exactly you meant by the word:
ls
The shell goes through a predefined lookup order until it finds something that matches your command.
This lookup order is extremely important because multiple things may share the same name.
For example, all of these are perfectly valid:
- a shell function named
ls - an alias named
ls - an executable file named
ls - a Bash built-in command named
ls(hypothetically)
Which one should Bash execute?
The answer depends entirely on Bash's command lookup order.
The Bash Command Lookup Order
When Bash receives a command, it searches for it in the following order:
- Shell keywords.
- Aliases.
- Special built-in commands.
- Shell functions.
- Regular built-in commands.
- Executable programs and shell scripts.
- The
command_not_found_handle()function (if one exists).
Let's look at each one individually.
Step 1 - Shell Keywords
The very first thing Bash checks is whether your command is one of its reserved keywords.
Examples include:
if
then
else
fi
for
while
until
case
do
done
select
function
time
These aren't commands in the traditional sense. They are part of the Bash language itself.
For example:
if true
then
echo "Hello!"
fi
Here, Bash isn't launching an executable program called:
if
Instead, it recognizes the keyword and parses the syntax accordingly.
Keywords always have the highest priority.
Step 2 - Aliases
The next thing Bash checks is its alias system.
Aliases are simply shortcuts that expand into other commands.
For example:
alias ll='ls -lah'
Now whenever you type:
ll
Bash automatically replaces it with:
ls -lah
Aliases are extremely useful for:
- shortening long commands
- customizing command behavior
- creating your own shortcuts
You cannot create an alias that replaces Bash syntax itself, but you can create aliases that use shell keywords.
For example:
alias update='sudo apt update && sudo apt upgrade'
Simple and very convenient.
Step 3 - Special Built-In Commands
After aliases, Bash looks for special built-in commands.
Examples include:
break
continue
eval
exec
exit
export
readonly
return
set
shift
times
trap
unset
These commands are special because they are defined by the POSIX standard and behave slightly differently from regular built-ins.
Why does this matter?
Because Bash treats them differently during execution and error handling.
For example:
export MY_VARIABLE="Hello"
or:
exit
These commands are so fundamental that Bash executes them internally rather than launching external programs.
Step 4 - Shell Functions
Shell functions are one of the most powerful features of Bash.
For example:
hello()
{
echo "Hello World!"
}
Now simply running:
hello
executes the function.
What's interesting is that shell functions can even override built-in commands.
For example:
cd()
{
command cd "$@"
echo "Current directory:"
echo "$PWD"
}
Now every time you change directories, Bash will automatically display your new location.
Example:
cd Documents
Output:
Current directory:
/home/pavel/Documents
This is an extremely useful technique for:
- debugging
- logging
- extending existing Bash functionality
- creating custom development environments
To learn more about defining and using functions in Bash, read my detailed guide: Bash Functions Explained: The name() Function Syntax.
A Small Note About POSIX Mode
There's an interesting detail worth mentioning.
If Bash is running in POSIX compatibility mode, the lookup order changes slightly.
Normally, Bash executes functions before special built-in commands.
In POSIX mode, however, special built-ins take precedence.
For most Linux users, this isn't something you'll encounter every day, but it's good to know if you're writing highly portable shell scripts.
Step 5 - Regular Built-In Commands
Next come the regular built-in commands.
Some popular examples include:
cd
pwd
test
echo
type
history
help
jobs
These commands are implemented directly inside Bash itself.
The advantage is obvious:
- faster execution
- no external programs required
- always available
For example:
cd /tmp
No executable file is launched.
Bash simply performs the directory change internally.
Step 6 - Executable Programs and Scripts
Finally, Bash searches for executable programs and shell scripts.
It uses the directories listed in your:
PATH
environment variable.
You can view it by running:
echo $PATH
Example output:
/usr/local/bin:/usr/bin:/bin:/usr/sbin
Bash searches these directories from left to right.
Suppose you type:
python3
Bash will search:
/usr/local/bin
/usr/bin
/bin
/usr/sbin
...
until it finds:
python3
The first match wins.
This is why the order of directories inside your PATH variable is so important.
Which Command Is Bash Actually Running?
If you've ever wondered what Bash is executing, use:
type command_name
For example:
type cd
Output:
cd is a shell builtin
Or:
type ls
Output:
ls is /usr/bin/ls
Or:
type ll
Output:
ll is aliased to 'ls -lah'
This command is incredibly useful when debugging shell scripts.
What Happens When Bash Can't Find a Command?
If Bash searches everywhere and still can't find your command, something interesting happens.
It checks whether the following function exists:
command_not_found_handle()
If you've defined it, Bash automatically executes it.
For example:
command_not_found_handle()
{
echo "Oops! Command not found."
}
Now running:
my_super_ai_server
produces:
Oops! Command not found.
Ubuntu actually uses this mechanism to suggest packages that contain missing commands.
You've probably seen messages like:
Command 'htop' not found.
You can install it by running:
sudo apt install htop
That's not magic—it's simply Bash being helpful.
Understanding Built-In Commands
Many Bash beginners assume that every command they use is an executable program.
That's not true.
Some commands are:
- keywords
- aliases
- shell functions
- built-ins
- external programs
For example:
type echo
returns:
echo is a shell builtin
while:
type grep
returns:
grep is /usr/bin/grep
Learning the difference will help you understand:
- shell performance
- scripting portability
- command behavior
- Bash internals
What About Child Processes?
There's one more interesting detail that every Bash programmer should know.
If Bash exits because it receives the:
SIGHUP
signal, or if the:
huponexit
option is enabled, it sends the same signal to all of its child processes.
This can become a problem when you're running:
- long-running scripts
- background tasks
- servers
- Docker containers
- AI workloads
- build processes
Fortunately, Bash provides a convenient solution:
disown
For example:
disown -h %1
or:
disown -a
This tells Bash not to send the hangup signal when it exits.
It's an incredibly useful command for system administrators and developers working with background jobs.
Practical Example
Let's imagine you've created all of the following:
alias hello='echo Alias'
hello()
{
echo Function
}
and there's also an executable file named:
hello
Which one wins?
The answer is:
Alias
Remove the alias:
unalias hello
Now Bash executes:
Function
Remove the function:
unset -f hello
Now Bash executes:
Executable file
Understanding the lookup order suddenly becomes very useful.
Final Thoughts
Every time you press Enter in your terminal, Bash performs quite a bit of work before it actually executes anything. It searches through keywords, aliases, built-ins, functions, and executables until it finds the best match for the command you've entered.
Knowing this lookup order will help you write better shell scripts, customize your development environment, and debug problems much faster.
The next time a command behaves strangely or doesn't do what you expect, remember that Bash isn't being mysterious—it's simply following its rules. Once you learn those rules, you'll start to appreciate just how elegant and flexible the Bash shell really is.