Table of Contents
- What Are Shell Built-Ins?
- Types of Shell Built-Ins
- How to Identify Shell Built-Ins
- Advantages of Using Built-Ins
- Common Shell Built-Ins with Examples
- Pitfalls and Considerations
- Advanced Usage: Custom Built-Ins and Debugging
- Conclusion
- References
What Are Shell Built-Ins?
A shell built-in (or “builtin”) is a command executed directly by the shell itself, rather than by spawning a separate process to run an external executable. Unlike external commands (e.g., /bin/ls, /usr/bin/grep), which are standalone programs stored on disk, built-ins are part of the shell’s codebase.
Key Difference: Built-Ins vs. External Commands
- External commands: When you run an external command (e.g.,
ls), the shell forks a new process (viafork()), loads the executable from disk, and executes it in the child process. The parent shell waits for the child to finish. - Built-ins: The shell executes the command directly in its own process. No forking, no disk I/O—just immediate execution.
Types of Shell Built-Ins
Bash built-ins serve diverse roles, from managing the shell environment to controlling script flow. Here’s a breakdown of common categories:
1. Core/Navigation Built-Ins
Essential for daily shell interaction, these handle navigation and basic I/O:
cd: Change the current working directory.echo: Print text to the terminal.pwd: Print the current working directory.
2. Environment Management
Modify the shell’s environment (variables, options) or process state:
export: Mark a variable for export to child processes.unset: Remove a variable or function.set: Set or unset shell options and positional parameters.shopt: Toggle advanced shell options (Bash-specific).
3. Job Control
Manage background/foreground processes:
jobs: List active jobs.fg: Bring a background job to the foreground.bg: Send a suspended job to the background.wait: Wait for background jobs to finish.
4. Command Execution & Scripting
Control how commands and scripts run:
exec: Replace the current shell process with a new command (no forking).source(or.): Execute a script in the current shell (instead of a subshell).command: Bypass shell functions/aliases to run the original command.
5. Flow Control
Bash scripting constructs for logic and loops:
if,then,else,elif,fi: Conditional statements.for,while,until: Loop constructs.case,esac: Pattern-matching conditional.
6. Arithmetic & Evaluation
Perform arithmetic operations or string evaluation:
let: Evaluate arithmetic expressions.(( )): Arithmetic evaluation (Bash-specific).[[ ]]: Extended test command (Bash-specific, for pattern matching).
7. Utility Built-Ins
Helper commands for shell administration:
alias,unalias: Create/remove command shortcuts.help: Display help for built-ins.hash: Cache paths to external commands for faster lookup.
How to Identify Shell Built-Ins
Not sure if a command is a built-in or external? Use these tools:
1. The type Command
The type built-in (yes, itself a built-in!) tells you how the shell interprets a command:
# Check if 'cd' is a built-in
type cd
# Output: cd is a shell builtin
# Check if 'ls' is external
type ls
# Output: ls is /usr/bin/ls
2. The help Command
help lists all Bash built-ins and provides documentation. Use help [command] for details:
# List all built-ins
help
# Get help for 'export'
help export
3. compgen -b
To list all Bash built-ins, use compgen -b (short for “completion generate built-ins”):
compgen -b | head -5 # List first 5 built-ins
# Output:
# :
# [
# alias
# bg
# bind
Advantages of Using Built-Ins
Why use built-ins instead of external commands? Here are key benefits:
1. Speed
Built-ins execute directly in the shell, avoiding the overhead of forking a new process (as external commands do). For scripts with loops or frequent command calls, this speedup is significant.
2. Access to Shell Internals
External commands run in a subshell and cannot modify the parent shell’s environment (e.g., variables, working directory). Built-ins, however, act directly on the shell:
# Example: 'export' modifies the shell's environment
MY_VAR="hello"
export MY_VAR # Built-in: makes MY_VAR available to child processes
# An external command CANNOT do this:
# ./external-script.sh # Even if it tries to 'export NEW_VAR', NEW_VAR won't exist in the parent shell
3. No Subshell Limitations
Commands run in a subshell (e.g., in a pipeline or background) cannot affect the parent shell. Built-ins run in the current shell, so their changes persist:
# 'cd' must be a built-in; an external 'cd' would fail:
cd /tmp # Works: changes the shell's working directory
# Hypothetical external 'cd' (would NOT work):
/usr/bin/cd /tmp # Fails: runs in a subshell, parent shell's directory unchanged
Common Shell Built-Ins with Examples
Let’s dive into practical examples of the most useful built-ins:
cd (Change Directory)
The quintessential navigation built-in. No external version exists because changing directories must modify the shell’s state:
cd /home/user/documents # Absolute path
cd ../downloads # Relative path (up one directory, then into 'downloads')
cd ~ # Home directory (shortcut for $HOME)
cd - # Previous directory (toggle between last two)
export (Environment Variables)
Mark variables for export to child processes. Critical for passing variables to scripts or commands:
# Define a variable (only in current shell)
APP_CONFIG="/etc/app.conf"
# Export it so child processes can access it
export APP_CONFIG
# Now, any command/script run from this shell can read $APP_CONFIG
./my-script.sh # Script can use $APP_CONFIG
source (or .) (Execute Script in Current Shell)
Run a script in the current shell instead of a subshell. Use this to load config files or set variables:
# Create a script with variables
echo "FOO=bar" > config.sh
# Run in subshell (variables NOT available in parent)
./config.sh
echo $FOO # Output: (empty)
# Run with 'source' (variables available in parent)
source ./config.sh # Or: . ./config.sh
echo $FOO # Output: bar
exec (Replace Current Process)
Replace the current shell process with a new command. Useful for “clean exits” in scripts:
# In a script: after setup, replace the script process with 'nginx'
echo "Starting Nginx..."
exec nginx # Script exits, and nginx runs in its place
[[ ]] (Extended Test)
Bash-specific built-in for advanced conditionals (supports pattern matching and regex):
name="Alice"
# Check if name starts with "A" (pattern matching)
if [[ $name == A* ]]; then
echo "Name starts with A!"
fi
# Check if a file exists and is readable
if [[ -r "/etc/passwd" ]]; then
echo "File is readable."
fi
(( )) (Arithmetic Evaluation)
Bash’s arithmetic context for numerical operations. Faster and cleaner than expr (an external command):
x=5
y=10
# Add x and y
sum=$((x + y))
echo $sum # Output: 15
# Increment x (equivalent to x=$((x + 1)))
((x++))
echo $x # Output: 6
Pitfalls and Considerations
While built-ins are powerful, watch for these edge cases:
1. Built-In vs. External Overlap
Some commands have both built-in and external versions (e.g., echo, printf, test). Use type -a to see all variants:
type -a echo
# Output:
# echo is a shell builtin
# echo is /usr/bin/echo
To force the external version, use command:
command echo "This uses the external echo (if available)"
2. Portability Across Shells
Built-ins (and their behavior) vary between shells (Bash, Zsh, POSIX sh). For example:
[[ ]]and(( ))are Bash/Zsh-specific; POSIX sh uses[ ]and$(( )).echo -n(no newline) is not portable—useprintfinstead for cross-shell scripts.
3. hash Caching
The hash built-in caches paths to external commands for speed. If you move an external command, hash may still point to the old path. Clear the cache with hash -r.
Advanced Usage: Custom Built-Ins and Debugging
Custom Built-Ins (Advanced)
Bash allows loading custom built-ins via shared libraries using enable -f:
# Compile a custom built-in (example C code)
gcc -fPIC -shared -o my_builtin.so my_builtin.c
# Load it into Bash
enable -f ./my_builtin.so my_builtin
# Use it
my_builtin "Hello, custom built-in!"
This is rare for most users but useful for performance-critical tools.
Debugging Built-Ins
Use set -x to trace built-in execution, or help for documentation:
# Trace execution of 'export'
set -x
export DEBUG=1
set +x # Disable tracing
# Get help for 'for' loops
help for
Conclusion
Shell built-ins are the backbone of Bash scripting. They provide speed, direct access to the shell’s internals, and avoid subshell limitations—making them indispensable for writing efficient, reliable scripts.
By mastering built-ins, you’ll:
- Write faster scripts (no forking overhead).
- Modify the shell environment with confidence (e.g.,
export,cd). - Avoid common pitfalls with subshells (e.g., using
sourceinstead of./script.sh).
Next time you use a command, run type [command] to see if it’s a built-in—you might be surprised by how many essential tools are part of the shell itself!
References
- GNU Bash Manual: Shell Builtins
man bash-builtins: Local manual page for Bash built-ins.helpcommand: Runhelpin Bash to list all built-ins, orhelp [command]for details.- TLDP Bash Guide for Beginners
- Bash Hackers Wiki: Builtins