Advanced Bash Scripting
Refresher
Built in Commands
Every time you invoke an external binary in a shell script, the shell forks a new process—adding latency and consuming CPU/memory. Bash and other modern shells mitigate this overhead by providing built-in commands that execute inside the shell process. Leveraging built-ins can dramatically speed up your scripts and reduce resource usage.
In this guide, we’ll cover:
- Command categories and types
- How to identify built-in commands
- Performance benefits and benchmarks
- Verifying process creation with
strace
- Listing all built-in commands and keywords
The Chef Analogy
Think of your shell as a chef in a kitchen. If the chef delegated every simple task—chopping vegetables, stirring soup, plating food—to sous-chefs, the overhead would be enormous. Instead, the chef handles routine tasks directly and only calls for help on specialized jobs. Built-in commands work the same way: the shell “chef” handles them instantly, while external binaries require spawning a separate “assistant” process.
Command Categories and Types
Shell commands fall into two main categories:
Category | Description | Examples |
---|---|---|
Built-in Command | Implemented inside the shell; runs without forking a new process. | cd , echo , true |
External Binary | Stored on disk; invoking them forks a new process then uses execve . | /bin/ls , /usr/bin/cat |
Example:
$ ls
file1.txt file2.txt
$ echo "Hello, world!"
Hello, world!
In the first case, ls
is an external binary loaded from disk; echo
is handled directly by the shell.
Identifying Built-ins vs. External Binaries
Use the type
built-in to check how a command is implemented:
$ type echo
echo is a shell builtin
$ type cat
cat is /usr/bin/cat
Note
You can also use which
or command -v
, but type
gives the most accurate distinction between built-in, keyword, and function.
Performance Benefits of Built-ins
Benchmarks: true
vs /usr/bin/true
The true
command simply returns a zero exit status. Compare its built-in version to the external binary:
$ time true
real 0m0.000s
user 0m0.000s
sys 0m0.000s
$ time /usr/bin/true
real 0m0.009s
user 0m0.001s
sys 0m0.005s
Command Execution Times at a Glance
Command | Built-in (real) | External /usr/bin (real) |
---|---|---|
true | 0.000s | 0.009s |
echo | 0.000s | 0.324s |
Built-ins not only start faster but also complete quicker, especially when called repeatedly in loops.
Verifying Process Creation with strace
To confirm built-ins don’t fork, trace execve
system calls in your current shell:
- Find your shell’s PID
$ pgrep -o bash 56120
- Attach
strace
and filter forexecve
sudo strace -Tfp $(pgrep -o bash) 2>&1 | grep execve &
- In another terminal, run a built-in vs an external binary
$ echo "Hello" Hello # No execve call appears for echo $ cat file.txt [pid 56147] execve("/usr/bin/cat", ["cat","file.txt"], 0x... ) = 0 <0.000186> Hello, world!
You’ll see execve
only for cat
, confirming echo
runs inside the shell.
External vs. Built-in Counterparts
Many built-ins have binary counterparts on disk:
$ which echo
/bin/echo
$ type /bin/echo
/bin/echo is /bin/echo
$ type echo
echo is a shell builtin
Performance comparison:
$ time /usr/bin/echo "Test"
$ time echo "Test"
# real 0m0.000s
Built-in echo
completes instantly compared to the external /usr/bin/echo
.
Listing Built-ins and Keywords
- List built-in commands:
$ compgen -b
- List shell keywords:
$ compgen -k
- Check if a word is a keyword:
$ type time time is a shell keyword
Warning
Keywords (like time
, if
, for
) are parsed by the shell and do not spawn new processes. Confusing them with external binaries can lead to unexpected behavior.
For a comprehensive list of shell built-ins and keywords, see the Bash manual:
References
Watch Video
Watch video content